初始代码

This commit is contained in:
2025-09-03 18:52:07 +08:00
parent 2d21a47e87
commit 6e3e30efa5
3249 changed files with 239147 additions and 0 deletions
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Android 12 or lower -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Android 13 or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Android 14 or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<application android:supportsRtl="true">
<activity
android:name="com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordActivity"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
android:screenOrientation="portrait" />
<activity
android:name="com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaEditActivity"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
android:screenOrientation="portrait" />
<activity
android:name="com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker.AlbumPickerActivity"
android:exported="false"
android:screenOrientation="portrait"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode|locale"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" />
</application>
</manifest>
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

@@ -0,0 +1,161 @@
{
"beauty_type_list": [
{
"beauty_type_name": "@string/multimedia_plugin_setting_panel_beauty",
"beauty_item_list": [
{
"item_type": 0,
"item_name": "@string/multimedia_plugin_beauty_cancel",
"item_level": -1,
"item_icon_normal": "@drawable/multimedia_plugin_beauty_cancel",
"item_filter_bitmap": ""
},
{
"item_type": 13,
"item_name": "@string/multimedia_plugin_panel_white",
"item_level": 1,
"item_icon_normal": "@drawable/multimedia_plugin_beauty_white",
"item_filter_bitmap": ""
},
{
"item_type": 10,
"item_name": "@string/multimedia_plugin_panel_style_smooth",
"item_level": 4,
"item_icon_normal": "@drawable/multimedia_plugin_beauty_smooth",
"item_filter_bitmap": ""
},
{
"item_type": 14,
"item_name": "@string/multimedia_plugin_panel_ruddy",
"item_level": 0,
"item_icon_normal": "@drawable/multimedia_plugin_beauty_ruddy",
"item_filter_bitmap": ""
}
]
},
{
"beauty_type_name": "@string/multimedia_plugin_setting_panel_filter",
"beauty_item_list": [
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_beauty_cancel",
"item_level": -1,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cancel",
"item_filter_bitmap": ""
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_standard",
"item_level": 4,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_standard",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_biaozhun"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_cheery",
"item_level": 8,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cheery",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_yinghong"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_cloud",
"item_level": 8,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cloud",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_yunshang"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_pure",
"item_level": 8,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_pure",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_chunzhen"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_orchid",
"item_level": 9,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_orchid",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_bailan"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_vitality",
"item_level": 8,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_vitality",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_yuanqi"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_super",
"item_level": 9,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_super",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_chaotuo"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_fragrance",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_fragrance",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_xiangfen"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_romantic",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_romantic",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_langman"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_fresh",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_fresh",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_qingxin"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_beautiful",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_beautiful",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_weimei"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_pink",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_pink",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_fennen"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_reminiscence",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_reminiscence",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_huaijiu"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_blues",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_blues",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_landiao"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_cool",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cool",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_qingliang"
},
{
"item_type": 20,
"item_name": "@string/multimedia_plugin_panel_filter_Japanese",
"item_level": 5,
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_japanese",
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_rixi"
}
]
}
]
}
@@ -0,0 +1,42 @@
{
"bgm_list": [
{
"bgm_type_name": "@string/multimedia_plugin_bgm_type_name_default",
"bgm_item_list": [
{
"item_bgm_path": "file:///asset/bgm/test1.mp3",
"item_bgm_name": "测试音乐1",
"item_bgm_author": "未知",
"item_start_time": 0,
"item_end_time": 0
},
{
"item_bgm_path": "file:///asset/bgm/test2.mp3",
"item_bgm_name": "测试音乐2",
"item_bgm_author": "未知",
"item_start_time": 0,
"item_end_time": 0
},
{
"item_bgm_path": "file:///asset/bgm/test3.mp3",
"item_bgm_name": "测试音乐3",
"item_bgm_author": "未知",
"item_start_time": 0,
"item_end_time": 0
}
]
},
{
"bgm_type_name": "@string/multimedia_plugin_bgm_type_name_chosen",
"bgm_item_list": [
{
"item_bgm_path": "file:///asset/bgm/test1.mp3",
"item_bgm_name": "测试音乐1",
"item_bgm_author": "未知",
"item_start_time": 0,
"item_end_time": 0
}
]
}
]
}
@@ -0,0 +1,20 @@
{
"max_record_duration_ms": "15000",
"min_record_duration_ms": "2000",
"video_quality": "2",
"picture_paster_config_file_path": "file:///asset/config/picture_paster_data.json",
"bgm_config_file_path": "file:///asset/config/bgm_data.json",
"support_record_torch": "true",
"support_record_beauty": "true",
"support_record_aspect": "true",
"support_record_scroll_filter": "true",
"support_video_edit_graffiti": "true",
"support_video_edit_paster": "true",
"support_video_edit_subtitle": "true",
"support_video_edit_bgm": "true",
"support_picture_edit_graffiti": "true",
"support_picture_edit_mosaic": "true",
"support_picture_edit_paster": "true",
"support_picture_edit_subtitle": "true",
"support_picture_edit_crop": "true"
}
@@ -0,0 +1,143 @@
{
"paster_type_list": [
{
"type_name": "",
"type_icon_path": "@drawable/emoji_48",
"paster_item_list": [
{
"item_icon_path": "@drawable/multimedia_plugin_edit_paster_file_selector",
"item_is_file_selector": true
},
{
"item_icon_path": "@drawable/emoji_31",
"item_image_path": "@drawable/emoji_31"
},
{
"item_icon_path": "@drawable/emoji_4",
"item_image_path": "@drawable/emoji_4"
},
{
"item_icon_path": "@drawable/emoji_10",
"item_image_path": "@drawable/emoji_10"
},
{
"item_icon_path": "@drawable/emoji_35",
"item_image_path": "@drawable/emoji_35"
}
]
},
{
"type_icon_path": "file:///asset/4351/ys02@2x.png",
"type_name": "@string/multimedia_plugin_default",
"paster_item_list": [
{
"item_image_path": "file:///asset/4351/ys00@2x.png",
"item_icon_path": "file:///asset/4351/ys00@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys01@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys02@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys03@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys04@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys05@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys06@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys07@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys08@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys09@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys10@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys11@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys12@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys13@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys14@2x.png"
},
{
"item_image_path": "file:///asset/4351/ys15@2x.png"
}
]
},
{
"type_icon_path": "file:///asset/4352/gcs05@2x.png",
"type_name": "@string/multimedia_plugin_default",
"paster_item_list": [
{
"item_image_path": "file:///asset/4352/gcs00@2x.png",
"item_icon_path": "file:///asset/4352/gcs00@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs01@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs02@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs03@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs04@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs05@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs06@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs07@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs08@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs09@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs10@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs11@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs12@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs13@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs14@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs15@2x.png"
},
{
"item_image_path": "file:///asset/4352/gcs16@2x.png"
}
]
}
]
}
@@ -0,0 +1,38 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin;
public class TUIMultimediaConstants {
public static final String RECORD_TYPE_KEY = "record_type";
public static final String PARAM_NAME_RECORD_TYPE = "recordType";
public static final String PARAM_NAME_EDIT_FILE_PATH = "editFilePath";
public static final String PARAM_NAME_EDITED_FILE_PATH = "editedFilePath";
public static final String PARAM_NAME_EDIT_FILE_RATIO = "recordFileRatio";
public static final String PARAM_NAME_IS_RECODE_FILE = "isRecordFile";
public static final int RECORD_TYPE_VIDEO = 0;
public static final int RECORD_TYPE_PHOTO = 1;
public enum VideoQuality {
LOW(1),
MEDIUM(2),
HIGH(3);
private final int value;
VideoQuality(int value) {
this.value = value;
}
public static VideoQuality fromInteger(int value) {
for (VideoQuality code : VideoQuality.values()) {
if (value == code.getValue()) {
return code;
}
}
return LOW;
}
public int getValue() {
return value;
}
}
}
@@ -0,0 +1,207 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants.VideoQuality;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
public class TUIMultimediaIConfig {
static final String TAG = TUIMultimediaIConfig.class.getSimpleName();
static final String DEFAULT_JSON_PATH = "file:///asset/config/default_config.json";
static final String DEFAULT_BGM_JSON_PATH = "file:///asset/config/bgm_data.json";
static final String DEFAULT_PASTER_JSON_PATH = "file:///asset/config/picture_paster_data.json";
static final int DEFAULT_MAX_RECORD_DURATION_MS = 15000;
static final int DEFAULT_MIN_RECORD_DURATION_MS = 2000;
@SerializedName("max_record_duration_ms")
private int mMaxRecordDurationMs = DEFAULT_MAX_RECORD_DURATION_MS;
@SerializedName("min_record_duration_ms")
private int mMinRecordDurationMs = DEFAULT_MIN_RECORD_DURATION_MS;
@SerializedName("video_quality")
private int mVideoQuality = VideoQuality.MEDIUM.getValue();
@SerializedName("picture_paster_config_file_path")
private String mPicturePasterConfigFilePath = DEFAULT_PASTER_JSON_PATH;
@SerializedName("bgm_config_file_path")
private String mBGMConfigFilePath = DEFAULT_BGM_JSON_PATH;
@SerializedName("support_record_torch")
private boolean mIsSupportRecordTorch = true;
@SerializedName("support_record_beauty")
private boolean mIsSupportRecordBeauty = true;
@SerializedName("support_record_aspect")
private boolean mIsSupportRecordAspect = true;
@SerializedName("support_record_scroll_filter")
private boolean mIsSupportRecordScrollFilter = true;
@SerializedName("support_video_edit_graffiti")
private boolean mIsSupportVideoEditGraffiti = true;
@SerializedName("support_video_edit_paster")
private boolean mIsSupportVideoEditPaster = true;
@SerializedName("support_video_edit_subtitle")
private boolean mIsSupportVideoEditSubtitle = true;
@SerializedName("support_video_edit_bgm")
private boolean mIsSupportVideoEditBGM = true;
@SerializedName("support_picture_edit_graffiti")
private boolean mIsSupportPictureEditGraffiti = true;
@SerializedName("support_picture_edit_mosaic")
private boolean mIsSupportPictureEditMosaic = true;
@SerializedName("support_picture_edit_paster")
private boolean mIsSupportPictureEditPaster = true;
@SerializedName("support_picture_edit_subtitle")
private boolean mIsSupportPictureEditSubtitle = true;
@SerializedName("support_picture_edit_crop")
private boolean mIsSupportPictureEditCrop = true;
private JsonObject mJsonObject;
private static TUIMultimediaIConfig sInstance;
public static TUIMultimediaIConfig getInstance() {
if (sInstance == null) {
synchronized (TUIMultimediaIConfig.class) {
if (sInstance == null) {
String json = TUIMultimediaFileUtil.readTextFromFile(DEFAULT_JSON_PATH);
LiteavLog.i(TAG, "initDefaultConfig json = " + json);
Gson gson = new Gson();
sInstance = gson.fromJson(json, TUIMultimediaIConfig.class);
}
}
}
return sInstance;
}
public void setConfig(String configJsonString) {
if (configJsonString == null || configJsonString.isEmpty()) {
return;
}
LiteavLog.i(TAG, "setConfigJsonString json = " + configJsonString);
Gson gson = new Gson();
mJsonObject = gson.fromJson(configJsonString, JsonObject.class);
}
public boolean isSupportRecordBeauty() {
return getBoolFromJsonObject("support_record_beauty", mIsSupportRecordBeauty);
}
public boolean isSupportRecordAspect() {
return getBoolFromJsonObject("support_record_aspect", mIsSupportRecordAspect);
}
public boolean isSupportRecordTorch() {
return getBoolFromJsonObject("support_record_torch", mIsSupportRecordTorch);
}
public boolean isSupportRecordScrollFilter() {
return getBoolFromJsonObject("support_record_scroll_filter", mIsSupportRecordScrollFilter);
}
public boolean isSupportVideoEditGraffiti() {
return getBoolFromJsonObject("support_video_edit_graffiti", mIsSupportVideoEditGraffiti);
}
public boolean isSupportVideoEditPaster() {
return getBoolFromJsonObject("support_video_edit_paster", mIsSupportVideoEditPaster);
}
public boolean isSupportVideoEditSubtitle() {
return getBoolFromJsonObject("support_video_edit_subtitle", mIsSupportVideoEditSubtitle);
}
public boolean isSupportVideoEditBGM() {
return getBoolFromJsonObject("support_video_edit_bgm", mIsSupportVideoEditBGM);
}
public boolean isSupportPictureEditGraffiti() {
return getBoolFromJsonObject("support_picture_edit_graffiti", mIsSupportPictureEditGraffiti);
}
public boolean isSupportPictureEditMosaic() {
return getBoolFromJsonObject("support_picture_edit_mosaic", mIsSupportPictureEditMosaic);
}
public boolean isSupportPictureEditPaster() {
return getBoolFromJsonObject("support_picture_edit_paster", mIsSupportPictureEditPaster);
}
public boolean isSupportPictureEditSubtitle() {
return getBoolFromJsonObject("support_picture_edit_subtitle", mIsSupportPictureEditSubtitle);
}
public boolean isSupportPictureEditCrop() {
return getBoolFromJsonObject("support_picture_edit_crop", mIsSupportPictureEditCrop);
}
public int getThemeColor() {
return TUIMultimediaResourceUtils.getResources()
.getColor(TUIThemeManager.getAttrResId(TUIMultimediaPlugin.getAppContext(),
com.tencent.qcloud.tuicore.R.attr.core_primary_color));
}
public int getMaxRecordDurationMs() {
return getIntFromJsonObject("max_record_duration_ms", mMaxRecordDurationMs);
}
public int getMinRecordDurationMs() {
return getIntFromJsonObject("min_record_duration_ms", sInstance.mMinRecordDurationMs);
}
public VideoQuality getVideoQuality() {
return VideoQuality.fromInteger(sInstance.getIntFromJsonObject("video_quality", sInstance.mVideoQuality));
}
public String getPicturePasterConfigFilePath() {
return getStringFromJsonObject("picture_paster_config_file_path", mPicturePasterConfigFilePath);
}
public String getBGMConfigFilePath() {
return getStringFromJsonObject("bgm_config_file_path", mBGMConfigFilePath);
}
private int getIntFromJsonObject(String key, int defaultValue) {
if (mJsonObject == null || !mJsonObject.has(key)) {
return defaultValue;
}
return mJsonObject.get(key).getAsInt();
}
private boolean getBoolFromJsonObject(String key, boolean defaultValue) {
if (mJsonObject == null || !mJsonObject.has(key)) {
return defaultValue;
}
return mJsonObject.get(key).getAsBoolean();
}
private String getStringFromJsonObject(String key, String defaultValue) {
if (mJsonObject == null || !mJsonObject.has(key)) {
return defaultValue;
}
return mJsonObject.get(key).getAsString();
}
}
@@ -0,0 +1,58 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin;
import android.content.Context;
import android.text.TextUtils;
import com.google.auto.service.AutoService;
import com.tencent.qcloud.tuicore.ServiceInitializer;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.annotations.TUIInitializerDependency;
import com.tencent.qcloud.tuicore.annotations.TUIInitializerID;
import com.tencent.qcloud.tuicore.interfaces.ITUINotification;
import com.tencent.qcloud.tuicore.interfaces.TUIInitializer;
import com.tencent.qcloud.tuikit.tuichat.component.album.AlbumPicker;
import com.tencent.qcloud.tuikit.tuichat.component.album.VideoRecorder;
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker;
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker.SignatureCheckListener;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.TUIMultimediaAlbumPicker;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecorder;
import java.util.Map;
@AutoService(TUIInitializer.class)
@TUIInitializerDependency({"TUIChat"})
@TUIInitializerID("TUIMultimediaRecordService")
public class TUIMultimediaPlugin implements TUIInitializer, ITUINotification {
public static final String TAG = TUIMultimediaPlugin.class.getSimpleName();
public static Context getAppContext() {
return ServiceInitializer.getAppContext();
}
@Override
public void init(Context context) {
TUICore.registerEvent(TUIConstants.TUILogin.EVENT_LOGIN_STATE_CHANGED, TUIConstants.TUILogin.EVENT_SUB_KEY_USER_LOGIN_SUCCESS, this);
AlbumPicker.registerAdvancedAlbumPicker(new TUIMultimediaAlbumPicker());
VideoRecorder.registerAdvancedVideoRecorder(new TUIMultimediaRecorder());
}
@Override
public void onNotifyEvent(String key, String subKey, Map<String, Object> param) {
if (TextUtils.equals(key, TUIConstants.TUILogin.EVENT_LOGIN_STATE_CHANGED)) {
if (TextUtils.equals(subKey, TUIConstants.TUILogin.EVENT_SUB_KEY_USER_LOGIN_SUCCESS)) {
TUICore.registerEvent(
TUIConstants.NetworkConnection.EVENT_CONNECTION_STATE_CHANGED, TUIConstants.NetworkConnection.EVENT_SUB_KEY_CONNECT_SUCCESS, this);
updateSignature();
}
} else if (TUIConstants.NetworkConnection.EVENT_CONNECTION_STATE_CHANGED.equals(key)) {
if (TUIConstants.NetworkConnection.EVENT_SUB_KEY_CONNECT_SUCCESS.equals(subKey)) {
updateSignature();
}
}
}
private void updateSignature() {
TUIMultimediaSignatureChecker.getInstance().startUpdateSignature(() -> {
});
}
}
@@ -0,0 +1,114 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import com.tencent.imsdk.base.ThreadUtils;
import com.tencent.liteav.base.util.LiteavLog;
public class TUIHorizontalScrollView extends HorizontalScrollView {
private final String TAG = TUIHorizontalScrollView.class + "_" + hashCode();
private DataSetObserver mObserver;
private TUIMultimediaScrollViewAdapter mAdapter;
public TUIHorizontalScrollView(Context context) {
super(context);
initialize();
}
public TUIHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public TUIHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
public void setAdapter(TUIMultimediaScrollViewAdapter adapter) {
LiteavLog.i(TAG, "set Adapter adapter = " + adapter);
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mObserver);
}
mAdapter = adapter;
mAdapter.registerDataSetObserver(mObserver);
updateAdapter();
ThreadUtils.getUiThreadHandler().post(this::scrollToSelectView);
}
private void updateAdapter() {
ViewGroup group = (ViewGroup) getChildAt(0);
group.removeAllViews();
for (int i = 0; i < mAdapter.getCount(); i++) {
View view = mAdapter.getView(i, null, group);
group.addView(view);
}
}
void initialize() {
mObserver = new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
updateAdapter();
}
@Override
public void onInvalidated() {
super.onInvalidated();
((ViewGroup) getChildAt(0)).removeAllViews();
}
};
}
public void setClicked(int position) {
LiteavLog.i(TAG, "setClicked position = " + position + " old position = " + mAdapter.getSelectPosition());
if (position == mAdapter.getSelectPosition()) {
return;
}
View itemView = ((ViewGroup) getChildAt(0)).getChildAt(position);
if (itemView != null) {
itemView.performClick();
}
scrollToSelectView();
}
private void scrollToSelectView() {
View view = ((ViewGroup) getChildAt(0)).getChildAt(mAdapter.getSelectPosition());
if (view == null) {
return;
}
Rect scrollBounds = new Rect();
getHitRect(scrollBounds);
int scrollX = getScrollX();
int itemViewLeft = view.getLeft();
int itemViewWidth = view.getWidth();
int visibleZoomLeft = scrollBounds.left + scrollX + TUIMultimediaResourceUtils.dip2px(getContext(), 20);
int visibleZoomRight = scrollBounds.right + scrollX - TUIMultimediaResourceUtils.dip2px(getContext(), 20);
int newScrollX = scrollX;
if (itemViewLeft < visibleZoomLeft) {
newScrollX = scrollX - ((visibleZoomLeft - itemViewLeft));
}
if (itemViewLeft + itemViewWidth > visibleZoomRight) {
newScrollX = scrollX + (itemViewLeft + itemViewWidth - visibleZoomRight);
}
if (newScrollX < 0) {
newScrollX = 0;
}
scrollTo(newScrollX, 0);
}
}
@@ -0,0 +1,128 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.tencent.imsdk.BuildConfig;
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker;
import com.tencent.qcloud.tuikit.tuimultimediacore.utils.TUIMultimediaLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import java.util.Objects;
public class TUIMultimediaAuthorizationPrompter {
private static final String IM_MULTIMEDIA_PLUGIN_DOCUMENT_URL = "https://cloud.tencent.com/document/product/269/113290";
public static boolean verifyPermissionGranted(Context context) {
if (TUIMultimediaSignatureChecker.getInstance().isSupportFunction()) {
return true;
}
TUIMultimediaLog.e("AuthorizationPrompter", "Signature checker do not support function");
if (context != null) {
showPrompterDialog(context);
}
return false;
}
private static void showPrompterDialog(Context context) {
if (!BuildConfig.DEBUG) {
return;
}
View dialogView = LayoutInflater.from(context).inflate(R.layout.multimedia_plugin_authorization_prompter, null);
TextView linkText = dialogView.findViewById(R.id.authorization_prompter_accessing_documents_tv);
setHyperlink(linkText,TUIMultimediaResourceUtils.getString(R.string.authorization_prompter_accessing_documents),
TUIMultimediaResourceUtils.getString(R.string.authorization_prompter_documents_title),
IM_MULTIMEDIA_PLUGIN_DOCUMENT_URL);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context, R.style.AuthorizationPrompterDialog)
.setView(dialogView)
.setPositiveButton(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_confirm), null)
.setOnDismissListener(dialog -> {
});
AlertDialog dialog = builder.create();
Window window = dialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(params);
}
Objects.requireNonNull(dialog.getWindow())
.setBackgroundDrawableResource(R.drawable.multimedia_plugin_bg_dialog_rounded);
dialog.show();
Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
if (positiveButton != null) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) positiveButton.getLayoutParams();
params.leftMargin = TUIMultimediaResourceUtils.dip2px(context, 24);
}
}
private static void setHyperlink(TextView textView, String fullText,
String linkText, String url) {
SpannableString spannable = new SpannableString(fullText);
int start = fullText.indexOf(linkText);
int end = start + linkText.length();
if (start == -1) {
return;
}
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
openUrl(widget.getContext(), url);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(true);
}
};
spannable.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#007AFF")),
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannable);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);
}
private static void openUrl(Context context, String url) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context,TUIMultimediaResourceUtils.getString(R.string.link_error), Toast.LENGTH_SHORT).show();
}
}
}
@@ -0,0 +1,31 @@
// Copyright (c) 2024 Tencent. All rights reserved.
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 通用线程池,核心线程个数为1,最大线程个数为3,非核心线程空闲时间为100ms
*/
public class TUIMultimediaCommonThreadPool extends ThreadPoolExecutor {
public static final int MAX_POOL_SIZE = 3;
public static ThreadPoolExecutor sCommonThreadPool;
private TUIMultimediaCommonThreadPool(int poolSize) {
super(1, poolSize, 100L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>(),
Executors.defaultThreadFactory(), new AbortPolicy());
}
public static synchronized ThreadPoolExecutor getThreadExecutor() {
if (sCommonThreadPool == null || sCommonThreadPool.isShutdown()) {
sCommonThreadPool = new TUIMultimediaCommonThreadPool(MAX_POOL_SIZE);
}
return sCommonThreadPool;
}
}
@@ -0,0 +1,63 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class TUIMultimediaData<T> {
private static final Object DEFAULT_OBJECT = new Object();
private final List<TUIMultimediaDataObserver<? super T>> mObservers = new ArrayList<>();
private volatile Object mData;
public TUIMultimediaData(T value) {
mData = value;
}
public void observe(TUIMultimediaDataObserver<? super T> observer) {
if (observer == null || mObservers.contains(observer)) {
return;
}
mObservers.add(observer);
}
public void removeObserver(final TUIMultimediaDataObserver<? super T> observer) {
mObservers.remove(observer);
}
public void set(T value) {
mData = value;
dispatchingValue();
}
public void add(Object item) {
if (mData != null) {
if (mData instanceof List) {
((List) mData).add(item);
dispatchingValue();
} else if (mData instanceof Set) {
((Set) mData).add(item);
dispatchingValue();
}
}
}
public T get() {
Object data = mData;
if (data != DEFAULT_OBJECT) {
return (T) data;
}
return null;
}
private void dispatchingValue() {
for (int i = 0; i < mObservers.size(); i++) {
mObservers.get(i).onChanged((T) mData);
}
}
public interface TUIMultimediaDataObserver<T> {
void onChanged(T t);
}
}
@@ -0,0 +1,323 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.util.TypedValue;
import androidx.annotation.Nullable;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuicore.util.TUIBuild;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Random;
public class TUIMultimediaFileUtil {
public static final String TAG = TUIMultimediaFileUtil.class.getSimpleName();
public static final String ASSET_FILE_PREFIX = "file:///asset/";
public static final String LOCAL_FILE_PREFIX = "/";
public static final String CONTENT_FILE_PREFIX = "content:";
public static boolean isFileExists(String path) {
try {
File file = new File(path);
return file.exists() && file.isFile();
} catch (Exception e) {
return false;
}
}
@Nullable
public static File generateFileName(@Nullable String name, File directory) {
if (name == null) {
return null;
}
File file = new File(directory, name);
if (file.exists()) {
String fileName = name;
String extension = "";
int dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) {
fileName = name.substring(0, dotIndex);
extension = name.substring(dotIndex);
}
int index = 0;
while (file.exists()) {
index++;
name = fileName + '(' + index + ')' + extension;
file = new File(directory, name);
}
}
try {
if (!file.createNewFile()) {
return null;
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
return file;
}
public static boolean saveFileFromUri(Context context, Uri uri, String destinationPath) {
InputStream is = null;
BufferedOutputStream bos = null;
try {
is = context.getContentResolver().openInputStream(uri);
bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false));
byte[] buf = new byte[1024];
int actualBytes;
while ((actualBytes = is.read(buf)) != -1) {
bos.write(buf, 0, actualBytes);
}
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
try {
if (is != null) {
is.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
public static boolean saveBitmap(String path, Bitmap b) {
try {
FileOutputStream outputStream = new FileOutputStream(path);
BufferedOutputStream bufferOutputStream = new BufferedOutputStream(outputStream);
b.compress(Bitmap.CompressFormat.JPEG, 100, bufferOutputStream);
bufferOutputStream.flush();
bufferOutputStream.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static String readTextFromFile(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return "";
}
boolean isAssetFile = fileName.startsWith(ASSET_FILE_PREFIX);
StringBuilder sb = new StringBuilder();
InputStream is = null;
BufferedReader br = null;
try {
if (isAssetFile) {
is = TUIMultimediaPlugin.getAppContext().getAssets()
.open(fileName.substring(TUIMultimediaFileUtil.ASSET_FILE_PREFIX.length()));
} else {
is = new FileInputStream(fileName);
}
br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
String readLine;
while ((readLine = br.readLine()) != null) {
sb.append(readLine);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
public static Bitmap readBitmapFromUrl(Uri uri) {
try {
ContentResolver contentResolver = TUIMultimediaPlugin.getAppContext().getContentResolver();
ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r");
FileInputStream fileInputStream = new FileInputStream(parcelFileDescriptor.getFileDescriptor());
Bitmap bitmap = BitmapFactory.decodeStream(fileInputStream);
fileInputStream.close();
parcelFileDescriptor.close();
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String generateFilePath(MultimediaPluginFileType fileType) {
String fileName = "";
String suffix = "";
switch (fileType) {
case RECODE_FILE:
fileName = "multimedia_plugin_record_";
suffix = ".mp4";
break;
case EDIT_FILE:
fileName = "multimedia_plugin_edit_";
suffix = ".mp4";
break;
case PICTURE_FILE:
fileName = "multimedia_plugin_pic_";
suffix = ".jpg";
break;
}
if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
File dir = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + TUIConfig
.getAppContext().getPackageName()
+ TUIConfig.RECORD_DIR_SUFFIX);
if (!dir.exists()) {
dir.mkdirs();
}
return dir.getAbsolutePath() + File.separatorChar + fileName + System.nanoTime() + "_" + Math
.abs(new Random().nextInt()) + suffix;
} else {
String name = System.nanoTime() + "_" + Math.abs(new Random().nextInt());
return TUIConfig.getRecordDir() + fileName + name + suffix;
}
}
public static boolean copyAssetsFile(String assetsFileName, String outputFilePath) {
try {
InputStream inputStream = TUIMultimediaPlugin.getAppContext().getAssets().open(assetsFileName);
OutputStream outputStream = new FileOutputStream(outputFilePath);
byte[] buffer = new byte[1024];
int read;
while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
}
inputStream.close();
outputStream.flush();
outputStream.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static Bitmap decodeBitmap(String path) {
LiteavLog.i(TAG, "decodeBitmap. path = " + path);
if (path != null && path.startsWith(TUIMultimediaFileUtil.LOCAL_FILE_PREFIX)) {
return decodeLocalFile(path);
} else if (path != null && path.startsWith(TUIMultimediaResourceUtils.DRAWABLE_RESOURCE_PREFIX)) {
return decodeDrawableRes(path);
} else if (path != null && path.startsWith(TUIMultimediaFileUtil.ASSET_FILE_PREFIX)) {
return decodeAssetFile(path);
} else if (path != null && path.startsWith(TUIMultimediaFileUtil.CONTENT_FILE_PREFIX)) {
return readBitmapFromUrl(Uri.parse(path));
} else {
LiteavLog.i(TAG, "decodeBitmap " + path + " is not supported path");
return null;
}
}
private static Bitmap decodeLocalFile(String localFilePath) {
return BitmapFactory.decodeFile(localFilePath);
}
private static Bitmap decodeDrawableRes(String resName) {
LiteavLog.i(TAG, "decode drawable res. resName = " + resName);
TypedValue value = new TypedValue();
int resId = TUIMultimediaResourceUtils.getDrawableId(resName);
TUIMultimediaResourceUtils.getResources().openRawResource(resId, value);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inTargetDensity = value.density;
return BitmapFactory.decodeResource(TUIMultimediaResourceUtils.getResources(), resId, opts);
}
private static Bitmap decodeAssetFile(String assetFileName) {
LiteavLog.i("PicturePasterItemInfo", "decodeAssetFile assetFileName = " + assetFileName);
try {
assetFileName = assetFileName.substring(TUIMultimediaFileUtil.ASSET_FILE_PREFIX.length());
InputStream is = TUIMultimediaPlugin.getAppContext().getAssets().open(assetFileName);
return BitmapFactory.decodeStream(is);
} catch (Exception e) {
LiteavLog.e("PicturePasterItemInfo", "decode asset file error " + e.toString());
return null;
}
}
public static boolean saveBmpToFile(Bitmap bmp, File file, CompressFormat format) {
if (null != bmp && null != file) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(format, 100, baos);
return writeToFile(baos.toByteArray(), file);
} else {
LiteavLog.e("DebugUtils", "bmp or file is null");
return false;
}
}
public static boolean writeToFile(byte[] data, File file) {
FileOutputStream fos = null;
boolean ret = false;
try {
fos = new FileOutputStream(file);
fos.write(data);
fos.flush();
ret = true;
} catch (IOException var8) {
} finally {
closeQuietly(fos);
}
return ret;
}
private static void closeQuietly(final Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException var2) {
}
}
public enum MultimediaPluginFileType {
RECODE_FILE,
EDIT_FILE,
PICTURE_FILE
}
}
@@ -0,0 +1,40 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class TUIMultimediaFullScreen extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initStatusBar();
}
private void initStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
| View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
}
@Override
protected void onResume() {
super.onResume();
initStatusBar();
}
}
@@ -0,0 +1,61 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import android.graphics.Point;
import android.graphics.PointF;
import androidx.annotation.NonNull;
public class TUIMultimediaGraphComputerUtils {
public static float distance4PointF(@NonNull PointF pf1, @NonNull PointF pf2) {
float disX = pf2.x - pf1.x;
float disY = pf2.y - pf1.y;
return (float) Math.sqrt(disX * disX + disY * disY);
}
public static double radianToDegree(double radian) {
return radian * 180 / Math.PI;
}
public static double degreeToRadian(double degree) {
return degree * Math.PI / 180;
}
@NonNull
public static Point obtainRotationPoint(@NonNull Point center, @NonNull Point source, float degree) {
Point disPoint = new Point();
disPoint.x = source.x - center.x;
disPoint.y = source.y - center.y;
double originRadian = 0;
double originDegree;
double resultDegree;
double resultRadian;
Point resultPoint = new Point();
double distance = Math.sqrt(disPoint.x * disPoint.x + disPoint.y * disPoint.y);
if (disPoint.x == 0 && disPoint.y == 0) {
return center;
} else if (disPoint.x >= 0 && disPoint.y >= 0) {
originRadian = Math.asin(disPoint.y / distance);
} else if (disPoint.x < 0 && disPoint.y >= 0) {
originRadian = Math.asin(Math.abs(disPoint.x) / distance);
originRadian = originRadian + Math.PI / 2;
} else if (disPoint.x < 0 && disPoint.y < 0) {
originRadian = Math.asin(Math.abs(disPoint.y) / distance);
originRadian = originRadian + Math.PI;
} else if (disPoint.x >= 0 && disPoint.y < 0) {
originRadian = Math.asin(disPoint.x / distance);
originRadian = originRadian + Math.PI * 3 / 2;
}
originDegree = radianToDegree(originRadian);
resultDegree = originDegree + degree;
resultRadian = degreeToRadian(resultDegree);
resultPoint.x = (int) Math.round(distance * Math.cos(resultRadian));
resultPoint.y = (int) Math.round(distance * Math.sin(resultRadian));
resultPoint.x += center.x;
resultPoint.y += center.y;
return resultPoint;
}
}
@@ -0,0 +1,114 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.WindowManager;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
import java.util.Locale;
public class TUIMultimediaResourceUtils {
public static final String DRAWABLE_RESOURCE_PREFIX = "@drawable/";
public static final String STRING_RESOURCE_PREFIX = "@string/";
public static final String TYPE_STRING = "string";
public static final String TYPE_DRAWABLE = "drawable";
public static int getDrawableId(String resName) {
if (resName != null && resName.startsWith(DRAWABLE_RESOURCE_PREFIX)) {
return getResources().getIdentifier(resName, TYPE_DRAWABLE, TUIMultimediaResourceUtils.getPackageName());
}
throw new IllegalArgumentException("\"" + resName + "\" is illegal, must start with \"@\".");
}
public static int getStringId(String resName) {
return getResources().getIdentifier(resName, TYPE_STRING, TUIMultimediaResourceUtils.getPackageName());
}
public static String getString(String resName) {
if (resName != null && resName.startsWith(STRING_RESOURCE_PREFIX)) {
return getResources().getString(getStringId(resName.substring(STRING_RESOURCE_PREFIX.length())));
}
return resName;
}
public static String getString(@StringRes int resId) {
return getResources().getString(resId);
}
public static int getColor(int colorId) {
return getResources().getColor(colorId);
}
public static Resources getResources() {
return TUIMultimediaPlugin.getAppContext().getResources();
}
public static Drawable getColorDrawable(int colorId) {
int color = ContextCompat.getColor(TUIMultimediaPlugin.getAppContext(), colorId); // 获取颜色
return new ColorDrawable(color);
}
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getDrawable(int drawableId) {
Resources res = getResources();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
return res.getDrawable(drawableId, null);
} else {
return res.getDrawable(drawableId);
}
}
public static Drawable getDrawable(@NonNull Context context, int drawableId, @ColorInt int tint) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (drawable == null) {
return null;
}
Drawable drawableUp = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(drawableUp, tint);
return drawableUp;
}
public static float spToPx(Context context, float sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
}
public static Point getScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Point point = new Point();
windowManager.getDefaultDisplay().getRealSize(point);
return point;
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public static void setImageResource(ImageView imageView, String res) {
imageView.setImageResource(TUIMultimediaResourceUtils.getDrawableId(res));
}
public static String getPackageName() {
return TUIMultimediaPlugin.getAppContext().getPackageName();
}
public static String secondsToTimeString(long timeMs) {
long seconds = timeMs / 1000;
int minutes = (int) (seconds / 60);
seconds = (int) (seconds % 60);
return String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
}
}
@@ -0,0 +1,16 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
import android.widget.BaseAdapter;
public abstract class TUIMultimediaScrollViewAdapter extends BaseAdapter {
protected int mSelectPosition = -1;
public int getSelectPosition() {
return mSelectPosition;
}
public void setSelectPosition(int position) {
mSelectPosition = position;
}
}
@@ -0,0 +1,755 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMEditListener;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMManager;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMPanelView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop.PictureCropControlView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop.PictureCropControlView.PictureCropControlViewListener;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerViewGroup;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView.BrushMode;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.PicturePasterFloatLayerView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.PicturePasterManager;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.PicturePasterPanelView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster.SubtitlePanelView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster.SubtitlePasterFloatLayerView;
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
import com.tencent.ugc.TXVideoEditConstants.TXRect;
import java.util.LinkedList;
import java.util.List;
public class TUIMultiMediaEditCommonCtrlView extends RelativeLayout {
private final static int COMMON_FUNCTION_ICON_SIZE_DP = 28;
private final static int BGM_FUNCTION_ICON_MARGIN_DP = 34;
private final String TAG = TUIMultiMediaEditCommonCtrlView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final EditType mEditType;
private final Boolean mIsRecordFile;
private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener;
private RelativeLayout mOperationLayout;
private LinearLayout mFunctionButtonLayout;
private ImageView mReturnBackView;
private View mRootView;
private FloatLayerViewGroup mFloatLayerViewGroup;
private float mAspectRatio = 9.0f / 16.0f;
private View mMediaView;
private TUIMultimediaTransformLayout mPreviewContainer;
private PictureCropControlView mPictureCropControlView;
private BrushControlView mBrushControlView;
private ImageButton mGraffitiBrushBtn;
private ImageButton mMosaicBrushBtn;
private PicturePasterPanelView mPicturePasterPanelView;
private PicturePasterManager mPicturePasterManager;
private SubtitlePanelView mSubtitlePanelView;
private BGMPanelView mBGMPanelView;
private BGMManager mBGMManager;
private ImageButton mBGMBtn;
private Rect mPreviewRect;
private PictureCropListener mPictureCropListener;
private BGMEditListener mVideoFragmentBgmEditListener;
private CommonMediaEditListener mCommonMediaEditListener;
private Bitmap mPicture;
public enum EditType {
VIDEO,
PHOTO
}
public interface PictureCropListener {
void onConfirmCrop(RectF rectF, int rotation);
void onRotation(int rotation);
void onStartCrop();
void onCancelCrop();
}
public interface CommonMediaEditListener {
void onGenerateMedia();
void onCancelEdit();
}
private final OnClickListener mOnPreviewClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (mPictureCropControlView != null) {
return;
}
if (mPicturePasterPanelView != null) {
mPicturePasterPanelView.setVisibility(View.GONE);
}
if (mBGMPanelView != null) {
mBGMPanelView.setVisibility(View.GONE);
}
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.unSelectOperationView();
}
showOperationView(true);
}
};
private final BGMEditListener mBgmEditListener = new BGMEditListener() {
@Override
public void onAddBGM(String bgmPath) {
if (mVideoFragmentBgmEditListener != null) {
mVideoFragmentBgmEditListener.onAddBGM(bgmPath);
}
}
@Override
public void onCutBGM(long startTime, long endTime) {
if (mVideoFragmentBgmEditListener != null) {
mVideoFragmentBgmEditListener.onCutBGM(startTime, endTime);
}
}
@Override
public void onMuteSourceAudio(boolean isMute) {
if (mVideoFragmentBgmEditListener != null) {
mVideoFragmentBgmEditListener.onMuteSourceAudio(isMute);
}
}
@Override
public void onMuteBGM(boolean isMute) {
if (mVideoFragmentBgmEditListener != null) {
mVideoFragmentBgmEditListener.onMuteBGM(isMute);
}
if (mBGMBtn != null) {
mBGMBtn.setSelected(!isMute);
}
}
};
public TUIMultiMediaEditCommonCtrlView(Context context, EditType editType, boolean isRecordFile) {
super(context);
mContext = context;
mEditType = editType;
mIsRecordFile = isRecordFile;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
if (mPicturePasterManager != null) {
mPicturePasterManager.uninit();
}
mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
removeAllViews();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (!changed) {
return;
}
adjustFunctionButtonLayout();
adjustPreviewLayout();
}
public void initView() {
mRootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_common_view, this);
mOperationLayout = mRootView.findViewById(R.id.operation_layout);
mReturnBackView = mRootView.findViewById(R.id.edit_return_back);
mPreviewContainer = mRootView.findViewById(R.id.rl_preview);
mFunctionButtonLayout = mRootView.findViewById(R.id.function_button_layout);
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
mPreviewContainer.setOnClickListener(mOnPreviewClickListener);
mReturnBackView.setOnClickListener(view -> {
if (mCommonMediaEditListener != null) {
mCommonMediaEditListener.onCancelEdit();
}
});
initSendBtn();
initGraffitiBrushBtn();
initMosaicBrushBtn();
initPasterFunction();
initSubtitleBtn();
initBGMFunction();
initCropFunction();
adjustPreviewLayout();
setMediaView(mMediaView);
mOnGlobalLayoutListener = this::adjustFunctionButtonLayout;
mRootView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
public void setMediaView(View view) {
if (view == null) {
return;
}
adjustPreviewLayout();
if (mPreviewContainer != null) {
RelativeLayout.LayoutParams layoutParams = new LayoutParams(mPreviewRect.width(), mPreviewRect.height());
layoutParams.leftMargin = mPreviewRect.left;
layoutParams.topMargin = mPreviewRect.top;
mPreviewContainer.addView(view, 0, layoutParams);
}
mMediaView = view;
}
public void setMediaAspectRatio(float aspectRatio) {
LiteavLog.i(TAG, "set media aspect ratio. aspect ratio:" + aspectRatio);
mAspectRatio = aspectRatio;
adjustPreviewLayout();
}
public void setPicture(Bitmap bitmap) {
mPicture = bitmap;
if (mBrushControlView != null) {
mBrushControlView.setMosaicImage(bitmap);
}
}
public void setBGMListener(BGMEditListener listener) {
mVideoFragmentBgmEditListener = listener;
}
private void initSendBtn() {
Button button = mRootView.findViewById(R.id.send_btn);
button.setBackground(TUIMultimediaResourceUtils
.getDrawable(mContext, R.drawable.multimedia_plugin_edit_send_button,
TUIMultimediaIConfig.getInstance().getThemeColor()));
button.setGravity(Gravity.CENTER);
if (mIsRecordFile) {
button.setText(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_send));
} else {
button.setText(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_done));
}
button.setOnClickListener(view -> {
showOperationView(false);
if (mCommonMediaEditListener != null) {
mCommonMediaEditListener.onGenerateMedia();
}
});
}
private void initGraffitiBrushBtn() {
if (!isSupportEditGraffiti()) {
return;
}
int[][] states = new int[][]{
new int[]{android.R.attr.state_enabled, -android.R.attr.state_selected},
new int[]{android.R.attr.state_enabled, android.R.attr.state_selected},
};
int[] colors = new int[]{
Color.WHITE, TUIMultimediaIConfig.getInstance().getThemeColor()
};
mGraffitiBrushBtn = addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
R.drawable.multimedia_plugin_edit_graffiti, new ColorStateList(states, colors),
v -> switchEnableBrush(BrushMode.GRAFFITI));
}
private void initMosaicBrushBtn() {
if (!isSupportEditMosaic()) {
return;
}
int[][] states = new int[][]{
new int[]{android.R.attr.state_enabled, -android.R.attr.state_selected},
new int[]{android.R.attr.state_enabled, android.R.attr.state_selected},
};
int[] colors = new int[]{
Color.WHITE, TUIMultimediaIConfig.getInstance().getThemeColor()
};
mMosaicBrushBtn = addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
R.drawable.multimedia_plugin_edit_mosaic, new ColorStateList(states, colors),
v -> switchEnableBrush(BrushMode.MOSAIC));
}
private void initPasterFunction() {
if (!isSupportEditPaster()) {
return;
}
addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
R.drawable.multimedia_plugin_edit_paster,
null, v -> addPicturePaster());
initPicturePasterSetManager();
}
private void initSubtitleBtn() {
if (!isSupportEditSubtitle()) {
return;
}
addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
R.drawable.multimedia_plugin_edit_subtitle, null, v -> addSubtitlePaster());
}
private void initBGMFunction() {
if (!isSupportEditBGM()) {
return;
}
int[][] states = new int[][]{
new int[]{android.R.attr.state_enabled, -android.R.attr.state_selected},
new int[]{android.R.attr.state_enabled, android.R.attr.state_selected},
};
int[] colors = new int[]{
Color.WHITE, TUIMultimediaIConfig.getInstance().getThemeColor()
};
mBGMBtn = addFunctionButton(BGM_FUNCTION_ICON_MARGIN_DP, BGM_FUNCTION_ICON_MARGIN_DP,
R.drawable.multimedia_plugin_edit_bgm, new ColorStateList(states, colors),
v -> addBGMPanel());
initBGMManager();
}
private void initCropFunction() {
if (!isSupportEditCrop()) {
return;
}
addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
R.drawable.multimedia_plugin_edit_crop, null, v -> startCrop());
}
private ImageButton addFunctionButton(int widthDp, int heightDp, int drawableId, ColorStateList tint,
OnClickListener onClickListener) {
ImageButton imageButton = new ImageButton(mContext);
Drawable drawable = ContextCompat.getDrawable(mContext, drawableId);
if (tint != null) {
DrawableCompat.setTintList(drawable, tint);
} else {
DrawableCompat.setTint(drawable, Color.WHITE);
}
imageButton.setBackground(drawable);
imageButton.setScaleType(ScaleType.FIT_CENTER);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
TUIMultimediaResourceUtils.dip2px(mContext, widthDp),
TUIMultimediaResourceUtils.dip2px(mContext, heightDp));
mFunctionButtonLayout.addView(imageButton, layoutParams);
imageButton.setOnClickListener(onClickListener);
return imageButton;
}
private void addSubtitlePaster() {
initFloatLayerViewGroup();
if (mSubtitlePanelView == null) {
mSubtitlePanelView = new SubtitlePanelView(mContext);
RelativeLayout subtitlePasterInputViewContainer = mRootView
.findViewById(R.id.rl_subtitle_paster_input_view_container);
subtitlePasterInputViewContainer.removeAllViews();
subtitlePasterInputViewContainer.addView(mSubtitlePanelView,
new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mSubtitlePanelView.inputText(null, subtitleInfo -> {
if (subtitleInfo != null && !subtitleInfo.getText().isEmpty()) {
new SubtitlePasterFloatLayerView(getContext(),
mFloatLayerViewGroup, subtitleInfo, mSubtitlePanelView);
}
showOperationView(true);
mFloatLayerViewGroup.setCanTouch(true);
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
});
disableBrush();
showOperationView(false);
mPreviewContainer.enableTransform(false);
mFloatLayerViewGroup.setCanTouch(false);
mFloatLayerViewGroup.unSelectOperationView();
}
private void addPicturePaster() {
initFloatLayerViewGroup();
if (mPicturePasterPanelView == null) {
RelativeLayout picturePasterPanelViewContainer = mRootView
.findViewById(R.id.rl_picture_paster_panel_view_container);
picturePasterPanelViewContainer.removeAllViews();
picturePasterPanelViewContainer.setVisibility(View.VISIBLE);
mPicturePasterPanelView = new PicturePasterPanelView(mContext, mPicturePasterManager);
picturePasterPanelViewContainer
.addView(mPicturePasterPanelView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mPicturePasterPanelView.setVisibility(View.VISIBLE);
mPicturePasterPanelView.selectPaster(bitmap -> {
if (bitmap != null) {
new PicturePasterFloatLayerView(getContext(), mFloatLayerViewGroup, bitmap);
}
mPicturePasterPanelView.setVisibility(View.GONE);
showOperationView(true);
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
});
disableBrush();
showOperationView(false);
mFloatLayerViewGroup.unSelectOperationView();
mPreviewContainer.enableTransform(false);
}
private void addBGMPanel() {
if (mBGMPanelView == null) {
mBGMPanelView = new BGMPanelView(mContext, mBGMManager);
RelativeLayout bgmPanelViewContainer = mRootView.findViewById(R.id.rl_bgm_panel_view_container);
bgmPanelViewContainer.setVisibility(View.VISIBLE);
bgmPanelViewContainer.addView(mBGMPanelView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mBGMPanelView.setBGMEditListener(mBgmEditListener);
}
mBGMPanelView.showBGMPanel();
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.unSelectOperationView();
}
disableBrush();
showOperationView(false);
}
private void initPicturePasterSetManager() {
if (mPicturePasterManager != null) {
return;
}
mPicturePasterManager = new PicturePasterManager(
TUIMultimediaIConfig.getInstance().getPicturePasterConfigFilePath());
mPicturePasterManager.init();
}
private void initFloatLayerViewGroup() {
if (mFloatLayerViewGroup == null) {
mFloatLayerViewGroup = new FloatLayerViewGroup(mContext);
mPreviewContainer.addView(mFloatLayerViewGroup, 2);
}
}
private void initBGMManager() {
if (mBGMManager != null) {
return;
}
mBGMManager = new BGMManager(TUIMultimediaIConfig.getInstance().getBGMConfigFilePath());
mBGMManager.init();
}
private void switchEnableBrush(BrushMode brushMode) {
boolean enableBrush =
mBrushControlView == null || !mBrushControlView.isEnableDraw();
if (!enableBrush && mBrushControlView != null) {
enableBrush = (brushMode != mBrushControlView.getBrushMode());
}
if (enableBrush) {
enableBrush(brushMode);
} else {
disableBrush();
}
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.unSelectOperationView();
}
}
private void enableBrush(BrushMode brushMode) {
if (mBrushControlView == null) {
mBrushControlView = new BrushControlView(mContext,
mRootView.findViewById(R.id.rl_brush_tool_view_container));
mPreviewContainer.addView(mBrushControlView.getBrushDrawView(), 1);
mBrushControlView.tuiDataIsDrawing.observe(isDrawing -> showOperationView(!isDrawing));
mBrushControlView.setMosaicImage(mPicture);
}
mBrushControlView.showToolView(true);
mBrushControlView.enableDraw(true);
mBrushControlView.setBrushMode(brushMode);
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.setCanTouch(false);
}
mPreviewContainer.enableTransform(false);
if (mGraffitiBrushBtn != null) {
mGraffitiBrushBtn.setSelected(mBrushControlView.getBrushMode() == BrushMode.GRAFFITI);
}
if (mMosaicBrushBtn != null) {
mMosaicBrushBtn.setSelected(mBrushControlView.getBrushMode() == BrushMode.MOSAIC);
}
mOperationLayout.setBackgroundColor(
TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_edit_function_button_view_background));
}
private void disableBrush() {
if (mBrushControlView != null) {
mBrushControlView.showToolView(false);
mBrushControlView.enableDraw(false);
}
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.setCanTouch(true);
}
if (mGraffitiBrushBtn != null) {
mGraffitiBrushBtn.setSelected(false);
}
if (mMosaicBrushBtn != null) {
mMosaicBrushBtn.setSelected(false);
}
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
mOperationLayout.setBackgroundColor(
TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_edit_function_view_background));
}
public void setPictureCropListener(PictureCropListener pictureCropListener) {
mPictureCropListener = pictureCropListener;
}
public void setCommonMediaEditListener(CommonMediaEditListener commonMediaEditListener) {
mCommonMediaEditListener = commonMediaEditListener;
}
private void startCrop() {
showOperationView(false);
mPreviewContainer.reset();
mPictureCropControlView = new PictureCropControlView(mContext, mPreviewContainer);
((RelativeLayout) mRootView).addView(mPictureCropControlView);
mPictureCropControlView.layout(0, 0, mRootView.getWidth(), mRootView.getHeight());
mPictureCropControlView.setCropMainViewListener(new PictureCropControlViewListener() {
@Override
public void onCancelCrop() {
showOperationView(true);
((RelativeLayout) mRootView).removeView(mPictureCropControlView);
mPictureCropControlView = null;
if (mBrushControlView != null) {
mBrushControlView.showDrawView(true);
}
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.setVisibility(VISIBLE);
}
if (mPictureCropListener != null) {
mPictureCropListener.onCancelCrop();
}
mPreviewContainer.reset();
}
@Override
public void onConfirmCrop(RectF rectF, int rotation) {
if (mPictureCropListener != null) {
mPictureCropListener.onConfirmCrop(rectF, rotation);
}
showOperationView(true);
((RelativeLayout) mRootView).removeView(mPictureCropControlView);
mPictureCropControlView = null;
mPreviewContainer.reset();
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.removeAllFloatLayerView();
mFloatLayerViewGroup.setVisibility(VISIBLE);
}
if (mBrushControlView != null) {
mBrushControlView.clean();
mBrushControlView.showDrawView(true);
}
}
@Override
public void onRotation(int rotation) {
if (mPictureCropListener != null) {
mPictureCropListener.onRotation(rotation);
}
}
});
if (mPictureCropListener != null) {
mPictureCropListener.onStartCrop();
}
if (mBrushControlView != null) {
mBrushControlView.showDrawView(false);
disableBrush();
}
if (mFloatLayerViewGroup != null) {
mFloatLayerViewGroup.setVisibility(INVISIBLE);
}
}
public List<TUIMultimediaPasterInfo> getPaster() {
List<TUIMultimediaPasterInfo> pasterInfoList = mFloatLayerViewGroup == null ? new LinkedList<>() : mFloatLayerViewGroup.getAllPaster();
TUIMultimediaPasterInfo paster = null;
if (mBrushControlView != null) {
paster = mBrushControlView.getPaster();
}
if (paster != null) {
pasterInfoList.add(0, paster);
}
return pasterInfoList;
}
public List<TXPaster> getNormalizedPaster() {
List<TUIMultimediaPasterInfo> pasterInfoList = getPaster();
Rect previewLayout = mPreviewContainer.getContentRect();
int mPreviewWidth = previewLayout != null ? previewLayout.width() : getWidth();
int mPreviewHeight = previewLayout != null ? previewLayout.height() : getHeight();
List<TXPaster> pasterList = new LinkedList<>();
for (TUIMultimediaPasterInfo pasterInfo : pasterInfoList) {
TXPaster txPaster = new TXPaster();
txPaster.frame = new TXRect();
txPaster.frame.x = pasterInfo.frame.x / mPreviewWidth;
txPaster.frame.y = pasterInfo.frame.y / mPreviewHeight;
txPaster.frame.width = pasterInfo.frame.width / mPreviewWidth;
txPaster.pasterImage = pasterInfo.image;
pasterList.add(txPaster);
}
return pasterList;
}
public void showOperationView(boolean show) {
int visible = show ? View.VISIBLE : View.GONE;
mReturnBackView.setVisibility(visible);
mOperationLayout.setVisibility(visible);
if (mBrushControlView != null) {
mBrushControlView.showToolView(show && mBrushControlView.isEnableDraw());
}
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
}
private void adjustPreviewLayout() {
if (mPreviewContainer == null) {
return;
}
int previewContainerWidth = mPreviewContainer.getWidth();
int previewContainerHeight = mPreviewContainer.getHeight();
if (previewContainerWidth == 0 || previewContainerHeight == 0) {
Point screenSize = TUIMultimediaResourceUtils.getScreenSize(mContext);
previewContainerWidth = screenSize.x;
previewContainerHeight = screenSize.y;
}
int previewHeight = (int) (previewContainerWidth / mAspectRatio);
int previewTop = (previewContainerHeight - previewHeight) / 2;
Rect previewRect = new Rect(0, previewTop, previewContainerWidth, previewTop + previewHeight);
if (previewRect.equals(mPreviewRect)) {
return;
}
mPreviewRect = previewRect;
mPreviewContainer.initContentLayout(mPreviewRect);
}
private void adjustFunctionButtonLayout() {
int width = mFunctionButtonLayout.getWidth();
int count = mFunctionButtonLayout.getChildCount();
int viewWidth = count * TUIMultimediaResourceUtils.dip2px(mContext, COMMON_FUNCTION_ICON_SIZE_DP);
int margin = (width - viewWidth) / (count + 1);
for (int i = 0; i < count; i++) {
View child = mFunctionButtonLayout.getChildAt(i);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) child.getLayoutParams();
layoutParams.setMarginStart(margin);
layoutParams.setMarginEnd(0);
child.setLayoutParams(layoutParams);
}
}
public boolean isSupportEditGraffiti() {
return mEditType == EditType.VIDEO ? TUIMultimediaIConfig.getInstance().isSupportVideoEditGraffiti()
: TUIMultimediaIConfig.getInstance().isSupportPictureEditGraffiti();
}
public boolean isSupportEditMosaic() {
return mEditType == EditType.PHOTO && TUIMultimediaIConfig.getInstance().isSupportPictureEditMosaic();
}
public boolean isSupportEditPaster() {
return mEditType == EditType.VIDEO ? TUIMultimediaIConfig.getInstance().isSupportVideoEditPaster()
: TUIMultimediaIConfig.getInstance().isSupportPictureEditPaster();
}
public boolean isSupportEditSubtitle() {
return mEditType == EditType.VIDEO ? TUIMultimediaIConfig.getInstance().isSupportVideoEditSubtitle()
: TUIMultimediaIConfig.getInstance().isSupportPictureEditSubtitle();
}
public boolean isSupportEditBGM() {
return mEditType == EditType.VIDEO && TUIMultimediaIConfig.getInstance().isSupportVideoEditBGM();
}
public boolean isSupportEditCrop() {
return mEditType == EditType.PHOTO && TUIMultimediaIConfig.getInstance().isSupportPictureEditCrop();
}
}
@@ -0,0 +1,49 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFullScreen;
import com.tencent.ugc.TXVideoEditConstants.TXVideoInfo;
public class TUIMultimediaEditActivity extends TUIMultimediaFullScreen {
private final String TAG = TUIMultimediaEditActivity.class.getSimpleName() + "_" + hashCode();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
LiteavLog.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.multimedia_plugin_edit_activity);
startUpMediaEdit();
}
private void startUpMediaEdit() {
Fragment fragment = null;
Bundle bundle = getIntent().getExtras();
if (bundle == null) {
return;
}
TXVideoInfo videoInfo = TUIMultimediaVideoEditorCore
.getVideoFileInfo(this, bundle.getString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH));
if (TUIMultimediaVideoEditorCore.isValidVideo(videoInfo)) {
fragment = new TUIMultimediaVideoEditFragment(this);
bundle.putFloat(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_RATIO, TUIMultimediaVideoEditorCore.getVideoAspect(videoInfo));
}
if (fragment == null) {
fragment = new TUIMultimediaPictureEditFragment(this);
}
fragment.setArguments(bundle);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fl_edit_fragment_container, fragment)
.commit();
}
}
@@ -0,0 +1,215 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.HandlerThread;
import androidx.activity.result.ActivityResultCaller;
import com.tencent.liteav.base.util.CustomHandler;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuikit.timcommon.util.FileUtil;
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
import com.tencent.ugc.TXVideoEditConstants.TXGenerateResult;
import com.tencent.ugc.TXVideoEditConstants.TXVideoInfo;
import com.tencent.ugc.TXVideoEditer.TXVideoGenerateListener;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
public class TUIMultimediaMediaProcessor {
private final String TAG = TUIMultimediaMediaProcessor.class.getSimpleName() + "_" + hashCode();
public interface TUIMultimediaMediaEditListener {
void onEditCompleted(Uri uri);
}
public interface TUIMultimediaMediaTranscodeListener {
void onTranscodeFinished(TranscodeResult transcodeResult);
void onTranscodeProgress(Uri originUri, int transcodeProgress);
}
public static class TranscodeResult {
public int errorCode;
public String errorString;
public Uri originalUri;
public Uri transcodeMediaUri;
}
private static TUIMultimediaMediaProcessor sInstance;
public static TUIMultimediaMediaProcessor getInstance() {
if (sInstance == null) {
synchronized (TUIMultimediaMediaProcessor.class) {
sInstance = new TUIMultimediaMediaProcessor();
}
}
return sInstance;
}
public void editMedia(Context context, Uri uri, TUIMultimediaMediaEditListener listener) {
LiteavLog.i(TAG, "edit media uri = " + uri.toString());
Bundle bundle = new Bundle();
bundle.putString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH, uri.toString());
TUICore.startActivityForResult((ActivityResultCaller) context, TUIMultimediaEditActivity.class,
bundle, result -> {
Uri editedMediaUri = null;
if (result.getData() != null) {
Bundle resultBundle = result.getData().getExtras();
if (resultBundle != null) {
String editedFilePath = resultBundle
.getString(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH);
editedMediaUri = editedFilePath != null ? FileUtil.getUriFromPath(editedFilePath) : null;
}
}
if (listener != null) {
LiteavLog.i(TAG, "onEditCompleted editedMediaUri = " + editedMediaUri);
listener.onEditCompleted(editedMediaUri);
}
});
}
public void transcodeMedia(List<Uri> uris, TUIMultimediaMediaTranscodeListener listener) {
LiteavLog.i(TAG, "transcodeMedia uris size is " + uris.size());
MultiMediaFileTranscoder multiVideoTranscoder = new MultiMediaFileTranscoder(uris, listener);
multiVideoTranscoder.startTranscode();
}
private TUIMultimediaMediaProcessor() {
}
private static class MultiMediaFileTranscoder {
private static final int CONCURRENT_TRANSCODE_NUMBER = 2;
private final String TAG = MultiMediaFileTranscoder.class.getSimpleName() + "_" + hashCode();
private final Queue<Uri> mUriQueue;
private final TUIMultimediaMediaTranscodeListener mListener;
private final CustomHandler mCustomHandler;
private final Map<Uri, TXVideoInfo> mVideoInfoMap = new HashMap<>();
private boolean transcoding = false;
public MultiMediaFileTranscoder(List<Uri> uris, TUIMultimediaMediaTranscodeListener listener) {
mUriQueue = new LinkedList<>(uris);
mListener = listener;
HandlerThread handlerThread = new HandlerThread("multi_video_transcode");
handlerThread.start();
mCustomHandler = new CustomHandler(handlerThread.getLooper());
}
public void startTranscode() {
mCustomHandler.runOrPost(() -> {
if (transcoding) {
return;
}
transcoding = true;
getVideoInfo();
for (int i = 0; i < CONCURRENT_TRANSCODE_NUMBER; i++) {
transcodeMediaInQueue();
}
});
}
private void transcodeMediaInQueue() {
if (mUriQueue.isEmpty()) {
return;
}
Uri uri;
do {
uri = mUriQueue.poll();
if (uri != null) {
transcodeMedia(uri);
}
} while (!mUriQueue.isEmpty() && uri == null);
}
private void getVideoInfo() {
for (Uri uri : mUriQueue) {
TXVideoInfo videoInfo = TUIMultimediaVideoEditorCore
.getVideoFileInfo(TUIMultimediaPlugin.getAppContext(), uri.toString());
mVideoInfoMap.put(uri, videoInfo);
}
}
private void transcodeMedia(Uri uri) {
LiteavLog.i(TAG, "transcode media uri is " + uri.toString());
if (!isNeedTranscode(mVideoInfoMap.get(uri))) {
LiteavLog.i(TAG, "do not need transcode. uri = " + uri);
mCustomHandler.runOrPost(this::transcodeMediaInQueue);
ThreadUtils.postOnUiThread(() -> {
mListener.onTranscodeProgress(uri, 100);
notifyTranscodeFinish(0, "", uri, uri);
});
return;
}
TUIMultimediaVideoEditorCore tuiMultimediaVideoEditorCore = new TUIMultimediaVideoEditorCore(TUIMultimediaPlugin.getAppContext() );
tuiMultimediaVideoEditorCore.setSource(uri.toString());
String outVideoPath = TUIMultimediaFileUtil
.generateFilePath(MultimediaPluginFileType.EDIT_FILE);
tuiMultimediaVideoEditorCore.generateVideo(outVideoPath, TUIMultimediaIConfig.getInstance().getVideoQuality(),
new TXVideoGenerateListener() {
@Override
public void onGenerateProgress(float progress) {
mListener.onTranscodeProgress(uri, (int) (progress * 100));
}
@Override
public void onGenerateComplete(TXGenerateResult txGenerateResult) {
mCustomHandler.runOrPost(() -> transcodeMediaInQueue());
notifyTranscodeFinish(txGenerateResult.retCode, txGenerateResult.descMsg, uri,
FileUtil.getUriFromPath(outVideoPath));
tuiMultimediaVideoEditorCore.release();
}
});
}
private void notifyTranscodeFinish(int errorCode, String errorString, Uri originalUri, Uri transcodeMediaUri) {
TranscodeResult transcodeResult = new TranscodeResult();
transcodeResult.errorCode = errorCode;
transcodeResult.errorString = errorString;
transcodeResult.transcodeMediaUri = transcodeMediaUri;
transcodeResult.originalUri = originalUri;
if (mListener != null) {
mListener.onTranscodeFinished(transcodeResult);
}
LiteavLog.i(TAG, "onTranscodeFinished source media uri is " + originalUri
+ " new media uri is " + transcodeMediaUri);
}
private boolean isNeedTranscode(TXVideoInfo videoInfo) {
if (videoInfo == null) {
LiteavLog.i(TAG, "isNeedTranscode false. because video info is null");
return false;
}
if (!TUIMultimediaVideoEditorCore.isValidVideo(videoInfo)) {
LiteavLog.i(TAG, "isNeedTranscode false. because is not valid video");
return false;
}
boolean isNeedTranscode = videoInfo.bitrate == 0 ||
videoInfo.bitrate > TUIMultimediaVideoEditorCore
.getBitrateAccordQuality(TUIMultimediaIConfig.getInstance().getVideoQuality()) * 1.2;
if (!isNeedTranscode) {
LiteavLog.i(TAG,
"isNeedTranscode false. because is source bitrate is low. bitrate is " + videoInfo.bitrate);
}
return isNeedTranscode;
}
}
}
@@ -0,0 +1,16 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import android.graphics.Bitmap;
import com.tencent.ugc.TXVideoEditConstants.TXRect;
public class TUIMultimediaPasterInfo {
public Bitmap image;
public TXRect frame;
public PasterType pasterType;
public enum PasterType {
SUBTITLE_PASTER,
STATIC_PICTURE_PASTER
}
}
@@ -0,0 +1,247 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.bumptech.glide.request.RequestOptions;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaAuthorizationPrompter;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.CommonMediaEditListener;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.EditType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.PictureCropListener;
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
import com.tencent.ugc.videobase.utils.CollectionUtils;
import java.io.File;
import java.security.MessageDigest;
import java.util.List;
public class TUIMultimediaPictureEditFragment extends Fragment {
private final String TAG = TUIMultimediaPictureEditFragment.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private TUIMultimediaPictureEditorCore mTuiMultimediaPictureEditorCore;
private View mRootView;
private String mPictureFilePath;
private Bitmap mCurrentBitmap;
private Bitmap mBeforeCropBitmap;
private Bitmap mOriginBitmap;
private ImageView mImageView;
private boolean mIsRecordFile = true;
public TUIMultimediaPictureEditFragment(Context context) {
mContext = context;
}
public static class RotateTransformation extends BitmapTransformation {
private final float rotateRotationAngle;
public RotateTransformation(float rotateRotationAngle) {
this.rotateRotationAngle = rotateRotationAngle;
}
@Override
protected Bitmap transform(@NonNull BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Matrix matrix = new Matrix();
matrix.postRotate(rotateRotationAngle, toTransform.getWidth() / 2.0f, toTransform.getHeight() / 2.0f);
return Bitmap
.createBitmap(toTransform, 0, 0, toTransform.getWidth(), toTransform.getHeight(), matrix, true);
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(("rotate" + rotateRotationAngle).getBytes(CHARSET));
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
LiteavLog.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
mTuiMultimediaPictureEditorCore = new TUIMultimediaPictureEditorCore(mContext);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LiteavLog.i(TAG, "onCreateView");
mRootView = inflater.inflate(R.layout.multimedia_plugin_edit_picture_fragment, container, false);
initView();
return mRootView;
}
@Override
public void onDestroyView() {
LiteavLog.i(TAG, "onDestroyView");
super.onDestroyView();
}
@Override
public void onDestroy() {
LiteavLog.i(TAG, "onDestroy");
super.onDestroy();
mTuiMultimediaPictureEditorCore.release();
}
public void initView() {
initExternalParameters();
TUIMultiMediaEditCommonCtrlView editCommonCtrlView = new TUIMultiMediaEditCommonCtrlView(mContext,
EditType.PHOTO, mIsRecordFile);
((RelativeLayout) mRootView.findViewById(R.id.edit_common_ctrl_view_container))
.addView(editCommonCtrlView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
LiteavLog.i(TAG, "preview picture. file path = " + mPictureFilePath);
mOriginBitmap = TUIMultimediaFileUtil.decodeBitmap(mPictureFilePath);
mCurrentBitmap = mOriginBitmap;
if (mCurrentBitmap == null) {
LiteavLog.e(TAG, "decode bitmap fail");
return;
}
mImageView = new ImageView(mContext);
editCommonCtrlView.setMediaView(mImageView);
editCommonCtrlView.setMediaAspectRatio(mCurrentBitmap.getWidth() * 1.0f / mCurrentBitmap.getHeight());
editCommonCtrlView.setPicture(mCurrentBitmap);
previewPicture(0);
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
editCommonCtrlView.setCommonMediaEditListener(new CommonMediaEditListener() {
@Override
public void onGenerateMedia() {
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
return;
}
List<TXPaster> pasterList = editCommonCtrlView.getNormalizedPaster();
LiteavLog.i(TAG, "on generate media." + " is recordFile: " + mIsRecordFile + " pasterList size is:" + (
pasterList != null ? pasterList.size() : 0));
if (CollectionUtils.isEmpty(pasterList)) {
Bitmap bitmap = mTuiMultimediaPictureEditorCore.getSourcePicture();
if (bitmap == mOriginBitmap && !mIsRecordFile) {
finishEdit(null);
} else {
finishEdit(savaBitmapToLocalFile(bitmap));
}
} else {
mTuiMultimediaPictureEditorCore.setPasterList(pasterList);
mTuiMultimediaPictureEditorCore.processPicture(bitmap -> finishEdit(savaBitmapToLocalFile(bitmap)));
}
}
@Override
public void onCancelEdit() {
cancelEdit();
}
});
editCommonCtrlView.setPictureCropListener(new PictureCropListener() {
@Override
public void onConfirmCrop(RectF rectF, int rotation) {
mTuiMultimediaPictureEditorCore.setCropRectF(rectF);
mTuiMultimediaPictureEditorCore.setOutputRotation(rotation);
mTuiMultimediaPictureEditorCore.processPicture(bitmap -> {
mCurrentBitmap = bitmap;
previewPicture(0);
mTuiMultimediaPictureEditorCore.resetEditor();
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
editCommonCtrlView.setMediaAspectRatio(mCurrentBitmap.getWidth() * 1.0f / mCurrentBitmap.getHeight());
editCommonCtrlView.setPicture(mCurrentBitmap);
});
}
@Override
public void onRotation(int rotation) {
LiteavLog.i(TAG, "onRotation rotation:" + rotation);
previewPicture(rotation);
}
@Override
public void onStartCrop() {
TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext);
mTuiMultimediaPictureEditorCore
.setPasterList(editCommonCtrlView.getNormalizedPaster());
mBeforeCropBitmap = mCurrentBitmap;
mTuiMultimediaPictureEditorCore.processPicture(bitmap -> {
mCurrentBitmap = bitmap;
previewPicture(0);
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
});
}
@Override
public void onCancelCrop() {
mCurrentBitmap = mBeforeCropBitmap;
previewPicture(0);
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
}
});
}
private String savaBitmapToLocalFile(Bitmap bitmap) {
String outVideoPath = TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE);
TUIMultimediaFileUtil.saveBmpToFile(bitmap, new File(outVideoPath), CompressFormat.JPEG);
return outVideoPath;
}
private void previewPicture(float rotation) {
if (mImageView == null || mCurrentBitmap == null) {
return;
}
LiteavLog.i(TAG, "previewPicture rotation : " + rotation);
Glide.with(mContext)
.load(mCurrentBitmap).apply(RequestOptions.bitmapTransform(new RotateTransformation(rotation)))
.into(mImageView);
}
private void cancelEdit() {
if (mIsRecordFile) {
((AppCompatActivity) mContext).getSupportFragmentManager().popBackStack();
} else {
finishEdit(null);
}
}
private void finishEdit(String editedFilePath) {
LiteavLog.i(TAG, "finish edit. path = " + editedFilePath);
Intent resultIntent = new Intent();
if (editedFilePath != null) {
resultIntent.putExtra(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH, editedFilePath);
resultIntent.putExtra(TUIMultimediaConstants.RECORD_TYPE_KEY, TUIMultimediaConstants.RECORD_TYPE_PHOTO);
}
((Activity) mContext).setResult(Activity.RESULT_OK, resultIntent);
((Activity) mContext).finish();
}
private void initExternalParameters() {
Bundle bundle = getArguments();
if (bundle == null) {
return;
}
mPictureFilePath = bundle.getString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH);
mIsRecordFile = bundle.getBoolean(TUIMultimediaConstants.PARAM_NAME_IS_RECODE_FILE, false);
}
}
@@ -0,0 +1,82 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
import androidx.core.math.MathUtils;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.ugc.TXPictureEditer;
import com.tencent.ugc.TXPictureEditer.PictureProcessListener;
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
import java.util.List;
public class TUIMultimediaPictureEditorCore {
private final String TAG = TUIMultimediaPictureEditorCore.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private TXPictureEditer mPictureEditer;
private Bitmap mBitmap;
public TUIMultimediaPictureEditorCore(Context context) {
LiteavLog.i(TAG,"TUIMultimedia Picture Editor Core Construct");
mContext = context;
mPictureEditer = TXPictureEditer.create(context);
}
public void release() {
if (mPictureEditer != null) {
TXPictureEditer.destroy(mPictureEditer);
}
}
public void resetEditor() {
LiteavLog.i(TAG,"reset editor");
if (mPictureEditer != null) {
TXPictureEditer.destroy(mPictureEditer);
mPictureEditer = TXPictureEditer.create(mContext);
}
mBitmap = null;
}
public void setSourcePicture(Bitmap bitmap) {
LiteavLog.i(TAG,"set source picture. bitmap: " + bitmap);
mPictureEditer.setPicture(bitmap);
mBitmap = bitmap;
}
public Bitmap getSourcePicture() {
return mBitmap;
}
public void setCropRectF(RectF rectf) {
if (mBitmap == null) {
LiteavLog.e(TAG,"set crop rect. bitmap is null");
return;
}
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
Rect rect = new Rect();
rect.left = MathUtils.clamp((int) (width * rectf.left), 0, width);
rect.right = MathUtils.clamp((int) (width * rectf.right), 0, width);
rect.top = MathUtils.clamp((int) (height * rectf.top), 0, height);
rect.bottom = MathUtils.clamp((int) (height * rectf.bottom), 0, height);
mPictureEditer.setCropRect(rect);
}
public void setPasterList(List<TXPaster> pasterList) {
mPictureEditer.setPasterList(pasterList);
}
public void setOutputRotation(int rotation) {
mPictureEditer.setOutputRotation(rotation);
}
public void processPicture(PictureProcessListener pictureProcessListener) {
LiteavLog.i(TAG,"process picture");
mPictureEditer.processPicture(pictureProcessListener);
}
}
@@ -0,0 +1,272 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.core.math.MathUtils;
import com.tencent.liteav.base.ThreadUtils;
import com.tencent.liteav.base.util.LiteavLog;
public class TUIMultimediaTransformLayout extends RelativeLayout implements View.OnTouchListener {
private final String TAG = TUIMultimediaTransformLayout.class.getSimpleName() + "_" + hashCode();
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private float mLastScaleFactor;
private float mScaleFactor = 1.0f;
private boolean isOnScaling = false;
private Rect mInitContentLayout;
private Rect mContentLayout;
private Rect mLimitLayout;
private final OnScaleGestureListener mOnScaleGestureListener = new OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float factorOffset = detector.getScaleFactor() - mLastScaleFactor;
mLastScaleFactor = detector.getScaleFactor();
mScaleFactor = MathUtils.clamp(mScaleFactor + factorOffset, 0.5f, 1.5f);
scaleContent(factorOffset + 1, new PointF(detector.getFocusX(), detector.getFocusY()));
return false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mLastScaleFactor = detector.getScaleFactor();
isOnScaling = true;
return true;
}
@Override
public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
ThreadUtils.getUiThreadHandler().postDelayed(() -> isOnScaling = false, 200);
}
};
private final OnGestureListener mOnGestureListener = new OnGestureListener() {
@Override
public boolean onDown(@NonNull MotionEvent e) {
return false;
}
@Override
public boolean onSingleTapUp(@NonNull MotionEvent e) {
return false;
}
@Override
public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
if (isOnScaling) {
return true;
}
if (Math.abs(distanceX) > 300 || Math.abs(distanceY) > 300) {
return true;
}
moveContent(distanceX, distanceY);
return false;
}
@Override
public void onShowPress(@NonNull MotionEvent e) {
}
@Override
public void onLongPress(@NonNull MotionEvent e) {
}
@Override
public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
return true;
}
};
public TUIMultimediaTransformLayout(Context context) {
super(context);
}
public TUIMultimediaTransformLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void initContentLayout(Rect rect) {
mInitContentLayout = rect;
mLimitLayout = mInitContentLayout;
setContentLayout(rect);
}
public Rect getInitContentLayout() {
return mInitContentLayout;
}
public void moveContent(float x, float y) {
if (mContentLayout == null || mLimitLayout == null) {
return;
}
int left = (int) (mContentLayout.left - x);
int top = (int) (mContentLayout.top - y);
setContentLayout(new Rect(left, top, left + mContentLayout.width(), top + mContentLayout.height()));
invalidate();
}
public void scaleContent(float scale, PointF center) {
if (mContentLayout == null) {
return;
}
int w = (int) (mContentLayout.width() * scale);
int h = (int) (mContentLayout.height() * scale);
int l = (int) (mContentLayout.left * scale + center.x - center.x * scale);
int t = (int) (mContentLayout.top * scale + center.y - center.y * scale);
setContentLayout(new Rect(l, t, w + l, h + t));
invalidate();
}
public void rotation90Content(Point rotationCenter) {
if (mContentLayout == null) {
return;
}
int newTop = rotationCenter.y - rotationCenter.x + mContentLayout.left;
int newLeft = rotationCenter.x + rotationCenter.y - mContentLayout.top - mContentLayout.height();
setContentLayout(new Rect(newLeft, newTop, newLeft + mContentLayout.height(), newTop + mContentLayout.width()));
}
public void enableTransform(boolean enable) {
LiteavLog.i(TAG, enable ? "enable " : "disable " + "transform.");
setOnTouchListener(enable ? this : null);
}
public void reset() {
mLastScaleFactor = 0.0f;
mScaleFactor = 1.0f;
mLimitLayout = mInitContentLayout;
setContentLayout(mInitContentLayout);
}
public Rect getContentRect() {
return mContentLayout;
}
@Override
public void addView(View view, int index) {
if (view.getParent() == this) {
return;
}
if (index > getChildCount()) {
index = getChildCount();
}
super.addView(view, index);
if (mContentLayout != null) {
view.layout(mContentLayout.left, mContentLayout.top, mContentLayout.right, mContentLayout.bottom);
}
}
public void setLimitRect(Rect rect) {
mLimitLayout = rect;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
mGestureDetector = new GestureDetector(getContext(), mOnGestureListener);
mScaleGestureDetector = new ScaleGestureDetector(getContext(), mOnScaleGestureListener);
setOnTouchListener(this);
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
removeAllViews();
super.onDetachedFromWindow();
}
@Override
public boolean onTouch(View v, @NonNull MotionEvent event) {
if (v != this) {
return false;
}
int pointerCount = event.getPointerCount();
if (event.getAction() == MotionEvent.ACTION_UP) {
adjustContentToLimitRect();
}
if (pointerCount >= 2) {
mScaleGestureDetector.onTouchEvent(event);
return true;
} else if (pointerCount == 1) {
mGestureDetector.onTouchEvent(event);
super.performClick();
return true;
}
return false;
}
@Override
public boolean performClick() {
return super.performClick();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed) {
return;
}
mLastScaleFactor = 0.0f;
mScaleFactor = 1.0f;
setContentLayout(mInitContentLayout);
}
private void setContentLayout(Rect rect) {
if (rect == null) {
return;
}
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(rect.left, rect.top, rect.right, rect.bottom);
}
mContentLayout = rect;
}
private void adjustContentToLimitRect() {
LiteavLog.i(TAG,"adjust content to limit rect.");
double scaleLimitX = mLimitLayout.width() * 1.0f / mContentLayout.width();
double scaleLimitY = mLimitLayout.height() * 1.0f / mContentLayout.height();
double scaleLimit = Math.max(scaleLimitX, scaleLimitY);
Rect rect = new Rect(mContentLayout);
Point center = new Point(rect.centerX(), rect.centerY());
if (scaleLimit > 1.0f) {
int w = (int) (mContentLayout.width() * scaleLimit);
int h = (int) (mContentLayout.height() * scaleLimit);
int l = (int) (mContentLayout.left * scaleLimit + center.x - center.x * scaleLimit);
int t = (int) (mContentLayout.top * scaleLimit + center.y - center.y * scaleLimit);
rect = new Rect(l, t, l + w, t + h);
}
int l = MathUtils.clamp(rect.left, mLimitLayout.right - rect.width(), mLimitLayout.left);
int t = MathUtils.clamp(rect.top, mLimitLayout.bottom - rect.height(), mLimitLayout.top);
rect = new Rect(l, t, l + rect.width(), t + rect.height());
setContentLayout(rect);
}
}
@@ -0,0 +1,224 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaAuthorizationPrompter;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.CommonMediaEditListener;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.EditType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMEditListener;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.ProgressRingView;
import com.tencent.ugc.TXVideoEditConstants.TXGenerateResult;
import com.tencent.ugc.TXVideoEditer.TXVideoGenerateListener;
public class TUIMultimediaVideoEditFragment extends Fragment {
private final String TAG = TUIMultimediaVideoEditFragment.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private TUIMultiMediaEditCommonCtrlView mTuiMultiMediaEditCommonCtrlView;
private TUIMultimediaVideoEditorCore mTUIMultimediaVideoEditorCore;
private View mRootView;
private View mGenerateVideoView;
private ProgressRingView mCircleProgressBar;
private String mEditFilePath;
private float mAspectRatio = 9.0f / 16.0f;
private boolean mIsRecordFile = false;
public TUIMultimediaVideoEditFragment(Context context) {
mContext = context;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
LiteavLog.i(TAG, "onCreate");
mTUIMultimediaVideoEditorCore = new TUIMultimediaVideoEditorCore(mContext);
initExternalParameters();
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LiteavLog.i(TAG, "onCreateView");
mRootView = inflater.inflate(R.layout.multimedia_plugin_edit_video_fragment, container, false);
initView();
return mRootView;
}
@Override
public void onDestroyView() {
LiteavLog.i(TAG, "onDestroyView");
super.onDestroyView();
}
@Override
public void onDestroy() {
LiteavLog.i(TAG, "onDestroy");
super.onDestroy();
mTUIMultimediaVideoEditorCore.release();
}
@Override
public void onResume() {
super.onResume();
mTUIMultimediaVideoEditorCore.resumePreview();
}
@Override
public void onPause() {
super.onPause();
mTUIMultimediaVideoEditorCore.pausePreview();
}
public void initView() {
mTuiMultiMediaEditCommonCtrlView = new TUIMultiMediaEditCommonCtrlView(mContext, EditType.VIDEO, mIsRecordFile);
mTuiMultiMediaEditCommonCtrlView.setMediaAspectRatio(mAspectRatio);
((RelativeLayout) mRootView.findViewById(R.id.edit_common_ctrl_view_container))
.addView(mTuiMultiMediaEditCommonCtrlView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mGenerateVideoView = mRootView.findViewById(R.id.rl_generate_video_view);
mCircleProgressBar = mRootView.findViewById(R.id.generate_video_progressBar);
mRootView.findViewById(R.id.generate_video_cancel_btn).setOnClickListener(view -> cancelGenerateVideo());
previewVideo(mEditFilePath);
mTuiMultiMediaEditCommonCtrlView.setBGMListener(new BGMEditListener() {
@Override
public void onAddBGM(String bgmPath) {
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
return;
}
mTUIMultimediaVideoEditorCore.setBGMPath(bgmPath);
}
@Override
public void onCutBGM(long startTime, long endTime) {
mTUIMultimediaVideoEditorCore.setBGMStartTime(startTime, endTime);
}
@Override
public void onMuteSourceAudio(boolean isMute) {
mTUIMultimediaVideoEditorCore.muteSourceAudio(isMute);
}
@Override
public void onMuteBGM(boolean isMute) {
mTUIMultimediaVideoEditorCore.muteBGM(isMute);
}
});
mTuiMultiMediaEditCommonCtrlView.setCommonMediaEditListener(new CommonMediaEditListener() {
@Override
public void onGenerateMedia() {
generateVideo();
}
@Override
public void onCancelEdit() {
if (mIsRecordFile) {
((AppCompatActivity) mContext).getSupportFragmentManager().popBackStack();
} else {
finishEdit(null);
}
}
});
}
private void previewVideo(String videoFilePath) {
LiteavLog.i(TAG, "preview video. file path = " + videoFilePath);
if (videoFilePath == null || videoFilePath.isEmpty()) {
LiteavLog.e(TAG, "vide file path is null");
return;
}
mTUIMultimediaVideoEditorCore.setSource(videoFilePath);
TUIMultimediaVideoPreview editorVideoView = new TUIMultimediaVideoPreview(mContext,
mTUIMultimediaVideoEditorCore);
mTuiMultiMediaEditCommonCtrlView.setMediaView(editorVideoView);
}
private void generateVideo() {
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
return;
}
mTUIMultimediaVideoEditorCore.stopPreview();
mTUIMultimediaVideoEditorCore.setPasterList(mTuiMultiMediaEditCommonCtrlView.getPaster());
if (!mIsRecordFile && !mTUIMultimediaVideoEditorCore.isVideoEdited()) {
finishEdit(null);
return;
}
mGenerateVideoView.setVisibility(View.VISIBLE);
mCircleProgressBar.setProgress(0);
String outVideoPath = TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.EDIT_FILE);
mTUIMultimediaVideoEditorCore.generateVideo(outVideoPath, TUIMultimediaIConfig.getInstance().getVideoQuality(),
new TXVideoGenerateListener() {
@Override
public void onGenerateProgress(float progress) {
mCircleProgressBar.setProgress(progress);
}
@Override
public void onGenerateComplete(TXGenerateResult txGenerateResult) {
LiteavLog.i(TAG, "onGenerateComplete = " + txGenerateResult.retCode);
finishEdit(outVideoPath);
mGenerateVideoView.setVisibility(View.GONE);
}
});
}
private void cancelGenerateVideo() {
((ProgressRingView) mRootView.findViewById(R.id.generate_video_progressBar)).setProgress(0);
mGenerateVideoView.setVisibility(View.GONE);
mTuiMultiMediaEditCommonCtrlView.bringToFront();
mTuiMultiMediaEditCommonCtrlView.showOperationView(true);
mTUIMultimediaVideoEditorCore.cancelGenerateVideo();
mTUIMultimediaVideoEditorCore.reStartPreview();
mTUIMultimediaVideoEditorCore.clearPaster();
}
private void finishEdit(String editedFilePath) {
LiteavLog.i(TAG, "finish edit. path = " + editedFilePath);
Intent resultIntent = new Intent();
if (editedFilePath != null) {
resultIntent.putExtra(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH, editedFilePath);
resultIntent.putExtra(TUIMultimediaConstants.RECORD_TYPE_KEY, TUIMultimediaConstants.RECORD_TYPE_VIDEO);
}
((Activity) mContext).setResult(Activity.RESULT_OK, resultIntent);
((Activity) mContext).finish();
}
private void initExternalParameters() {
Bundle bundle = getArguments();
if (bundle == null) {
return;
}
mEditFilePath = bundle.getString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH, "");
mAspectRatio = bundle.getFloat(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_RATIO, 9.0f / 16.0f);
mIsRecordFile = bundle.getBoolean(TUIMultimediaConstants.PARAM_NAME_IS_RECODE_FILE, false);
LiteavLog.i(TAG, "init external parameters mEditFilePath = " + mEditFilePath
+ " video quality = " + TUIMultimediaIConfig.getInstance().getVideoQuality()
+ ", video ratio = " + mAspectRatio);
}
}
@@ -0,0 +1,271 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import android.content.Context;
import androidx.annotation.NonNull;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants.VideoQuality;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
import com.tencent.rtmp.ui.TXCloudVideoView;
import com.tencent.ugc.TXRecordCommon;
import com.tencent.ugc.TXVideoEditConstants;
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
import com.tencent.ugc.TXVideoEditConstants.TXPreviewParam;
import com.tencent.ugc.TXVideoEditConstants.TXSubtitle;
import com.tencent.ugc.TXVideoEditConstants.TXVideoInfo;
import com.tencent.ugc.TXVideoEditer;
import com.tencent.ugc.TXVideoEditer.TXVideoPreviewListener;
import com.tencent.ugc.TXVideoInfoReader;
import java.util.LinkedList;
import java.util.List;
public class TUIMultimediaVideoEditorCore {
private static final int LOW_QUALITY_BITRATE = 1000;
private static final int MEDIUM_QUALITY_BITRATE = 3000;
private static final int HIGH_QUALITY_BITRATE = 5000;
private final String TAG = TUIMultimediaVideoEditorCore.class.getSimpleName() + "_" + hashCode();
private TXVideoEditer mEditorSDK;
private boolean mIsLoopPlay = false;
private boolean mIsAddBgm = false;
private boolean mIsAddPaster = false;
private boolean mIsMute = false;
private final TXVideoPreviewListener mTXVideoPreviewListener = new TXVideoPreviewListener() {
@Override
public void onPreviewProgress(int i) {}
@Override
public void onPreviewFinished() {
LiteavLog.i(TAG, "on preview finished.");
if (mIsLoopPlay && mEditorSDK != null) {
mEditorSDK.stopPlay();
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
}
}
};
public TUIMultimediaVideoEditorCore(Context context) {
LiteavLog.i(TAG, "TUIMultimediaEditorCore construct.");
mEditorSDK = new TXVideoEditer(context);
}
public void setSource(String path) {
LiteavLog.i(TAG, "set source path is " + path);
if (mEditorSDK != null) {
mEditorSDK.setVideoPath(path);
}
}
public void startPreview(TXCloudVideoView videoView, boolean isLoopPlay) {
if (mEditorSDK == null) {
LiteavLog.e(TAG,"start preview. editor SDK has release.");
return;
}
LiteavLog.i(TAG, "start preview videoView is " + videoView + " loop play flag is " + isLoopPlay);
mIsLoopPlay = isLoopPlay;
TXPreviewParam previewParam = new TXPreviewParam();
previewParam.videoView = videoView;
previewParam.renderMode = TXVideoEditConstants.PREVIEW_RENDER_MODE_FILL_EDGE;
mEditorSDK.initWithPreview(previewParam);
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
mEditorSDK.setTXVideoPreviewListener(mTXVideoPreviewListener);
}
public void muteBGM(boolean isMute) {
LiteavLog.i(TAG," mute bgm. is mute:" + isMute);
mIsAddBgm = !isMute;
if (mEditorSDK != null) {
mEditorSDK.setBGMVolume(isMute ? 0f : 1.0f);
}
}
public void muteSourceAudio(boolean isMute) {
LiteavLog.i(TAG," mute source audio. is mute:" + isMute);
mIsMute = isMute;
if (mEditorSDK != null) {
mEditorSDK.setVideoVolume(isMute ? 0f : 1.0f);
}
}
public void stopPreview() {
LiteavLog.i(TAG, "stop preview");
if (mEditorSDK != null) {
mEditorSDK.stopPlay();
mEditorSDK.setTXVideoPreviewListener(null);
}
}
public void pausePreview() {
LiteavLog.i(TAG,"pause preview");
if (mEditorSDK != null) {
mEditorSDK.pausePlay();
}
}
public void resumePreview() {
LiteavLog.i(TAG,"resume preview");
if (mEditorSDK != null) {
mEditorSDK.resumePlay();
}
}
public void reStartPreview() {
LiteavLog.i(TAG,"restart preview");
if (mEditorSDK != null) {
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
mEditorSDK.setTXVideoPreviewListener(mTXVideoPreviewListener);
}
}
public void generateVideo(String videoOutputPath, VideoQuality videoQuality,
TXVideoEditer.TXVideoGenerateListener listener) {
if (mEditorSDK == null) {
LiteavLog.e(TAG,"generate video. editor SDK has release.");
return;
}
LiteavLog.i(TAG, "generate video.videoOutputPath = " + videoOutputPath + " VideoQuality = " + videoQuality);
mEditorSDK.setVideoGenerateListener(listener);
mEditorSDK.setVideoBitrate(getBitrateAccordQuality(videoQuality));
mEditorSDK.generateVideo(getCompressLeveAccordQuality(videoQuality), videoOutputPath);
}
public void cancelGenerateVideo() {
LiteavLog.i(TAG, "cancel generate video");
if (mEditorSDK != null) {
mEditorSDK.cancel();
mEditorSDK.processVideo();
}
}
public void setPasterList(@NonNull List<TUIMultimediaPasterInfo> pasterInfoList) {
if (mEditorSDK == null) {
return;
}
LiteavLog.i(TAG,"set Paster List. size = " + pasterInfoList.size());
List<TXSubtitle> subtitleList = new LinkedList<>();
List<TXPaster> pasterList = new LinkedList<>();
for (TUIMultimediaPasterInfo pasterInfo : pasterInfoList) {
if (pasterInfo.pasterType == PasterType.SUBTITLE_PASTER) {
TXSubtitle subtitle = new TXSubtitle();
subtitle.frame = pasterInfo.frame;
subtitle.titleImage = pasterInfo.image;
subtitle.startTime = 0;
subtitle.endTime = Integer.MAX_VALUE;
subtitleList.add(subtitle);
} else if (pasterInfo.pasterType == PasterType.STATIC_PICTURE_PASTER) {
TXPaster staticPaster = new TXPaster();
staticPaster.frame = pasterInfo.frame;
staticPaster.pasterImage = pasterInfo.image;
staticPaster.startTime = 0;
staticPaster.endTime = Integer.MAX_VALUE;
pasterList.add(staticPaster);
}
mIsAddPaster = true;
}
if (!subtitleList.isEmpty()) {
mEditorSDK.setSubtitleList(subtitleList);
}
if (!pasterList.isEmpty()) {
mEditorSDK.setPasterList(pasterList);
}
}
public void clearPaster() {
LiteavLog.i(TAG, "clear Paster");
if (mEditorSDK != null) {
mEditorSDK.setSubtitleList(null);
mEditorSDK.setPasterList(null);
}
mIsAddPaster = false;
}
public void setBGMPath(String path) {
LiteavLog.i(TAG, "set bgm path. path is " + path);
if (mEditorSDK != null) {
mEditorSDK.setBGM(path);
mEditorSDK.setBGMLoop(true);
mEditorSDK.setBGMVolume(1.0f);
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
}
mIsAddBgm = (path != null && !path.isEmpty());
}
public void setBGMStartTime(long startTime, long endTime) {
LiteavLog.i(TAG, "set BGM start time. " + " start time :" + startTime + " end time : " + endTime);
if (startTime >= 0 && endTime > startTime && mEditorSDK != null) {
mEditorSDK.setBGMStartTime(startTime, endTime);
}
}
public boolean isVideoEdited() {
LiteavLog.i(TAG,"is add paster:[%s],is add bgm:[%s],",mIsAddPaster, mIsAddBgm);
return mIsAddPaster || mIsAddBgm || mIsMute;
}
public void release() {
LiteavLog.i(TAG, "stop preview");
if (mEditorSDK != null) {
mEditorSDK.release();
mEditorSDK = null;
}
}
public static int getCompressLeveAccordQuality(VideoQuality videoQuality) {
switch (videoQuality) {
case HIGH:
return TXRecordCommon.VIDEO_RESOLUTION_1080_1920;
case LOW:
case MEDIUM:
default:
return TXRecordCommon.VIDEO_RESOLUTION_720_1280;
}
}
public static int getBitrateAccordQuality(VideoQuality videoQuality) {
switch (videoQuality) {
case LOW:
return LOW_QUALITY_BITRATE;
case HIGH:
return HIGH_QUALITY_BITRATE;
case MEDIUM:
default:
return MEDIUM_QUALITY_BITRATE;
}
}
public static TXVideoInfo getVideoFileInfo(Context context, String filePath) {
try {
return TXVideoInfoReader.getInstance(context).getVideoFileInfo(filePath);
} catch (Exception e) {
LiteavLog.i(TUIMultimediaVideoEditorCore.class.getSimpleName(),"getVideoFileInfo Exception e = ",e);
return null;
}
}
public static boolean isValidVideo(TXVideoInfo videoInfo) {
if (videoInfo == null) {
return false;
}
return videoInfo.duration != 0 || videoInfo.width != 0 || videoInfo.height != 0
|| videoInfo.bitrate != 0 || videoInfo.fps != 0;
}
public static float getVideoAspect(TXVideoInfo videoInfo) {
float aspect = 9.0f / 16.0f;
if (videoInfo == null) {
return aspect;
}
if (videoInfo.width != 0 && videoInfo.height != 0) {
aspect = videoInfo.width * 1.0f / videoInfo.height;
}
return aspect;
}
}
@@ -0,0 +1,46 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.rtmp.ui.TXCloudVideoView;
@SuppressLint("ViewConstructor")
public class TUIMultimediaVideoPreview extends FrameLayout {
private final String TAG = TUIMultimediaVideoPreview.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaVideoEditorCore mEditorCore;
public TUIMultimediaVideoPreview(@NonNull Context context, TUIMultimediaVideoEditorCore editorCore) {
super(context);
mContext = context;
mEditorCore = editorCore;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
mEditorCore.stopPreview();
removeAllViews();
}
public void initView() {
View rootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_video_play_view, this, true);
TXCloudVideoView videoView = rootView.findViewById(R.id.edit_video_view);
mEditorCore.startPreview(videoView, true);
}
}
@@ -0,0 +1,11 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
public interface BGMEditListener {
void onAddBGM(String bgmPath);
void onCutBGM(long startTime, long endTime);
void onMuteSourceAudio(boolean isMute);
void onMuteBGM(boolean isMute);
}
@@ -0,0 +1,116 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo.ItemPosition;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMItem;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMType;
public class BGMItemAdapter extends BaseAdapter {
private final Context mContext;
private final BGMInfo mBGMInfo;
private final int mBGMTypeIndex;
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition;
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable;
private BGMItemView mLastSelectedItemView;
public BGMItemAdapter(Context context, BGMInfo bgmInfo, int bgmTypeIndex,
TUIMultimediaData<ItemPosition> tuiDataSelectedPosition, TUIMultimediaData<Boolean> tuiDataBGMEnable) {
mContext = context;
mBGMInfo = bgmInfo;
mBGMTypeIndex = bgmTypeIndex;
mTuiDataSelectedPosition = tuiDataSelectedPosition;
mTuiDataSelectedPosition.observe(position -> mLastSelectedItemView = null);
mTuiDataBGMEnable = tuiDataBGMEnable;
mTuiDataBGMEnable.observe(enable -> {
if (mLastSelectedItemView != null) {
mLastSelectedItemView.setEnabled(enable);
}
});
}
@Override
public int getCount() {
BGMType bgmType = mBGMInfo.getBGMType(mBGMTypeIndex);
return bgmType != null ? bgmType.getItemSize() : 0;
}
@Override
public Object getItem(int position) {
BGMType bgmType = mBGMInfo.getBGMType(mBGMTypeIndex);
return bgmType != null ? bgmType.getBGMItem(position) : null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.multimedia_plugin_edit_bgm_item_view_container, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ItemPosition currentPosition = new ItemPosition(mBGMTypeIndex, position);
BGMItem bgmItem = mBGMInfo.getBGMItem(currentPosition);
BGMItemView bgmItemView = holder.bgmItemView;
bgmItemView.bindBGMItem(bgmItem);
boolean isSelected = currentPosition.equals(mTuiDataSelectedPosition.get());
bgmItemView.setSelected(isSelected);
if (isSelected) {
bgmItemView.setEnabled(mTuiDataBGMEnable.get());
if (mLastSelectedItemView != null && mLastSelectedItemView != bgmItemView) {
mLastSelectedItemView.setSelected(false);
}
mLastSelectedItemView = bgmItemView;
}
convertView.setOnClickListener(v -> {
if (currentPosition.equals(mTuiDataSelectedPosition.get())) {
return;
}
if (mLastSelectedItemView != null) {
mLastSelectedItemView.setSelected(false);
}
mTuiDataSelectedPosition.set(currentPosition);
mLastSelectedItemView = bgmItemView;
bgmItemView.setSelected(true);
bgmItemView.setEnabled(true);
});
return convertView;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public BGMItemView bgmItemView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
bgmItemView = new BGMItemView(itemView.getContext());
((RelativeLayout) itemView).addView(bgmItemView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
}
}
@@ -0,0 +1,120 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMItem;
@SuppressLint("ViewConstructor")
public class BGMItemView extends RelativeLayout {
private final String TAG = BGMItemView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private TextView mBGMNameTextView;
private TextView mBGMAuthorTextView;
private TextView mBGMDurationTextView;
private BGMSpectrumView mSpectrumView;
private BGMItem mBgmItem;
public BGMItemView(Context context) {
super(context);
mContext = context;
}
public void bindBGMItem(@NonNull BGMItem bgmItem) {
mBgmItem = bgmItem;
initData();
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
mSpectrumView.stop();
removeAllViews();
}
private void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_bgm_item_view, this, true);
mBGMNameTextView = findViewById(R.id.bgm_name);
mBGMAuthorTextView = findViewById(R.id.bgm_author_name);
mBGMDurationTextView = findViewById(R.id.bgm_duration);
mSpectrumView = findViewById(R.id.bgm_spectrum_view);
initData();
setViewStatus();
}
private void initData() {
if (!isAttachedToWindow()) {
return;
}
String bgmName = mBgmItem.getBgmName();
if (bgmName != null) {
mBGMNameTextView.setText(bgmName);
}
String bgmAuthorName = mBgmItem.getBGMAuthorName();
if (bgmAuthorName != null && !bgmAuthorName.isEmpty()) {
mBGMAuthorTextView.setText(bgmAuthorName);
}
TUIMultimediaData<Long> tuiDataBGMDuration = new TUIMultimediaData<>(0L);
tuiDataBGMDuration.observe(
duration -> mBGMDurationTextView.setText(TUIMultimediaResourceUtils.secondsToTimeString(duration)));
mBgmItem.getBGMDuration(tuiDataBGMDuration);
}
@Override
public void setSelected(boolean selected) {
super.setSelected(selected);
setViewStatus();
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setViewStatus();
}
private void setViewStatus() {
if (!isAttachedToWindow()) {
return;
}
boolean isSelected = isSelected();
boolean isEnable = isEnabled();
if (isSelected) {
mSpectrumView.setVisibility(VISIBLE);
mSpectrumView.setEnabled(isEnable);
} else {
mSpectrumView.setVisibility(GONE);
mSpectrumView.stop();
}
@ColorInt int bgmNameTextColor = TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_bgm_item_name_text_color_normal);
if (isSelected && isEnable) {
bgmNameTextColor = TUIMultimediaIConfig.getInstance().getThemeColor();
}
mBGMNameTextView.setTextColor(bgmNameTextColor);
}
}
@@ -0,0 +1,44 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
import com.google.gson.Gson;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaCommonThreadPool;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class BGMManager {
private final String TAG = BGMManager.class.getSimpleName() + "_" + hashCode();
private final String mBGMJsonFilePath;
private Future<BGMInfo> mCreateBGMInfoFuture;
public BGMManager(String bgmJsonFilePath) {
mBGMJsonFilePath = bgmJsonFilePath;
}
public void init() {
mCreateBGMInfoFuture = TUIMultimediaCommonThreadPool.getThreadExecutor().submit(this::createBGMInfoFromJsonFile);
}
protected BGMInfo getBGMInfo() {
try {
return mCreateBGMInfoFuture.get(500, TimeUnit.MILLISECONDS);
} catch (Exception e) {
LiteavLog.e(TAG, "create picture paster set info error " + e);
return null;
}
}
private BGMInfo createBGMInfoFromJsonFile() {
LiteavLog.i(TAG, "bgm json file path =" + mBGMJsonFilePath);
if (mBGMJsonFilePath == null || mBGMJsonFilePath.isEmpty() || !mBGMJsonFilePath.endsWith(".json")) {
LiteavLog.e(TAG, "pasterJsonFilePath =" + mBGMJsonFilePath + " is invalid paster json file");
return null;
}
String json = TUIMultimediaFileUtil.readTextFromFile(mBGMJsonFilePath);
Gson gson = new Gson();
return gson.fromJson(json, BGMInfo.class);
}
}
@@ -0,0 +1,269 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
import com.google.android.material.tabs.TabLayout.Tab;
import com.google.android.material.tabs.TabLayoutMediator;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo.ItemPosition;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMItem;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMType;
@SuppressLint("ViewConstructor")
public class BGMPanelView extends RelativeLayout {
private final String TAG = BGMPanelView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final BGMInfo mBGMInfo;
private final BGMManager mBGMManager;
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable = new TUIMultimediaData<>(null);
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition = new TUIMultimediaData<>(null);
private TabLayout mBGMTypeTabLayout;
private BGMTypeAdapter mBGMTypeAdapter;
private CheckBox mBGMEnableCheckBox;
private ItemPosition mLastSelectedItemPosition;
private BGMEnableListener mBgmEnableListener;
private BGMEditListener mBGMEditListener;
private final OnTabSelectedListener onTabSelectedListener = new OnTabSelectedListener() {
@Override
public void onTabSelected(Tab tab) {
if (mBGMTypeAdapter != null) {
mBGMTypeAdapter.setSelectedBGMItemView(mTuiDataSelectedPosition.get());
}
changeTabSelectedStatus(tab, true);
}
@Override
public void onTabUnselected(Tab tab) {
changeTabSelectedStatus(tab, false);
}
@Override
public void onTabReselected(Tab tab) {
}
};
public BGMPanelView(Context context, BGMManager bgmManager) {
super(context);
mContext = context;
mBGMManager = bgmManager;
mBGMInfo = mBGMManager.getBGMInfo();
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
addObserver();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
removeObserver();
super.onDetachedFromWindow();
}
public void setBGMEditListener(BGMEditListener bgmEditListener) {
mBGMEditListener = bgmEditListener;
}
public void showBGMPanel() {
setVisibility(View.VISIBLE);
ItemPosition itemPosition = mTuiDataSelectedPosition.get();
if (itemPosition == null) {
return;
}
if (itemPosition.bgmTypeIndex != mBGMTypeTabLayout.getSelectedTabPosition()) {
Tab tab = mBGMTypeTabLayout.getTabAt(itemPosition.bgmTypeIndex);
if (tab != null) {
tab.select();
return;
}
}
if (mBGMTypeAdapter != null) {
mBGMTypeAdapter.setSelectedBGMItemView(itemPosition);
}
}
private void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_bgm_panel_view, this, true);
initBgmListView();
intBGMEnableView();
}
private void intBGMEnableView() {
mBGMEnableCheckBox = findViewById(R.id.bgm_enable);
mBGMEnableCheckBox.setButtonDrawable(getCheckBoxButtonDrawable());
mBGMEnableCheckBox.setEnabled(false);
mBGMEnableCheckBox.setChecked(false);
mBGMEnableCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (mBGMEditListener != null) {
mBGMEditListener.onMuteBGM(!isChecked);
}
mTuiDataBGMEnable.set(isChecked);
});
CheckBox sourceAudioEnableCheckBox = findViewById(R.id.source_sound_enable);
sourceAudioEnableCheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (mBGMEditListener != null) {
mBGMEditListener.onMuteSourceAudio(!isChecked);
}
});
sourceAudioEnableCheckBox.setButtonDrawable(getCheckBoxButtonDrawable());
}
private Drawable getCheckBoxButtonDrawable() {
StateListDrawable stateListDrawable = new StateListDrawable();
Drawable selectedDrawable = ContextCompat
.getDrawable(mContext, R.drawable.multimedia_plugin_edit_bgm_checkbox_selected);
DrawableCompat.setTint(selectedDrawable, TUIMultimediaIConfig.getInstance().getThemeColor());
Drawable unSelectedDrawable = ContextCompat
.getDrawable(mContext, R.drawable.multimedia_plugin_edit_bgm_checkbox_unselected);
DrawableCompat.setTint(unSelectedDrawable, TUIMultimediaIConfig.getInstance().getThemeColor());
stateListDrawable.addState(new int[]{android.R.attr.state_checked}, selectedDrawable);
stateListDrawable.addState(new int[]{-android.R.attr.state_checked}, unSelectedDrawable);
return stateListDrawable;
}
private void initBgmListView() {
ViewPager2 beautyPager = findViewById(R.id.bgm_panel_view_pager);
mBGMTypeAdapter = new BGMTypeAdapter(mContext, mBGMInfo, mTuiDataSelectedPosition, mTuiDataBGMEnable);
beautyPager.setAdapter(mBGMTypeAdapter);
mBGMTypeTabLayout = findViewById(R.id.bgm_panel_tab_layout);
mBGMTypeTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
mBGMTypeTabLayout.addOnTabSelectedListener(onTabSelectedListener);
new TabLayoutMediator(mBGMTypeTabLayout, beautyPager, this::configTab).attach();
}
private void configTab(@NonNull Tab tab, int position) {
if (mBGMInfo == null) {
return;
}
BGMType typeInfo = mBGMInfo.getBGMType(position);
String text = typeInfo != null ? typeInfo.getTypeName()
: TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_bgm_type_name_default);
TextView customTextView = new TextView(getContext());
customTextView.setGravity(Gravity.CENTER);
customTextView.setText(text);
customTextView.setMaxLines(1);
customTextView.setTextColor(
TUIMultimediaResourceUtils.getResources()
.getColor(R.color.multimedia_plugin_bgm_type_text_color_normal));
float textSize = TUIMultimediaResourceUtils.getResources()
.getDimension(R.dimen.multimedia_plugin_common_tab_text_size);
customTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
customTextView.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
tab.setCustomView(customTextView);
}
private void changeTabSelectedStatus(Tab tab, boolean isSelected) {
TextView tabTextView = (TextView) tab.getCustomView();
if (tabTextView == null) {
return;
}
int textColorResourceId = R.color.multimedia_plugin_bgm_type_text_color_normal;
int textColorFontStyle = Typeface.NORMAL;
if (isSelected) {
textColorResourceId = R.color.multimedia_plugin_bgm_type_text_color_selected;
textColorFontStyle = Typeface.BOLD;
}
tabTextView.setTextColor(
TUIMultimediaResourceUtils.getResources().getColor(textColorResourceId));
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
tabTextView.setTypeface(Typeface.create(fontName, textColorFontStyle));
}
private void onBGMItemSelectedChanged(ItemPosition bgmItemPosition) {
if (bgmItemPosition == mLastSelectedItemPosition) {
return;
}
setBGM(mBGMInfo.getBGMItem(bgmItemPosition));
if (mBGMEnableCheckBox != null) {
mBGMEnableCheckBox.setChecked(true);
}
mTuiDataBGMEnable.set(true);
mLastSelectedItemPosition = bgmItemPosition;
}
private void setBGM(BGMItem bgmItem) {
if (bgmItem == null) {
return;
}
mBGMEnableCheckBox.setEnabled(true);
TUIMultimediaData<String> tuiDataBGMFilePath = new TUIMultimediaData<>("");
tuiDataBGMFilePath.observe(path -> {
if (mBGMEditListener != null) {
mBGMEditListener.onAddBGM(path);
}
});
bgmItem.getBGMFilePath(tuiDataBGMFilePath);
long bgmStatTime = bgmItem.startTime > 0 ? bgmItem.startTime : 0;
long bgmEndTime = bgmItem.endTime > bgmStatTime ? bgmItem.endTime : Integer.MAX_VALUE;
if (mBGMEditListener != null) {
mBGMEditListener.onCutBGM(bgmStatTime, bgmEndTime);
}
}
private void addObserver() {
mTuiDataSelectedPosition.observe(this::onBGMItemSelectedChanged);
mTuiDataBGMEnable.observe(this::notifyBGMEnable);
}
private void removeObserver() {
mBGMTypeTabLayout.removeOnTabSelectedListener(onTabSelectedListener);
mTuiDataSelectedPosition.removeObserver(this::onBGMItemSelectedChanged);
mTuiDataBGMEnable.removeObserver(this::notifyBGMEnable);
}
private void notifyBGMEnable(boolean enable) {
if (mBgmEnableListener != null) {
mBgmEnableListener.onBGMEnable(enable);
}
}
public interface BGMEnableListener {
void onBGMEnable(boolean enable);
}
}
@@ -0,0 +1,111 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import java.util.Arrays;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
public class BGMSpectrumView extends View {
private static final int SPECTRUM_POINT_NUM = 20;
private static final float[] DEFAULT_FREQUENCY_SPECTRUM = new float[SPECTRUM_POINT_NUM];
static {
Random random = new Random();
for (int i = 0; i < DEFAULT_FREQUENCY_SPECTRUM.length; i++) {
DEFAULT_FREQUENCY_SPECTRUM[i] = random.nextFloat();
}
}
private final float[] mFrequencySpectrumArray = new float[SPECTRUM_POINT_NUM];
private Paint mPaint;
private float mStrokePreWidth;
private float mViewHeight;
private Timer mUpdateFrequencyTimer;
public BGMSpectrumView(Context context) {
this(context, null);
}
public BGMSpectrumView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BGMSpectrumView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Arrays.fill(mFrequencySpectrumArray, 0);
initPaint();
}
public void start() {
if (mUpdateFrequencyTimer != null) {
return;
}
mUpdateFrequencyTimer = new Timer();
mUpdateFrequencyTimer.schedule(new TimerTask() {
@Override
public void run() {
Random random = new Random();
for (int i = 0; i < mFrequencySpectrumArray.length; i++) {
mFrequencySpectrumArray[i] = random.nextFloat();
}
postInvalidate();
}
}, 0, 100);
}
public void stop() {
if (mUpdateFrequencyTimer != null) {
mUpdateFrequencyTimer.cancel();
mUpdateFrequencyTimer = null;
}
}
private void initPaint() {
mPaint = new Paint();
mPaint.setColor(TUIMultimediaIConfig.getInstance().getThemeColor());
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
public void setEnabled(boolean enabled) {
if (enabled) {
start();
mPaint.setColor(TUIMultimediaIConfig.getInstance().getThemeColor());
} else {
stop();
mPaint.setColor(TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_bgm_item_spectrum_color_disable));
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float[] frequencySpectrumArray =
mUpdateFrequencyTimer != null ? mFrequencySpectrumArray : DEFAULT_FREQUENCY_SPECTRUM;
for (int i = 0; i < frequencySpectrumArray.length; i++) {
canvas.drawLine(mStrokePreWidth * i, mViewHeight, mStrokePreWidth * i,
mViewHeight - mViewHeight * frequencySpectrumArray[i], mPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
super.onSizeChanged(w, h, oldW, oldH);
float mViewWidth = getWidth();
mViewHeight = getHeight();
mStrokePreWidth = (mViewWidth / SPECTRUM_POINT_NUM);
mPaint.setStrokeWidth(mStrokePreWidth - 1);
}
}
@@ -0,0 +1,100 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo.ItemPosition;
import java.util.LinkedList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public class BGMTypeAdapter extends FragmentStateAdapter {
private final Context mContext;
private final BGMInfo mBGMInfo;
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition;
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable;
private final List<BGMTypeItemFragment> mBGMTypeItemFragmentList = new LinkedList<>();
public BGMTypeAdapter(@NonNull Context context, @NonNull BGMInfo bgmInfo,
@NonNull TUIMultimediaData<ItemPosition> tuiDataSelectedPosition, TUIMultimediaData<Boolean> tuiDataBGMEnable) {
super((FragmentActivity) context);
mBGMInfo = bgmInfo;
mContext = context;
mTuiDataSelectedPosition = tuiDataSelectedPosition;
mTuiDataBGMEnable = tuiDataBGMEnable;
}
@NonNull
@Override
public Fragment createFragment(int bgmTypeIndex) {
BGMTypeItemFragment fragment = new BGMTypeItemFragment(mContext, mBGMInfo, bgmTypeIndex,
mTuiDataSelectedPosition, mTuiDataBGMEnable);
if (!mBGMTypeItemFragmentList.contains(fragment)) {
mBGMTypeItemFragmentList.add(bgmTypeIndex, fragment);
}
return fragment;
}
@Override
public int getItemCount() {
return mBGMInfo != null ? mBGMInfo.getBGMTypeSize() : 0;
}
public void setSelectedBGMItemView(ItemPosition position) {
if (position == null) {
return;
}
if (position.bgmTypeIndex >= 0 && position.bgmTypeIndex < mBGMTypeItemFragmentList.size()) {
BGMTypeItemFragment fragment = mBGMTypeItemFragmentList.get(position.bgmTypeIndex);
fragment.setSelectedBGMItemView(position.bgmItemIndex);
}
}
public static class BGMTypeItemFragment extends Fragment {
private final BGMInfo mBGMInfo;
private final Context mContext;
private final int mBGMTypeIndex;
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition;
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable;
private BGMItemAdapter mAdapter;
private ListView mListView;
public BGMTypeItemFragment(Context context, BGMInfo BGMInfo, int typeIndex,
TUIMultimediaData<ItemPosition> tuiDataSelectedPosition, TUIMultimediaData<Boolean> tuiDataBGMEnable) {
mContext = context;
mBGMInfo = BGMInfo;
mBGMTypeIndex = typeIndex;
mTuiDataSelectedPosition = tuiDataSelectedPosition;
mTuiDataBGMEnable = tuiDataBGMEnable;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.multimedia_plugin_edit_bgm_type_fragment, container, false);
mListView = view.findViewById(R.id.bgm_list_view);
mAdapter = new BGMItemAdapter(mContext, mBGMInfo, mBGMTypeIndex, mTuiDataSelectedPosition,
mTuiDataBGMEnable);
mListView.setAdapter(mAdapter);
return view;
}
public void setSelectedBGMItemView(int position) {
mListView.setSelection(position);
}
}
}
@@ -0,0 +1,117 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.tencent.liteav.base.util.LiteavLog;
import java.io.FileWriter;
import java.util.LinkedList;
import java.util.List;
public class BGMInfo {
@SerializedName("bgm_list")
private List<BGMType> mBGMTypeItemList;
public int getSize() {
return mBGMTypeItemList.size();
}
public void addBGMItem(BGMInfo.ItemPosition position, BGMItem bgmItem) {
if (position.bgmTypeIndex < 0) {
return;
}
if (mBGMTypeItemList == null) {
mBGMTypeItemList = new LinkedList<>();
}
while (position.bgmTypeIndex + 1 - mBGMTypeItemList.size() > 0) {
mBGMTypeItemList.add(new BGMType());
}
mBGMTypeItemList.get(position.bgmTypeIndex).addBGMItem(position.bgmItemIndex, bgmItem);
}
public int getBGMTypeSize() {
return mBGMTypeItemList.size();
}
public BGMType getBGMType(int BGMTypeIndex) {
if (mBGMTypeItemList == null) {
return null;
}
if (BGMTypeIndex < 0 || BGMTypeIndex >= mBGMTypeItemList.size()) {
return null;
}
return mBGMTypeItemList.get(BGMTypeIndex);
}
public BGMItem getBGMItem(BGMInfo.ItemPosition itemPosition) {
BGMType typeInfo = getBGMType(itemPosition.bgmTypeIndex);
return typeInfo != null ? typeInfo.getBGMItem(itemPosition.bgmItemIndex) : null;
}
public void addBGMType(BGMType bgmType) {
if (mBGMTypeItemList == null) {
mBGMTypeItemList = new LinkedList<>();
}
mBGMTypeItemList.add(bgmType);
}
public void append(BGMInfo otherSetInfo) {
if (otherSetInfo == null) {
return;
}
List<BGMType> otherBGMTypeItemList = otherSetInfo.mBGMTypeItemList;
if (otherBGMTypeItemList == null) {
return;
}
for (int i = 0; i < otherBGMTypeItemList.size(); i++) {
BGMType typeItemInfo = otherBGMTypeItemList.get(i);
if (typeItemInfo == null) {
continue;
}
List<BGMItem> itemInfoList = typeItemInfo.getBGMItemList();
if (itemInfoList == null) {
continue;
}
for (BGMItem itemInfo : itemInfoList) {
addBGMItem(new ItemPosition(i, 0), itemInfo);
}
}
}
public void saveToJsonFile(String filePath) {
Gson gson = new Gson();
try {
FileWriter fileWriter = new FileWriter(filePath);
gson.toJson(this, fileWriter);
fileWriter.flush();
fileWriter.close();
} catch (Exception e) {
LiteavLog.e("save json file error", e.toString());
}
}
public static class ItemPosition {
public final int bgmTypeIndex;
public final int bgmItemIndex;
public ItemPosition(int bgmTypeIndex, int bgmItemIndex) {
this.bgmTypeIndex = bgmTypeIndex;
this.bgmItemIndex = bgmItemIndex;
}
public boolean equals(BGMInfo.ItemPosition other) {
if (other == null) {
return false;
}
return bgmTypeIndex == other.bgmTypeIndex && bgmItemIndex == other.bgmItemIndex;
}
}
}
@@ -0,0 +1,142 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data;
import android.media.MediaMetadataRetriever;
import android.os.Looper;
import com.google.gson.annotations.SerializedName;
import com.tencent.liteav.base.util.CustomHandler;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaCommonThreadPool;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import java.io.File;
import java.io.IOException;
public class BGMItem {
private static final String BGM_CACHE_FILE_DIR = "/multimediaBGM/";
@SerializedName("item_bgm_path")
public String bgmFilePath;
@SerializedName("item_bgm_name")
public String bgmName;
@SerializedName("item_start_time")
public long startTime;
@SerializedName("item_end_time")
public long endTime;
public Long duration;
@SerializedName("item_bgm_author")
private String bgmAuthorName;
public String getBGMAuthorName() {
return bgmAuthorName;
}
public String getBgmName() {
return bgmName;
}
public void getBGMFilePath(TUIMultimediaData<String> tuiDataBGMFilePath) {
if (bgmFilePath.isEmpty()) {
tuiDataBGMFilePath.set(null);
return;
}
if (isSupportDirectReadFile(bgmFilePath)) {
tuiDataBGMFilePath.set(bgmFilePath);
return;
}
String bgmCacheFilePath = getBGMCacheFilePath(bgmFilePath);
if (TUIMultimediaFileUtil.isFileExists(bgmCacheFilePath)) {
tuiDataBGMFilePath.set(bgmCacheFilePath);
return;
}
CustomHandler handler = new CustomHandler(Looper.myLooper());
TUIMultimediaCommonThreadPool.getThreadExecutor().submit(() -> {
String filePath = copyFileToBGMCache(bgmFilePath);
handler.post(() -> {
tuiDataBGMFilePath.set(filePath);
bgmFilePath = filePath;
});
});
}
public void getBGMDuration(TUIMultimediaData<Long> tuiDataBGMDuration) {
if (duration != null) {
tuiDataBGMDuration.set(duration);
return;
}
CustomHandler handler = new CustomHandler(Looper.myLooper());
TUIMultimediaData<String> tuiDataBGMFilePath = new TUIMultimediaData<>("");
tuiDataBGMFilePath.observe(bgmFilePath -> {
TUIMultimediaCommonThreadPool.getThreadExecutor().submit(() -> {
duration = getAudioDuration(bgmFilePath);
handler.post(() -> tuiDataBGMDuration.set(duration));
});
});
getBGMFilePath(tuiDataBGMFilePath);
}
public boolean isSameBGM(BGMItem bgmItem) {
return bgmItem.bgmAuthorName.equals(bgmAuthorName)
&& bgmItem.bgmName.equals(bgmName)
&& bgmItem.bgmFilePath.equals(bgmAuthorName);
}
private boolean isSupportDirectReadFile(String path) {
if (path.startsWith(TUIMultimediaFileUtil.ASSET_FILE_PREFIX)) {
return false;
}
return true;
}
private String copyFileToBGMCache(String path) {
File f = new File(getBGMCacheFileDir());
boolean isSuccess = true;
if (!f.exists()) {
isSuccess = f.mkdirs();
}
if (!isSuccess) {
return path;
}
String cacheFilePath = getBGMCacheFilePath(path);
if (path.startsWith(TUIMultimediaFileUtil.ASSET_FILE_PREFIX)) {
isSuccess = TUIMultimediaFileUtil
.copyAssetsFile(path.substring(TUIMultimediaFileUtil.ASSET_FILE_PREFIX.length()), cacheFilePath);
return isSuccess ? cacheFilePath : path;
}
return path;
}
private String getBGMCacheFilePath(String srcBGMFilePath) {
return getBGMCacheFileDir() + Integer.toHexString(srcBGMFilePath.hashCode());
}
private String getBGMCacheFileDir() {
return TUIConfig.getDefaultAppDir() + BGM_CACHE_FILE_DIR;
}
private long getAudioDuration(String audioFilePath) {
MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
try {
metadataRetriever.setDataSource(audioFilePath);
String durationStr = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
return Long.parseLong(durationStr);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
metadataRetriever.release();
} catch (IOException e) {
e.printStackTrace();
}
}
return 0;
}
}
@@ -0,0 +1,71 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data;
import com.google.gson.annotations.SerializedName;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import java.util.LinkedList;
import java.util.List;
public class BGMType {
@SerializedName("bgm_type_name")
private String mTypeName;
@SerializedName("bgm_item_list")
private List<BGMItem> mBGMItemList;
public void addBGMItem(int itemIndex, BGMItem bgmItem) {
if (itemIndex < 0) {
return;
}
if (mBGMItemList == null) {
mBGMItemList = new LinkedList<>();
}
mBGMItemList.add(itemIndex, bgmItem);
}
public boolean isContainSameBGM(BGMItem bgmItem) {
if (bgmItem == null) {
return true;
}
if (mBGMItemList == null) {
return false;
}
for (BGMItem item : mBGMItemList) {
if (bgmItem.isSameBGM(item)) {
return true;
}
}
return false;
}
public BGMItem getBGMItem(int itemIndex) {
if (itemIndex < 0 || itemIndex >= mBGMItemList.size()) {
return null;
}
return mBGMItemList.get(itemIndex);
}
public List<BGMItem> getBGMItemList() {
return mBGMItemList;
}
public int getItemSize() {
return mBGMItemList.size();
}
public String getTypeName() {
if (mTypeName.startsWith(TUIMultimediaResourceUtils.STRING_RESOURCE_PREFIX)) {
return TUIMultimediaResourceUtils.getString(mTypeName);
}
return mTypeName;
}
public void setTypeName(String typeName) {
mTypeName = typeName;
}
}
@@ -0,0 +1,129 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushToolView.BrushToolListener;
import com.tencent.ugc.TXVideoEditConstants.TXRect;
public class BrushControlView {
public final TUIMultimediaData<Boolean> tuiDataIsDrawing = new TUIMultimediaData<>(false);
private final String TAG = BrushControlView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final RelativeLayout mBrushToolViewContainer;
private BrushDrawView mBrushDrawView;
private BrushToolView mBrushToolView;
private Boolean mEnableDraw = false;
private BrushMode mBrushMode = BrushMode.GRAFFITI;
public enum BrushMode {
GRAFFITI,
MOSAIC
}
public BrushControlView(Context context,
RelativeLayout brushToolViewContainer) {
mBrushToolViewContainer = brushToolViewContainer;
mContext = context;
initView();
}
public void enableDraw(boolean enable) {
LiteavLog.i(TAG, (enable ? " enable" : "disEnable") + " draw");
mEnableDraw = enable;
mBrushDrawView.enableDraw(enable);
}
public void showToolView(boolean show) {
mBrushToolViewContainer.setVisibility(show ? View.VISIBLE : View.GONE);
}
public void showDrawView(boolean show) {
mBrushDrawView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
public void setBrushMode(BrushMode brushMode) {
mBrushMode = brushMode;
mBrushDrawView.setBrushMode(mBrushMode);
mBrushToolView.setBrushMode(mBrushMode);
mBrushToolView.enableRedo(mBrushDrawView.canRedo());
mBrushToolView.enableUndo(mBrushDrawView.canUndo());
}
public void setMosaicImage(Bitmap bitmap) {
mBrushDrawView.setMosaicImage(bitmap);
}
public BrushMode getBrushMode() {
return mBrushMode;
}
public boolean isEnableDraw() {
return mEnableDraw;
}
public TUIMultimediaPasterInfo getPaster() {
Bitmap image = mBrushDrawView.getDrawingBitmap();
if (image == null) {
return null;
}
TUIMultimediaPasterInfo pasterInfo = new TUIMultimediaPasterInfo();
TXRect frame = new TXRect();
frame.width = image.getWidth();
frame.x = 0;
frame.y = 0;
pasterInfo.frame = frame;
pasterInfo.image = image;
pasterInfo.pasterType = PasterType.STATIC_PICTURE_PASTER;
return pasterInfo;
}
public void clean() {
mBrushDrawView.clean();
}
public View getBrushDrawView() {
return mBrushDrawView;
}
private void initView() {
mBrushToolView = new BrushToolView(mContext);
mBrushToolViewContainer.removeAllViews();
mBrushToolViewContainer.addView(mBrushToolView, new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mBrushDrawView = new BrushDrawView(mContext, mBrushToolView.mTuiDataSelectedTextColor,
tuiDataIsDrawing);
tuiDataIsDrawing.observe(isDrawing -> updateToolStatus());
mBrushToolView.setBrushToolListener(new BrushToolListener() {
@Override
public void redo() {
mBrushDrawView.redo();
updateToolStatus();
}
@Override
public void undo() {
mBrushDrawView.undo();
updateToolStatus();
}
});
}
private void updateToolStatus() {
mBrushToolView.enableRedo(mBrushDrawView.canRedo());
mBrushToolView.enableUndo(mBrushDrawView.canUndo());
}
}
@@ -0,0 +1,314 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.View;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.liteav.base.util.Size;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaGraphComputerUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView.BrushMode;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
public class BrushDrawView extends View {
private final String TAG = BrushDrawView.class + "_" + hashCode();
private static final int GRAFFITI_STROKE_WIDTH = 16;
private static final int MOSAIC_STROKE_WIDTH = 40;
private static final int MIN_MOVE_DST_PX = 10;
private BrushMode mBrushMode = BrushMode.GRAFFITI;
private final Stack<DrawPath> mGraffitiPaths = new Stack<>();
private final Stack<DrawPath> mEraseGraffitiPaths = new Stack<>();
private final Stack<DrawPath> mMosaicPaths = new Stack<>();
private final Stack<DrawPath> mEraseMosaicPaths = new Stack<>();
private final TUIMultimediaData<Integer> mTuiDataPaintColor;
private final TUIMultimediaData<Boolean> mTuiDataIsDrawing;
private Paint mPathPaint;
private Paint mMosaicImagePaint;
private Bitmap mMosaicImage;
private List<PointF> mCurrentPointList;
private boolean mEnableDraw = true;
private boolean mIsMotionActionMove = false;
private PointF mActionDownPoint;
public BrushDrawView(Context context, TUIMultimediaData<Integer> tuiDataPaintColor,
TUIMultimediaData<Boolean> tuiDataIsDrawing) {
super(context);
mTuiDataPaintColor = tuiDataPaintColor;
mTuiDataPaintColor.observe(color -> {
if (mPathPaint != null) {
mPathPaint.setColor(color);
}
});
mTuiDataIsDrawing = tuiDataIsDrawing;
setupPathPaint();
}
public void enableDraw(boolean enable) {
mEnableDraw = enable;
}
public void clean() {
mGraffitiPaths.clear();
mEraseGraffitiPaths.clear();
mMosaicPaths.clear();
mEraseMosaicPaths.clear();
if (mCurrentPointList != null) {
mCurrentPointList.clear();
}
}
public void setMosaicImage(Bitmap bitmap) {
if (bitmap == null) {
LiteavLog.i(TAG,"source picture is null");
return;
}
if (mMosaicImage != null) {
mMosaicImage.recycle();
}
int w = Math.max(Math.round(bitmap.getWidth() / 40f), 8);
int h = Math.max(Math.round(bitmap.getHeight() / 40f), 8);
mMosaicImage = Bitmap.createScaledBitmap(bitmap, w, h, false);
}
@Override
protected void onDraw(Canvas canvas) {
drawMosaic(canvas);
drawGraffiti(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LiteavLog.i(TAG, "onTouchEvent");
if (!mEnableDraw) {
performClick();
return false;
}
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mCurrentPointList = new LinkedList<>();
mCurrentPointList.add(new PointF(touchX, touchY));
mActionDownPoint = new PointF(touchX, touchY);
mIsMotionActionMove = false;
break;
case MotionEvent.ACTION_MOVE:
if (TUIMultimediaGraphComputerUtils.distance4PointF(mActionDownPoint, new PointF(touchX, touchY))
> MIN_MOVE_DST_PX) {
mTuiDataIsDrawing.set(true);
mIsMotionActionMove = true;
mCurrentPointList.add(new PointF(touchX, touchY));
}
break;
case MotionEvent.ACTION_UP:
if (mIsMotionActionMove) {
if (mBrushMode == BrushMode.MOSAIC) {
mMosaicPaths.add(new DrawPath(
mCurrentPointList, mTuiDataPaintColor.get(), new Size(getWidth(), getHeight())));
mEraseMosaicPaths.clear();
} else {
mGraffitiPaths.add(new DrawPath(
mCurrentPointList, mTuiDataPaintColor.get(), new Size(getWidth(), getHeight())));
mEraseGraffitiPaths.clear();
}
} else {
mTuiDataIsDrawing.set(!mTuiDataIsDrawing.get());
}
mCurrentPointList = null;
break;
}
invalidate();
return true;
}
@Override
public boolean performClick() {
return super.performClick();
}
public Bitmap getDrawingBitmap() {
if (mGraffitiPaths.empty() && mMosaicPaths.empty()) {
return null;
}
RectF rect = new RectF(0, 0, getWidth(), getHeight());
Bitmap canvasBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(canvasBitmap);
if (!mMosaicPaths.empty() && mMosaicImage != null) {
int layerCount = canvas.saveLayer(rect, null, Canvas.ALL_SAVE_FLAG);
drawPathList(canvas, mPathPaint, mMosaicPaths, MOSAIC_STROKE_WIDTH);
mMosaicImagePaint.setFilterBitmap(false);
canvas.drawBitmap(mMosaicImage, null, rect, mMosaicImagePaint);
canvas.restoreToCount(layerCount);
}
if (!mGraffitiPaths.empty()) {
drawPathList(canvas, mPathPaint, mGraffitiPaths, GRAFFITI_STROKE_WIDTH);
}
return canvasBitmap;
}
public void setBrushMode(BrushMode brushMode) {
mBrushMode = brushMode;
}
public void undo() {
if (mBrushMode == BrushMode.GRAFFITI && !mGraffitiPaths.empty()) {
mEraseGraffitiPaths.push(mGraffitiPaths.pop());
} else if (mBrushMode == BrushMode.MOSAIC && !mMosaicPaths.empty()) {
mEraseMosaicPaths.push(mMosaicPaths.pop());
} else {
return;
}
invalidate();
}
public void redo() {
if (mBrushMode == BrushMode.GRAFFITI && !mEraseGraffitiPaths.empty()) {
mGraffitiPaths.push(mEraseGraffitiPaths.pop());
} else if (mBrushMode == BrushMode.MOSAIC && !mEraseMosaicPaths.empty()) {
mMosaicPaths.push(mEraseMosaicPaths.pop());
} else {
return;
}
invalidate();
if (mEraseGraffitiPaths.empty()) {
return;
}
mGraffitiPaths.push(mEraseGraffitiPaths.pop());
invalidate();
}
public boolean canUndo() {
return mBrushMode == BrushMode.GRAFFITI ? !mGraffitiPaths.empty() : !mMosaicPaths.empty();
}
public boolean canRedo() {
return mBrushMode == BrushMode.GRAFFITI ? !mEraseGraffitiPaths.empty() : !mEraseMosaicPaths.empty();
}
private void setupPathPaint() {
if (mPathPaint != null) {
return;
}
mPathPaint = new Paint();
mPathPaint.setColor(mTuiDataPaintColor.get());
mPathPaint.setAntiAlias(true);
mPathPaint.setStrokeWidth(GRAFFITI_STROKE_WIDTH);
mPathPaint.setStyle(Paint.Style.STROKE);
mPathPaint.setStrokeJoin(Paint.Join.ROUND);
mPathPaint.setStrokeCap(Paint.Cap.ROUND);
}
private void setupMosaicImagePaint() {
if (mMosaicImagePaint != null) {
return;
}
mMosaicImagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMosaicImagePaint.setFilterBitmap(false);
mMosaicImagePaint.setStrokeWidth(MOSAIC_STROKE_WIDTH);
mMosaicImagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
}
private void drawGraffiti(Canvas canvas) {
if (mBrushMode != BrushMode.GRAFFITI && mGraffitiPaths.empty()) {
return;
}
RectF rect = new RectF(0, 0, getWidth(), getHeight());
int layerCount = canvas.saveLayer(rect, null, Canvas.ALL_SAVE_FLAG);
drawPathList(canvas, mPathPaint, mGraffitiPaths, GRAFFITI_STROKE_WIDTH);
if (mBrushMode == BrushMode.GRAFFITI) {
mPathPaint.setColor(mTuiDataPaintColor.get());
drawCurrentPoint(canvas, mPathPaint, GRAFFITI_STROKE_WIDTH);
}
canvas.restoreToCount(layerCount);
}
private void drawMosaic(Canvas canvas) {
if (mBrushMode != BrushMode.MOSAIC && mMosaicPaths.empty()) {
return;
}
setupMosaicImagePaint();
RectF rect = new RectF(0, 0, getWidth(), getHeight());
int layerCount = canvas.saveLayer(rect, null, Canvas.ALL_SAVE_FLAG);
if (mBrushMode == BrushMode.MOSAIC) {
drawCurrentPoint(canvas, mPathPaint, MOSAIC_STROKE_WIDTH);
}
drawPathList(canvas, mPathPaint, mMosaicPaths, MOSAIC_STROKE_WIDTH);
if (mMosaicImage != null) {
mMosaicImagePaint.setFilterBitmap(false);
canvas.drawBitmap(mMosaicImage, null, rect, mMosaicImagePaint);
}
canvas.restoreToCount(layerCount);
}
private void drawCurrentPoint(Canvas canvas, Paint paint, int strokeWidth) {
if (mCurrentPointList != null) {
paint.setStrokeWidth(strokeWidth);
canvas.drawPath(getPathFromPoints(mCurrentPointList, 1.0f), paint);
}
}
private void drawPathList(Canvas canvas, Paint paint, Stack<DrawPath> paths, int paintWidth) {
for (DrawPath drawPath : paths) {
double originSize = Math.sqrt(drawPath.originViewSize.width * drawPath.originViewSize.width +
drawPath.originViewSize.height * drawPath.originViewSize.height);
double size = Math.sqrt(getWidth() * getWidth() + getHeight() * getHeight());
float scale = (float) (size / originSize);
paint.setStrokeWidth((int) (paintWidth * scale));
paint.setColor(drawPath.color);
canvas.drawPath(getPathFromPoints(drawPath.pathPoints, scale), paint);
}
}
private Path getPathFromPoints(List<PointF> pathPoints, float scale) {
Path path = new Path();
for (int i = 0; i < pathPoints.size(); i++) {
if (i == 0) {
path.moveTo(pathPoints.get(i).x * scale, pathPoints.get(i).y * scale);
}
path.lineTo(pathPoints.get(i).x * scale, pathPoints.get(i).y * scale);
}
return path;
}
private static class DrawPath {
private final List<PointF> pathPoints;
int color;
Size originViewSize;
public DrawPath(List<PointF> pathPoints, int color, Size size) {
this.pathPoints = pathPoints;
this.color = color;
this.originViewSize = size;
}
}
}
@@ -0,0 +1,90 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.widget.RelativeLayout;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIHorizontalScrollView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView.BrushMode;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster.SubtitleColorItemAdapter;
@SuppressLint("ViewConstructor")
public class BrushToolView extends RelativeLayout {
public final TUIMultimediaData<Integer> mTuiDataSelectedTextColor = new TUIMultimediaData<>(Color.RED);
private final String TAG = BrushToolView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private BrushToolListener mListener;
private BrushMode mBrushMode;
private TUIHorizontalScrollView mColorSelectScrollItemView;
public BrushToolView(Context context) {
super(context);
mContext = context;
}
public void setBrushToolListener(BrushToolListener listener) {
mListener = listener;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
removeAllViews();
super.onDetachedFromWindow();
}
public void setBrushMode(BrushMode brushMode) {
mBrushMode = brushMode;
mColorSelectScrollItemView.setVisibility(brushMode != BrushMode.MOSAIC ? VISIBLE : GONE);
}
public void enableRedo(boolean enable) {
findViewById(R.id.graffiti_operation_redo).setEnabled(enable);
}
public void enableUndo(boolean enable) {
findViewById(R.id.graffiti_operation_undo).setEnabled(enable);
}
private void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_graffiti_operation_view,
this, true);
mColorSelectScrollItemView = findViewById(R.id.graffiti_paster_color_select_view);
mColorSelectScrollItemView.setAdapter(new SubtitleColorItemAdapter(mContext, mTuiDataSelectedTextColor));
if (mBrushMode == BrushMode.MOSAIC) {
mColorSelectScrollItemView.setVisibility(GONE);
}
findViewById(R.id.graffiti_operation_redo).setEnabled(false);
findViewById(R.id.graffiti_operation_redo).setOnClickListener((v) -> {
if (mListener != null) {
mListener.redo();
}
});
findViewById(R.id.graffiti_operation_undo).setEnabled(false);
findViewById(R.id.graffiti_operation_undo).setOnClickListener((v) -> {
if (mListener != null) {
mListener.undo();
}
});
}
public interface BrushToolListener {
void redo();
void undo();
}
}
@@ -0,0 +1,219 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaTransformLayout;
public class PictureCropControlView extends RelativeLayout implements PictureCropView.CropListener {
private final String TAG = PictureCropControlView.class + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaTransformLayout mPreviewLayout;
private int mRotation = 0;
private PictureCropView mPictureCropView;
private PictureCropControlViewListener mCropControlViewListener;
private Button mRestoreCropButton;
public interface PictureCropControlViewListener {
void onCancelCrop();
void onConfirmCrop(RectF rectF, int rotation);
void onRotation(int rotation);
}
public PictureCropControlView(Context context, TUIMultimediaTransformLayout previewLayout) {
super(context);
mContext = context;
mPreviewLayout = previewLayout;
}
public void setCropMainViewListener(PictureCropControlViewListener cropMainViewListener) {
mCropControlViewListener = cropMainViewListener;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
}
private void initView() {
View mRootView = LayoutInflater.from(mContext)
.inflate(R.layout.multimedia_plugin_edit_picture_crop_view, this);
mPictureCropView = new PictureCropView(mContext, mPreviewLayout.getInitContentLayout());
mPictureCropView.setCropListener(this);
((RelativeLayout) mRootView).addView(mPictureCropView, 0);
mRootView.setOnTouchListener((view, motionEvent) -> false);
mRestoreCropButton = mRootView.findViewById(R.id.crop_restore);
mRestoreCropButton.setOnClickListener(view -> onRestoreCrop());
mRestoreCropButton.setEnabled(false);
mRootView.findViewById(R.id.crop_ok).setOnClickListener(view -> onConfirmCrop());
mRootView.findViewById(R.id.crop_cancel).setOnClickListener(view -> onCancelCrop());
mRootView.findViewById(R.id.crop_rotation).setOnClickListener(view -> onRotation());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (!changed) {
return;
}
updateCropMarkViewLayoutParams();
}
private void updateCropMarkViewLayoutParams() {
if (mPictureCropView == null) {
return;
}
RelativeLayout.LayoutParams layoutParams = (LayoutParams) mPictureCropView.getLayoutParams();
layoutParams.topMargin = 0;
layoutParams.leftMargin = 0;
layoutParams.height = getHeight();
layoutParams.width = getWidth();
LiteavLog.i(TAG, " width = " + layoutParams.width + " height = " + layoutParams.height);
mPictureCropView.setLayoutParams(layoutParams);
}
@Override
public void onCrop(float scaleCenterX, float scaleCenterY, float scale, int moveX, int moveY) {
if (mPreviewLayout != null) {
mPreviewLayout.setLimitRect(mPictureCropView.getCropRect());
mPreviewLayout.scaleContent(scale, new PointF(scaleCenterX, scaleCenterY));
mPreviewLayout.moveContent(moveX, moveY);
}
}
@Override
public void onTouch() {
mRestoreCropButton.setEnabled(isNeedCropOrRotation());
}
private void onRestoreCrop() {
mPreviewLayout.reset();
mPictureCropView.resetCropRect();
if (mCropControlViewListener != null && mRotation != 0) {
mCropControlViewListener.onRotation(0);
}
mRotation = 0;
mRestoreCropButton.setEnabled(false);
}
private void onRotation() {
Rect corpRectInMarkView = mPictureCropView.getCropRect();
mPreviewLayout.rotation90Content(new Point(corpRectInMarkView.centerX(), corpRectInMarkView.centerY()));
mPictureCropView.rotation90();
mRotation = (mRotation + 90 + 360) % 360;
if (mCropControlViewListener != null) {
mCropControlViewListener.onRotation(mRotation);
}
mRestoreCropButton.setEnabled(isNeedCropOrRotation());
}
private void onCancelCrop() {
if (mCropControlViewListener != null) {
mCropControlViewListener.onCancelCrop();
}
}
private void onConfirmCrop() {
LiteavLog.i(TAG, "on confirm crop.");
if (!isNeedCropOrRotation()) {
LiteavLog.i(TAG,"do not need crop or rotation.");
onCancelCrop();
}
RectF normalizedCropRect = getNormalizedCropRect();
if (mCropControlViewListener != null) {
mCropControlViewListener.onConfirmCrop(normalizedCropRect, mRotation);
}
}
// private Size getCropPictureSize(RectF normalizedCropRect) {
// Size srcBitmapSize = TUIMultimediaPictureEditorCore.getInstance(mContext).getBitmapSize();
// int outSizeWidth = Math.min(1080, srcBitmapSize.width);
// int outSizeHeight = (int) (outSizeWidth * normalizedCropRect.height() / normalizedCropRect.width());
// if (mRotation == 90 || mRotation == 270) {
// return new Size(outSizeHeight, outSizeWidth);
// } else {
// return new Size(outSizeWidth, outSizeHeight);
// }
// }
private RectF getNormalizedCropRect() {
Rect corpRect = mPictureCropView.getCropRect();
Rect previewRect = mPreviewLayout.getContentRect();
Rect cropRectInMedia = new Rect(corpRect.left - previewRect.left,
corpRect.top - previewRect.top,
corpRect.right - previewRect.left,
corpRect.bottom - previewRect.top);
int mediaWidth = previewRect.right - previewRect.left;
int mediaHeight = previewRect.bottom - previewRect.top;
RectF cropRectF = new RectF();
cropRectF.left = cropRectInMedia.left * 1.0f / mediaWidth;
cropRectF.right = cropRectInMedia.right * 1.0f / mediaWidth;
cropRectF.top = cropRectInMedia.top * 1.0f / mediaHeight;
cropRectF.bottom = cropRectInMedia.bottom * 1.0f / mediaHeight;
if (mRotation == 90) {
float left = cropRectF.left;
float right = cropRectF.right;
cropRectF.left = cropRectF.top;
cropRectF.right = cropRectF.bottom;
cropRectF.top = 1 - right;
cropRectF.bottom = 1 - left;
} else if (mRotation == 180) {
cropRectF.top = 1 - cropRectF.bottom;
cropRectF.bottom = 1 - cropRectF.top;
} else if (mRotation == 270) {
float left = cropRectF.left;
float right = cropRectF.right;
cropRectF.left = 1 - cropRectF.bottom;
cropRectF.right = 1 - cropRectF.top;
cropRectF.top = left;
cropRectF.bottom = right;
}
return cropRectF;
}
private boolean isNeedCropOrRotation() {
if ((mRotation + 360) % 360 != 0) {
return true;
}
Rect corpRect = mPictureCropView.getCropRect();
Rect previewRect = mPreviewLayout.getContentRect();
return !isApproximateRect(corpRect, previewRect);
}
private boolean isApproximateRect(Rect rect1, Rect rect2) {
return Math.abs(rect1.left - rect2.left) < 2 && Math.abs(rect1.right - rect2.right) < 2 &&
Math.abs(rect1.top - rect2.top) < 2 && Math.abs(rect1.bottom - rect2.bottom) < 2;
}
}
@@ -0,0 +1,332 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
import com.tencent.liteav.base.ThreadUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
public class PictureCropView extends View {
private final static int RECT_LINE_WIDTH = 3;
private final static int GRID_LINE_WIDTH = 1;
private final static int RECT_CORNER_LINE_WIDTH = 10;
private final static int CORNER_LENGTH = 50;
private final static int TOUCH_DISTANCE = 50;
private final static int CANCEL_BACKGROUND_TRANSPARENT_DELAY_TIME = 2000;
private final static int CANCEL_DRAW_GRID_DELAY_TIME = 4000;
private final static int LIMIT_LEFT_DP = 30;
private final static int LIMIT_BOTTOM_DP = 100;
private final static int LIMIT_TOP_DP = 15;
private final Rect mOriginPreviewRect;
private final Rect mMaxCropRect = new Rect();
private final Rect mCropRect = new Rect();
boolean mIsMoveLeft = false;
boolean mIsMoveRight = false;
boolean mIsMoveTop = false;
boolean mIsMoveBottom = false;
private Paint mCropRectDrawPaint;
private Paint mBackGroundDrawPaint;
private Path mCropRectPath;
private CropListener mCropListener;
private Boolean mIsDrawGrid = false;
Runnable mCancelBackGroundTransparentRunnable = () -> {
mBackGroundDrawPaint.setColor(Color.argb(255, 0, 0, 0));
invalidate();
};
Runnable mCancelDrawGridRunnable = () -> {
mIsDrawGrid = false;
invalidate();
};
public interface CropListener {
void onTouch();
void onCrop(float scaleCenterX, float scaleCenterY, float scale, int moveX, int moveY);
}
public PictureCropView(Context context, Rect originPreviewRect) {
super(context);
mOriginPreviewRect = originPreviewRect;
setupDrawing();
}
public void setCropListener(CropListener cropListener) {
mCropListener = cropListener;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (!changed) {
return;
}
initMaxCropRect();
initCropRect();
invalidate();
}
public void resetCropRect() {
initCropRect();
delayCancelBackGroundTransparent();
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackGround(canvas);
drawCropRect(canvas);
drawCorner(canvas);
if (mIsDrawGrid) {
drawGrid(canvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mCropListener != null) {
mCropListener.onTouch();
}
float touchX = event.getX();
float touchY = event.getY();
boolean isAdjustCropRect = true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsMoveLeft = Math.abs(touchX - mCropRect.left) < TOUCH_DISTANCE;
mIsMoveRight = Math.abs(touchX - mCropRect.right) < TOUCH_DISTANCE;
mIsMoveTop = Math.abs(touchY - mCropRect.top) < TOUCH_DISTANCE;
mIsMoveBottom = Math.abs(touchY - mCropRect.bottom) < TOUCH_DISTANCE;
setBackGroundTransparent();
isAdjustCropRect = mIsMoveBottom || mIsMoveTop || mIsMoveLeft || mIsMoveRight;
if (!isAdjustCropRect) {
delayCancelBackGroundTransparent();
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsMoveLeft && touchX > mMaxCropRect.left && touchX < mCropRect.right) {
mCropRect.left = (int) touchX;
}
if (mIsMoveRight && touchX > mCropRect.left && touchX < mMaxCropRect.right) {
mCropRect.right = (int) touchX;
}
if (mIsMoveTop && touchY > mMaxCropRect.top && touchY < mCropRect.bottom) {
mCropRect.top = (int) touchY;
}
if (mIsMoveBottom && touchY > mCropRect.top && touchY < mMaxCropRect.bottom) {
mCropRect.bottom = (int) touchY;
}
break;
case MotionEvent.ACTION_UP:
delayCancelBackGroundTransparent();
adjustCropRect();
break;
}
invalidate();
return isAdjustCropRect;
}
@Override
public boolean performClick() {
return super.performClick();
}
public Rect getCropRect() {
return new Rect(mCropRect);
}
public void rotation90() {
int centerX = mCropRect.centerX();
int centerY = mCropRect.centerY();
int width = mCropRect.width();
int height = mCropRect.height();
mCropRect.top = centerY - width / 2;
mCropRect.bottom = mCropRect.top + width;
mCropRect.left = centerX - height / 2;
mCropRect.right = mCropRect.left + height;
adjustCropRect();
invalidate();
}
private void setupDrawing() {
mCropRectPath = new Path();
mCropRectDrawPaint = new Paint();
mCropRectDrawPaint.setColor(Color.WHITE);
mCropRectDrawPaint.setAntiAlias(true);
mCropRectDrawPaint.setStyle(Paint.Style.STROKE);
mCropRectDrawPaint.setStrokeJoin(Paint.Join.ROUND);
mCropRectDrawPaint.setStrokeCap(Paint.Cap.ROUND);
mBackGroundDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackGroundDrawPaint.setColor(Color.argb(255, 0, 0, 0));
mBackGroundDrawPaint.setStyle(Paint.Style.FILL);
}
private void drawGrid(Canvas canvas) {
mCropRectDrawPaint.setStrokeWidth(GRID_LINE_WIDTH);
mCropRectPath.reset();
mCropRectPath.moveTo((float) (mCropRect.left + mCropRect.width() / 3.0), mCropRect.top);
mCropRectPath.lineTo((float) (mCropRect.left + mCropRect.width() / 3.0), mCropRect.bottom);
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
mCropRectPath.reset();
mCropRectPath.moveTo((float) (mCropRect.left + mCropRect.width() * 2 / 3.0), mCropRect.top);
mCropRectPath.lineTo((float) (mCropRect.left + mCropRect.width() * 2 / 3.0), mCropRect.bottom);
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
mCropRectPath.reset();
mCropRectPath.moveTo(mCropRect.left, (float)(mCropRect.top + mCropRect.height() / 3.0));
mCropRectPath.lineTo(mCropRect.right, (float)(mCropRect.top + mCropRect.height() / 3.0));
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
mCropRectPath.reset();
mCropRectPath.moveTo(mCropRect.left, (float)(mCropRect.top + mCropRect.height() * 2 / 3.0));
mCropRectPath.lineTo(mCropRect.right, (float)(mCropRect.top + mCropRect.height() * 2 / 3.0));
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
}
private void drawBackGround(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), mCropRect.top, mBackGroundDrawPaint);
canvas.drawRect(0, mCropRect.bottom, getWidth(), getHeight(), mBackGroundDrawPaint);
canvas.drawRect(0, mCropRect.top, mCropRect.left, mCropRect.bottom, mBackGroundDrawPaint);
canvas.drawRect(mCropRect.right, mCropRect.top, getWidth(), mCropRect.bottom, mBackGroundDrawPaint);
}
private void drawCropRect(Canvas canvas) {
mCropRectDrawPaint.setStrokeWidth(RECT_LINE_WIDTH);
mCropRectPath.reset();
mCropRectPath.moveTo(mCropRect.left, mCropRect.top);
mCropRectPath.lineTo(mCropRect.right, mCropRect.top);
mCropRectPath.lineTo(mCropRect.right, mCropRect.bottom);
mCropRectPath.lineTo(mCropRect.left, mCropRect.bottom);
mCropRectPath.lineTo(mCropRect.left, mCropRect.top);
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
}
private void drawCorner(Canvas canvas) {
mCropRectDrawPaint.setStrokeWidth(RECT_CORNER_LINE_WIDTH);
mCropRectPath.reset();
mCropRectPath.moveTo(mCropRect.left, mCropRect.top + CORNER_LENGTH);
mCropRectPath.lineTo(mCropRect.left, mCropRect.top);
mCropRectPath.lineTo(mCropRect.left + CORNER_LENGTH, mCropRect.top);
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
mCropRectPath.reset();
mCropRectPath.moveTo(mCropRect.right, mCropRect.top + CORNER_LENGTH);
mCropRectPath.lineTo(mCropRect.right, mCropRect.top);
mCropRectPath.lineTo(mCropRect.right - CORNER_LENGTH, mCropRect.top);
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
mCropRectPath.reset();
mCropRectPath.moveTo(mCropRect.right, mCropRect.bottom - CORNER_LENGTH);
mCropRectPath.lineTo(mCropRect.right, mCropRect.bottom);
mCropRectPath.lineTo(mCropRect.right - CORNER_LENGTH, mCropRect.bottom);
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
mCropRectPath.moveTo(mCropRect.left, mCropRect.bottom - CORNER_LENGTH);
mCropRectPath.lineTo(mCropRect.left, mCropRect.bottom);
mCropRectPath.lineTo(mCropRect.left + CORNER_LENGTH, mCropRect.bottom);
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
}
private void delayCancelBackGroundTransparent() {
ThreadUtils.getUiThreadHandler().postDelayed(mCancelBackGroundTransparentRunnable,
CANCEL_BACKGROUND_TRANSPARENT_DELAY_TIME);
ThreadUtils.getUiThreadHandler().postDelayed(mCancelDrawGridRunnable, CANCEL_DRAW_GRID_DELAY_TIME);
}
private void setBackGroundTransparent() {
mBackGroundDrawPaint.setColor(Color.argb(0, 0, 0, 0));
mIsDrawGrid = true;
ThreadUtils.getUiThreadHandler().removeCallbacks(mCancelBackGroundTransparentRunnable);
ThreadUtils.getUiThreadHandler().removeCallbacks(mCancelDrawGridRunnable);
}
private void initMaxCropRect() {
mMaxCropRect.left = TUIMultimediaResourceUtils.dip2px(getContext(), LIMIT_LEFT_DP);
mMaxCropRect.right = getWidth() - mMaxCropRect.left;
mMaxCropRect.top = TUIMultimediaResourceUtils.dip2px(getContext(), LIMIT_TOP_DP);
mMaxCropRect.bottom = getHeight() - TUIMultimediaResourceUtils.dip2px(getContext(), LIMIT_BOTTOM_DP);
}
private void initCropRect() {
if (mOriginPreviewRect == null) {
return;
}
float previewAspect = mOriginPreviewRect.width() * 1.0f / mOriginPreviewRect.height();
float maxCropAspect = mMaxCropRect.width() * 1.0f / mMaxCropRect.height();
if (previewAspect > maxCropAspect) {
mCropRect.left = mMaxCropRect.left;
mCropRect.right = mMaxCropRect.right;
int cropHeight = (int) (mCropRect.width() / previewAspect);
mCropRect.top = (mMaxCropRect.bottom - cropHeight) / 2;
mCropRect.bottom = mCropRect.top + cropHeight;
} else {
mCropRect.top = mMaxCropRect.top;
mCropRect.bottom = mMaxCropRect.bottom;
int cropWidth = (int) (mCropRect.height() * maxCropAspect);
mCropRect.left = (getWidth() - cropWidth) / 2;
mCropRect.right = mCropRect.left + cropWidth;
}
float scale = mCropRect.width() * 1.0f / mOriginPreviewRect.width();
int mediaAreaLeft = (int) (getWidth() / 2.0 - mOriginPreviewRect.width() / 2.0 * scale);
int mediaAreaTop = (int) (getHeight() / 2.0 - mOriginPreviewRect.height() / 2.0 * scale);
if (mCropListener != null) {
mCropListener.onCrop(getWidth() / 2.0f, getHeight() / 2.0f, scale,
mediaAreaLeft - mCropRect.left, mediaAreaTop - mCropRect.top);
}
}
private void adjustCropRect() {
float maxCropAreaAspect = mMaxCropRect.width() * 1.0f / mMaxCropRect.height();
float cropAspect = mCropRect.width() * 1.0f / mCropRect.height();
int sourceLeft = mCropRect.left;
int sourceTop = mCropRect.top;
int sourceRight = mCropRect.right;
if (cropAspect > maxCropAreaAspect) {
mCropRect.left = mMaxCropRect.left;
mCropRect.right = mMaxCropRect.right;
int cropHeight = (int) (mCropRect.width() / cropAspect);
mCropRect.top = (mMaxCropRect.bottom - cropHeight) / 2;
mCropRect.bottom = mCropRect.top + cropHeight;
} else {
mCropRect.top = mMaxCropRect.top;
mCropRect.bottom = mMaxCropRect.bottom;
int cropWidth = (int) (mCropRect.height() * cropAspect);
mCropRect.left = (getWidth() - cropWidth) / 2;
mCropRect.right = mCropRect.left + cropWidth;
}
float scale = mCropRect.width() * 1.0f / (sourceRight - sourceLeft);
int moveX = sourceLeft - mCropRect.left;
int moveY = sourceTop - mCropRect.top;
if (mCropListener != null) {
mCropListener.onCrop(sourceLeft, sourceTop, scale, moveX, moveY);
}
}
}
@@ -0,0 +1,613 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.liteav.base.util.Size;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaGraphComputerUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class FloatLayerView extends View {
private final String TAG = FloatLayerView.class.getSimpleName() + "_" + hashCode();
private static final int LEFT_TOP = 0;
private static final int RIGHT_TOP = 1;
private static final int RIGHT_BOTTOM = 2;
private static final int LEFT_BOTTOM = 3;
private static final int MIN_DISTANCE_TO_EDGE_DP = 10;
private static final float MAX_SCALE = 10.0f;
private static final float MIN_SCALE = 0.02f;
private static final float DEFAULT_SCALE = 1.0f;
private static final float DEFAULT_DEGREE = 0;
private static final int DEFAULT_FRAME_PADDING = 3;
private static final int DEFAULT_FRAME_WIDTH = 1;
private static final int DEFAULT_FRAME_COLOR = Color.WHITE;
private static final int DEFAULT_DELETE_DRAWABLE_ID = R.drawable.multimedia_plugin_edit_floatlayer_delete;
private static final int DEFAULT_EDIT_DRAWABLE_ID = R.drawable.multimedia_plugin_edit_floatlayer_edit;
private static final int DEFAULT_ROTATION_DRAWABLE_ID = R.drawable.multimedia_plugin_edit_floatlayer_rotation;
private static final int DRAWABLE_SIZE_DP = 25;
private final PointF mCenterPoint = new PointF();
private final PointF mMoveSize = new PointF();
private final Matrix mMatrix = new Matrix();
private final int mControlLocation = RIGHT_BOTTOM;
private final int mEditLocation = RIGHT_TOP;
private final int mDeleteLocation = LEFT_TOP;
private final Path mPath = new Path();
@NonNull
private final PointF mPreMovePointF = new PointF();
@NonNull
private final PointF mCurMovePointF = new PointF();
protected TUIMultimediaData<Status> mTuiDataStatus = new TUIMultimediaData<>(Status.STATUS_INIT);
private boolean mShowDelete = true;
private boolean mShowEdit = true;
private PasterType mPasterType;
private boolean isMeasured = false;
private Bitmap mBitmap;
private int mViewWidth, mViewHeight;
private float mDegree = DEFAULT_DEGREE;
private float mScale = DEFAULT_SCALE;
private int mViewPaddingLeft;
private int mViewPaddingTop;
private Point mLTPoint;
private Point mRTPoint;
private Point mRBPoint;
private Point mLBPoint;
private Point mEditPoint = new Point();
private Point mDeletePoint = new Point();
private Point mRotatePoint = new Point();
private Drawable mEditDrawable;
private Drawable mDeleteDrawable;
private Drawable mRotateDrawable;
private Paint mPaint;
private int mFramePadding = DEFAULT_FRAME_PADDING;
private int mFrameColor = DEFAULT_FRAME_COLOR;
private int mFrameWidth = DEFAULT_FRAME_WIDTH;
private int mOffsetX;
private int mOffsetY;
private int mImageLeft;
private int mImageTop;
private int mImageWidth;
private int mOperationIconSize = DRAWABLE_SIZE_DP;
private Zoom touchDownZoom = Zoom.IMAGE_ZOOM;
private boolean mCanTouch = true;
private Size mOriginParentSize;
private Size mLastParentPosition;
float mViewScaleX = 1.0f;
float mViewScaleY = 1.0f;
int mRotation = 0;
public FloatLayerView(Context context) {
super(context);
init();
}
public void showDelete(boolean showDelete) {
mShowDelete = showDelete;
}
public void showEdit(boolean showEdit) {
mShowEdit = showEdit;
}
public void setCanTouch(boolean touch) {
mCanTouch = touch;
}
public void setImageScale(float scale) {
LiteavLog.i(TAG, "set image scale. scale = " + scale);
if (this.mScale != scale) {
this.mScale = scale;
transformDraw();
}
}
public void unSelect() {
LiteavLog.i(TAG, "unSelect");
if (mTuiDataStatus.get() == Status.STATUS_SELECTED) {
mTuiDataStatus.set(Status.STATUS_INIT);
}
invalidate();
}
public TUIMultimediaData<Status> tuiDataGetStatus() {
return mTuiDataStatus;
}
public void setImageBitmap(@Nullable Bitmap bitmap) {
if (mBitmap != null && bitmap != null && !mBitmap.equals(bitmap)) {
mBitmap.recycle();
}
mBitmap = bitmap;
transformDraw();
}
public PasterType getPasterType() {
return mPasterType;
}
public void setPasterType(PasterType type) {
mPasterType = type;
}
public Bitmap getRotateBitmap() {
if (mBitmap == null) {
LiteavLog.e(TAG, "get rotate bitmap is null");
return null;
}
return Bitmap.createBitmap(this.mBitmap, 0, 0, this.mBitmap.getWidth(),
this.mBitmap.getHeight(), mMatrix, true);
}
public int getImageLeft() {
return this.mImageLeft;
}
public int getImageTop() {
return this.mImageTop;
}
public int getImageWidth() {
return this.mImageWidth;
}
public void setPadding(int padding) {
mFramePadding = padding;
}
public void setBorderWidth(int borderWidth) {
mFrameWidth = borderWidth;
}
public void setBorderColor(int color) {
mFrameColor = getResources().getColor(color);
}
public void setEditIconResource(int resId) {
mEditDrawable = getResources().getDrawable(resId);
}
public void setRotateIconResource(int resId) {
mRotateDrawable = getResources().getDrawable(resId);
}
private void init() {
mOperationIconSize = TUIMultimediaResourceUtils.dip2px(getContext(), DRAWABLE_SIZE_DP);
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
mFramePadding = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_PADDING, metrics);
mFrameWidth = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_WIDTH, metrics);
mFrameColor = DEFAULT_FRAME_COLOR;
mScale = DEFAULT_SCALE;
mDegree = DEFAULT_DEGREE;
mRotateDrawable = TUIMultimediaResourceUtils.getDrawable(DEFAULT_ROTATION_DRAWABLE_ID);
mEditDrawable = TUIMultimediaResourceUtils.getDrawable(DEFAULT_EDIT_DRAWABLE_ID);
mDeleteDrawable = TUIMultimediaResourceUtils.getDrawable(DEFAULT_DELETE_DRAWABLE_ID);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mFrameColor);
mPaint.setStrokeWidth(mFrameWidth);
mPaint.setStyle(Paint.Style.STROKE);
transformDraw();
}
public void onParentSizeChange(Size parentSize) {
if (mOriginParentSize != null) {
if (mOriginParentSize.getWidth() != 0) {
mViewScaleX = parentSize.width * 1.0f / mOriginParentSize.getWidth();
}
if (mOriginParentSize.getHeight() != 0) {
mViewScaleY = parentSize.height * 1.0f / mOriginParentSize.getHeight();
}
}
float mViewScaleAddX = 1.0f;
float mViewScaleAddY = 1.0f;
if (mLastParentPosition != null) {
mViewScaleAddX = parentSize.width * 1.0f / mLastParentPosition.width;
mViewScaleAddY = parentSize.height * 1.0f / mLastParentPosition.height;
}
mLastParentPosition = new Size(parentSize.width, parentSize.height);
mCenterPoint.x = mCenterPoint.x * mViewScaleAddX;
mCenterPoint.y = mCenterPoint.y * mViewScaleAddY;
transformDraw();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
ViewGroup viewGroup = (ViewGroup) getParent();
if (null != viewGroup) {
int parentWidth = viewGroup.getWidth();
int parentHeight = viewGroup.getHeight();
mCenterPoint.set(parentWidth * 1.0f / 2, parentHeight * 1.0f / 2);
if (mOriginParentSize == null) {
mOriginParentSize = new Size(parentWidth, parentHeight);
mLastParentPosition = mOriginParentSize;
LiteavLog.i(TAG, "viewGroup = " + viewGroup);
}
}
isMeasured = true;
adjustLayout();
}
}
private void adjustLayout() {
int minDistanceToEdge = TUIMultimediaResourceUtils.dip2px(getContext(), MIN_DISTANCE_TO_EDGE_DP);
int halfViewWidth = (int) (mViewWidth * 1.0f / 2);
int halfViewHeight = (int) (mViewHeight * 1.0f / 2);
if (mCenterPoint.x < minDistanceToEdge - halfViewWidth) {
mCenterPoint.x = minDistanceToEdge - halfViewWidth;
}
if (mCenterPoint.y < minDistanceToEdge - halfViewHeight) {
mCenterPoint.y = minDistanceToEdge - halfViewHeight;
}
ViewGroup mViewGroup = (ViewGroup) getParent();
if (null != mViewGroup) {
int parentWidth = mViewGroup.getWidth();
if (mCenterPoint.x > parentWidth - minDistanceToEdge + halfViewWidth) {
mCenterPoint.x = parentWidth - minDistanceToEdge + halfViewWidth;
}
int parentHeight = mViewGroup.getHeight();
if (mCenterPoint.y > parentHeight - minDistanceToEdge + halfViewHeight) {
mCenterPoint.y = parentHeight - minDistanceToEdge + halfViewHeight;
}
}
int actualWidth = mViewWidth + mOperationIconSize;
int actualHeight = mViewHeight + mOperationIconSize;
int newPaddingLeft = (int) (mCenterPoint.x - actualWidth / 2.0);
int newPaddingTop = (int) (mCenterPoint.y - actualHeight / 2.0);
if (mViewPaddingLeft != newPaddingLeft || mViewPaddingTop != newPaddingTop) {
mViewPaddingLeft = newPaddingLeft;
mViewPaddingTop = newPaddingTop;
}
layout(newPaddingLeft, newPaddingTop, newPaddingLeft + actualWidth,
newPaddingTop + actualHeight);
mImageLeft = newPaddingLeft + mOperationIconSize / 2;
mImageTop = newPaddingTop + mOperationIconSize / 2;
mImageWidth = mViewWidth;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
if (mBitmap == null) {
return;
}
transformDraw();
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
if (mTuiDataStatus.get() == Status.STATUS_SELECTED) {
drawOuterFrame(canvas);
drawOperationIcon(canvas);
}
adjustLayout();
}
private void drawOuterFrame(@NonNull Canvas canvas) {
mPath.reset();
mPath.moveTo(mLTPoint.x, mLTPoint.y);
mPath.lineTo(mRTPoint.x, mRTPoint.y);
mPath.lineTo(mRBPoint.x, mRBPoint.y);
mPath.lineTo(mLBPoint.x, mLBPoint.y);
mPath.lineTo(mLTPoint.x, mLTPoint.y);
canvas.drawPath(mPath, mPaint);
}
private void drawOperationIcon(@NonNull Canvas canvas) {
if (mRotateDrawable != null) {
mRotateDrawable.setBounds(mRotatePoint.x - mOperationIconSize / 2,
mRotatePoint.y - mOperationIconSize / 2,
mRotatePoint.x + mOperationIconSize / 2,
mRotatePoint.y + mOperationIconSize / 2);
canvas.save();
canvas.rotate(-45.0f, mRotatePoint.x, mRotatePoint.y);
mRotateDrawable.draw(canvas);
canvas.restore();
}
if (mEditDrawable != null && mShowEdit) {
mEditDrawable.setBounds(mEditPoint.x - mOperationIconSize / 2,
mEditPoint.y - mOperationIconSize / 2,
mEditPoint.x + mOperationIconSize / 2,
mEditPoint.y + mOperationIconSize / 2);
mEditDrawable.draw(canvas);
}
if (mDeleteDrawable != null && mShowDelete) {
mDeleteDrawable.setBounds(mDeletePoint.x - mOperationIconSize / 2,
mDeletePoint.y - mOperationIconSize / 2,
mDeletePoint.x + mOperationIconSize / 2,
mDeletePoint.y + mOperationIconSize / 2);
mDeleteDrawable.draw(canvas);
}
}
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (!mCanTouch) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPreMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
touchDownZoom = judgeZoom(event.getX(), event.getY());
if (touchDownZoom != Zoom.ROTATE_ZOOM) {
mMoveSize.x = 0;
mMoveSize.y = 0;
}
break;
case MotionEvent.ACTION_UP:
if (touchDownZoom == Zoom.ROTATE_ZOOM) {
break;
} else if (Math.abs(mMoveSize.x) > 3 || Math.abs(mMoveSize.y) > 3) {
break;
}
Zoom touchUpZoom = judgeZoom(event.getX(), event.getY());
switch (touchUpZoom) {
case EDIT_ZOOM:
mTuiDataStatus.set(Status.STATUS_EDIT);
break;
case DELETE_ZOOM:
mTuiDataStatus.set(Status.STATUS_DELETE);
break;
case IMAGE_ZOOM:
if (mTuiDataStatus.get() == Status.STATUS_INIT) {
mTuiDataStatus.set(Status.STATUS_SELECTED);
break;
}
if (mShowEdit && mTuiDataStatus.get() == Status.STATUS_SELECTED) {
mTuiDataStatus.set(Status.STATUS_EDIT);
break;
}
}
invalidate();
case MotionEvent.ACTION_MOVE:
mCurMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
if (touchDownZoom == Zoom.ROTATE_ZOOM) {
rotateImage();
} else {
moveImage();
}
mPreMovePointF.set(mCurMovePointF);
break;
}
return true;
}
private void rotateImage() {
if (mBitmap == null) {
return;
}
int halfBitmapWidth = mBitmap.getWidth() / 2;
int halfBitmapHeight = mBitmap.getHeight() / 2;
float bitmapToCenterDistance = TUIMultimediaGraphComputerUtils.distance4PointF(new PointF(0, 0),
new PointF(halfBitmapWidth * mViewScaleX, halfBitmapHeight * mViewScaleY));
float moveToCenterDistance = TUIMultimediaGraphComputerUtils.distance4PointF(mCenterPoint, mCurMovePointF);
float scale = moveToCenterDistance / bitmapToCenterDistance;
if (scale <= MIN_SCALE) {
scale = MIN_SCALE;
} else if (scale >= MAX_SCALE) {
scale = MAX_SCALE;
}
double a = TUIMultimediaGraphComputerUtils.distance4PointF(mCenterPoint, mPreMovePointF);
double b = TUIMultimediaGraphComputerUtils.distance4PointF(mPreMovePointF, mCurMovePointF);
double c = TUIMultimediaGraphComputerUtils.distance4PointF(mCenterPoint, mCurMovePointF);
double cosb = (a * a + c * c - b * b) / (2 * a * c);
if (cosb >= 1) {
cosb = 1f;
}
double radian = Math.acos(cosb);
float newDegree = (float) TUIMultimediaGraphComputerUtils.radianToDegree(radian);
PointF centerToProMove = new PointF((mPreMovePointF.x - mCenterPoint.x),
(mPreMovePointF.y - mCenterPoint.y));
PointF centerToCurMove = new PointF((mCurMovePointF.x - mCenterPoint.x),
(mCurMovePointF.y - mCenterPoint.y));
float result = centerToProMove.x * centerToCurMove.y
- centerToProMove.y * centerToCurMove.x;
if (result < 0) {
newDegree = -newDegree;
}
mDegree = mDegree + newDegree;
mScale = scale;
transformDraw();
}
private void moveImage() {
mCenterPoint.x += mCurMovePointF.x - mPreMovePointF.x;
mCenterPoint.y += mCurMovePointF.y - mPreMovePointF.y;
mMoveSize.x += mCurMovePointF.x - mPreMovePointF.x;
mMoveSize.y += mCurMovePointF.y - mPreMovePointF.y;
adjustLayout();
}
private void transformDraw() {
if (mBitmap == null) {
return;
}
int bitmapWidth = (int) (mBitmap.getWidth() * mScale * mViewScaleX);
int bitmapHeight = (int) (mBitmap.getHeight() * mScale * mViewScaleY);
int framePadding = (int) (mFramePadding * Math.max(mViewScaleX, mViewScaleY));
computeRect(-framePadding, -framePadding, bitmapWidth + framePadding,
bitmapHeight + framePadding, mDegree);
LiteavLog.i(TAG, "mScale = " + mScale + " mViewScaleX = " + mViewScaleX + " mViewScaleY= " + mViewScaleY);
mMatrix.setScale(mScale * mViewScaleX, mScale * mViewScaleY);
mMatrix.postRotate(mDegree % 360, bitmapWidth * 1.0f / 2, bitmapHeight * 1.0f / 2);
mMatrix.postTranslate(
mOffsetX + mOperationIconSize * 1.0f / 2, mOffsetY + mOperationIconSize * 1.0f / 2);
adjustLayout();
}
private void computeRect(int left, int top, int right, int bottom, float degree) {
Point lt = new Point(left, top);
Point rt = new Point(right, top);
Point rb = new Point(right, bottom);
Point lb = new Point(left, bottom);
Point cp = new Point((left + right) / 2, (top + bottom) / 2);
mLTPoint = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, lt, degree);
mRTPoint = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, rt, degree);
mRBPoint = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, rb, degree);
mLBPoint = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, lb, degree);
int maxCoordinateX = getMaxValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);
int minCoordinateX = getMinValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);
mViewWidth = maxCoordinateX - minCoordinateX;
int maxCoordinateY = getMaxValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y);
int minCoordinateY = getMinValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y);
mViewHeight = maxCoordinateY - minCoordinateY;
Point viewCenterPoint = new Point(
(maxCoordinateX + minCoordinateX) / 2, (maxCoordinateY + minCoordinateY) / 2);
mOffsetX = mViewWidth / 2 - viewCenterPoint.x;
mOffsetY = mViewHeight / 2 - viewCenterPoint.y;
int halfDrawableWidth = mOperationIconSize / 2;
int halfDrawableHeight = mOperationIconSize / 2;
mLTPoint.x += (mOffsetX + halfDrawableWidth);
mRTPoint.x += (mOffsetX + halfDrawableWidth);
mRBPoint.x += (mOffsetX + halfDrawableWidth);
mLBPoint.x += (mOffsetX + halfDrawableWidth);
mLTPoint.y += (mOffsetY + halfDrawableHeight);
mRTPoint.y += (mOffsetY + halfDrawableHeight);
mRBPoint.y += (mOffsetY + halfDrawableHeight);
mLBPoint.y += (mOffsetY + halfDrawableHeight);
mRotatePoint = locatePoint(mControlLocation);
mEditPoint = locatePoint(mEditLocation);
mDeletePoint = locatePoint(mDeleteLocation);
}
private Point locatePoint(int location) {
switch (location) {
case LEFT_TOP:
return mLTPoint;
case RIGHT_TOP:
return mRTPoint;
case RIGHT_BOTTOM:
return mRBPoint;
case LEFT_BOTTOM:
return mLBPoint;
}
return mLTPoint;
}
private Zoom judgeZoom(float x, float y) {
PointF touchPoint = new PointF(x, y);
PointF controlPointF = new PointF(mRotatePoint);
float distanceToControl = TUIMultimediaGraphComputerUtils.distance4PointF(touchPoint, controlPointF);
if (distanceToControl < mOperationIconSize / 2.0) {
return Zoom.ROTATE_ZOOM;
}
if (mShowEdit) {
float disToEdit = TUIMultimediaGraphComputerUtils.distance4PointF(touchPoint, new PointF(mEditPoint));
if (disToEdit < mOperationIconSize / 2.0) {
return Zoom.EDIT_ZOOM;
}
}
if (mShowDelete) {
float disToDelete = TUIMultimediaGraphComputerUtils.distance4PointF(touchPoint, new PointF(mDeletePoint));
if (disToDelete < mOperationIconSize / 2.0) {
return Zoom.DELETE_ZOOM;
}
}
return Zoom.IMAGE_ZOOM;
}
private int getMaxValue(Integer... array) {
List<Integer> list = Arrays.asList(array);
Collections.sort(list);
return list.get(list.size() - 1);
}
private int getMinValue(Integer... array) {
List<Integer> list = Arrays.asList(array);
Collections.sort(list);
return list.get(0);
}
public enum Status {
STATUS_INIT,
STATUS_EDIT,
STATUS_DELETE,
STATUS_SELECTED
}
private enum Zoom {
IMAGE_ZOOM,
ROTATE_ZOOM,
EDIT_ZOOM,
DELETE_ZOOM
}
}
@@ -0,0 +1,127 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.liteav.base.util.Size;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView.Status;
import com.tencent.ugc.TXVideoEditConstants.TXRect;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class FloatLayerViewGroup extends FrameLayout {
private final String TAG = FloatLayerViewGroup.class.getSimpleName() + "_" + hashCode();
private final List<FloatLayerView> mFloatLayerViewList = new ArrayList<>();
private FloatLayerView mSelectedFloatLayerView;
public FloatLayerViewGroup(@NonNull Context context) {
super(context);
}
public FloatLayerViewGroup(@NonNull Context context, AttributeSet attrs) {
super(context, attrs);
}
public FloatLayerViewGroup(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void addFloatLayerView(FloatLayerView view) {
if (view == null) {
return;
}
mFloatLayerViewList.add(view);
addView(view, MATCH_PARENT, MATCH_PARENT);
view.tuiDataGetStatus().observe(status -> {
if (status == Status.STATUS_SELECTED) {
if (mSelectedFloatLayerView != null && view != mSelectedFloatLayerView) {
mSelectedFloatLayerView.unSelect();
}
mSelectedFloatLayerView = view;
}
});
}
public void removeFloatLayerView(FloatLayerView view) {
if (view == null) {
return;
}
mFloatLayerViewList.remove(view);
removeView(view);
}
public void removeAllFloatLayerView() {
for(FloatLayerView view : mFloatLayerViewList) {
removeView(view);
}
mFloatLayerViewList.clear();
}
@NonNull
public List<TUIMultimediaPasterInfo> getAllPaster() {
List<TUIMultimediaPasterInfo> pasterInfoList = new LinkedList<>();
for (FloatLayerView view : mFloatLayerViewList) {
Bitmap image = view.getRotateBitmap();
if (image == null) {
continue;
}
TUIMultimediaPasterInfo pasterInfo = new TUIMultimediaPasterInfo();
TXRect frame = new TXRect();
frame.width = view.getImageWidth();
frame.x = view.getImageLeft();
frame.y = view.getImageTop();
pasterInfo.frame = frame;
pasterInfo.image = image;
pasterInfo.pasterType = view.getPasterType();
pasterInfoList.add(pasterInfo);
}
return pasterInfoList;
}
public void unSelectOperationView() {
if (mSelectedFloatLayerView != null) {
mSelectedFloatLayerView.unSelect();
}
}
public void setCanTouch(boolean touch) {
for (FloatLayerView floatLayerView : mFloatLayerViewList) {
if (floatLayerView != null) {
floatLayerView.setCanTouch(touch);
}
}
}
@Override
public int getChildCount() {
return mFloatLayerViewList.size();
}
@SuppressLint("DrawAllocation")
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view instanceof FloatLayerView) {
((FloatLayerView)view).onParentSizeChange(new Size(r - l, b - t));
}
view.invalidate();
}
}
}
@@ -0,0 +1,76 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerViewGroup;
@SuppressLint("ViewConstructor")
public class PicturePasterFloatLayerView extends FloatLayerView {
private final static int MAX_BITMAP_SIDE_SIZE_DP = 150;
private final String TAG = PicturePasterFloatLayerView.class + "_" + hashCode();
private final Context mContext;
private final FloatLayerViewGroup mFloatLayerViewGroup;
private final TUIMultimediaDataObserver<Status> mOnStatusChanged = status -> {
if (status == Status.STATUS_DELETE) {
onDeleted();
}
};
public PicturePasterFloatLayerView(Context context, FloatLayerViewGroup floatLayerViewGroup, Bitmap bitmap) {
super(context);
mContext = context;
mFloatLayerViewGroup = floatLayerViewGroup;
mFloatLayerViewGroup.addFloatLayerView(this);
setPasterType(PasterType.STATIC_PICTURE_PASTER);
showDelete(true);
showEdit(false);
setImageBitmap(bitmap);
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
addObserve();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
removeObserve();
}
@Override
public void setImageBitmap(Bitmap bitmap) {
if (bitmap == null) {
return;
}
int sideLength = Math.max(bitmap.getWidth(), bitmap.getHeight());
float maxSideLength = TUIMultimediaResourceUtils.spToPx(mContext, MAX_BITMAP_SIDE_SIZE_DP);
if (sideLength > maxSideLength) {
setImageScale(maxSideLength / sideLength);
}
super.setImageBitmap(bitmap);
}
private void onDeleted() {
mFloatLayerViewGroup.removeFloatLayerView(this);
}
private void addObserve() {
mTuiDataStatus.observe(mOnStatusChanged);
}
private void removeObserve() {
mTuiDataStatus.removeObserver(mOnStatusChanged);
}
}
@@ -0,0 +1,86 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterItem;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterType;
public class PicturePasterItemAdapter extends BaseAdapter {
private final Context mContext;
private final PicturePasterInfo mPasterInfo;
private final int mPasterTypeIndex;
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedItem;
public PicturePasterItemAdapter(Context context, PicturePasterInfo pasterInfo, int pasterTypeIndex,
TUIMultimediaData<ItemPosition> tuiDataSelectedItem) {
mContext = context;
mPasterInfo = pasterInfo;
mPasterTypeIndex = pasterTypeIndex;
mTuiDataSelectedItem = tuiDataSelectedItem;
}
@Override
public int getCount() {
PicturePasterType typeInfo = mPasterInfo.getPasterType(mPasterTypeIndex);
if (typeInfo != null) {
return typeInfo.getItemSize();
}
return 0;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.multimedia_plugin_edit_picture_paster_item_view, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (position >= 0) {
PicturePasterItem pasterItem = mPasterInfo.getPasterItem(new ItemPosition(mPasterTypeIndex, position));
Bitmap bitmap = pasterItem != null ? pasterItem.getPasterIcon() : null;
if (bitmap != null && !bitmap.isRecycled()) {
holder.icon.setImageBitmap(bitmap);
}
}
convertView.setOnClickListener(v -> mTuiDataSelectedItem.set(new ItemPosition(mPasterTypeIndex, position)));
return convertView;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView icon;
public ViewHolder(View itemView) {
super(itemView);
icon = itemView.findViewById(R.id.paster_iv_icon);
}
}
}
@@ -0,0 +1,195 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
import android.graphics.Bitmap;
import android.net.Uri;
import com.google.gson.Gson;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuicore.TUIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaCommonThreadPool;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterItem;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class PicturePasterManager {
private static final int USER_PASTER_ICON_MAX_SIZE = 360;
private final String TAG = PicturePasterManager.class + "_" + hashCode();
private PicturePasterInfo mPicturePasterSet;
private PicturePasterInfo mUserPicturePasterSet;
private String mPasterJsonFilePath = "paster_info_data.json";
private Future<PicturePasterInfo> mCratePicturePasterSetFuture;
public PicturePasterManager(String pasterJsonFilePath) {
LiteavLog.i(TAG, "pasterJsonFilePath =" + pasterJsonFilePath);
if (pasterJsonFilePath == null || pasterJsonFilePath.isEmpty() || !pasterJsonFilePath.endsWith(".json")) {
LiteavLog.e(TAG, "pasterJsonFilePath =" + pasterJsonFilePath + " is invalid paster json file");
} else {
mPasterJsonFilePath = pasterJsonFilePath;
}
}
static public String getUserPasterJsonFilePath() {
File file = new File(TUIConfig.getDefaultAppDir(), "user_picture_paster.json");
if (file.exists()) {
return file.getPath();
}
try {
if (!file.createNewFile()) {
return null;
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
return file.getPath();
}
public void init() {
mCratePicturePasterSetFuture = TUIMultimediaCommonThreadPool.getThreadExecutor().submit(this::cratePicturePasterSet);
}
public PicturePasterInfo getPicturePasterSetInfo() {
try {
return mCratePicturePasterSetFuture.get(500, TimeUnit.MILLISECONDS);
} catch (Exception e) {
LiteavLog.e(TAG, "create picture paster set info error " + e);
return null;
}
}
public PicturePasterItem getPasterItem(ItemPosition itemPosition) {
return mPicturePasterSet.getPasterItem(itemPosition);
}
protected Bitmap getPaster(ItemPosition position) {
LiteavLog.i(TAG, "select paster position TypeIndex = " +
position.pasterTypeIndex + " ItemIndex = " + position.pasterItemIndex);
PicturePasterItem pasterItem = mPicturePasterSet.getPasterItem(position);
return pasterItem != null ? pasterItem.getPasterImage() : null;
}
public boolean addUserPasterFromUri(Uri uri, ItemPosition itemPosition) {
Bitmap bitmap = TUIMultimediaFileUtil.readBitmapFromUrl(uri);
if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
LiteavLog.w(TAG, "select image is error");
return false;
}
Bitmap iconBitmap = createIcon(bitmap);
ItemPosition newPasterPosition = new ItemPosition(itemPosition.pasterTypeIndex,
itemPosition.pasterItemIndex + 1);
if (mPicturePasterSet != null) {
mPicturePasterSet.addPasterItem(newPasterPosition, new PicturePasterItem(bitmap, iconBitmap));
}
saveUserPicturePaster(itemPosition.pasterTypeIndex, uri, iconBitmap);
return true;
}
public void uninit() {
TUIMultimediaCommonThreadPool.getThreadExecutor().execute(() -> {
if (mUserPicturePasterSet != null) {
mUserPicturePasterSet.saveToJsonFile(getUserPasterJsonFilePath());
}
releaseSource();
});
}
public void releaseSource() {
if (mPicturePasterSet != null) {
mPicturePasterSet.release();
mPicturePasterSet = null;
}
if (mUserPicturePasterSet != null) {
mUserPicturePasterSet.release();
mUserPicturePasterSet = null;
}
}
private PicturePasterInfo cratePicturePasterSet() {
LiteavLog.i(TAG, "crate picture paster set");
mPicturePasterSet = getDefaultPicturePaster();
mUserPicturePasterSet = getUserPicturePaster();
if (mPicturePasterSet == null) {
mPicturePasterSet = new PicturePasterInfo();
}
mPicturePasterSet.append(mUserPicturePasterSet);
return mPicturePasterSet;
}
private PicturePasterInfo getDefaultPicturePaster() {
String json = TUIMultimediaFileUtil.readTextFromFile(mPasterJsonFilePath);
Gson gson = new Gson();
return gson.fromJson(json, PicturePasterInfo.class);
}
public PicturePasterInfo getUserPicturePaster() {
String json = TUIMultimediaFileUtil.readTextFromFile(getUserPasterJsonFilePath());
Gson gson = new Gson();
return gson.fromJson(json, PicturePasterInfo.class);
}
private void saveUserPicturePaster(int pasterTypeIndex, Uri imageUrl, Bitmap iconBitmap) {
TUIMultimediaCommonThreadPool.getThreadExecutor().execute(() -> {
String imagePath = savePasterImage(imageUrl);
String iconPath = savePasterIcon(iconBitmap);
if (imagePath != null && iconPath != null) {
addUserPicturePasterInfo(pasterTypeIndex, imagePath, iconPath);
}
});
}
private void addUserPicturePasterInfo(int pasterTypeIndex, String imageFilePath, String iconFilePath) {
if (mUserPicturePasterSet == null) {
mUserPicturePasterSet = getUserPicturePaster();
if (mUserPicturePasterSet == null) {
mUserPicturePasterSet = new PicturePasterInfo();
}
}
mUserPicturePasterSet.addPasterItem(new ItemPosition(pasterTypeIndex, 0),
new PicturePasterItem(imageFilePath, iconFilePath));
}
private String savePasterImage(Uri uri) {
String time = System.nanoTime() + "_" + Math.abs(new Random().nextInt());
String fileName = "paster_image_" + time;
File pasterFile = TUIMultimediaFileUtil.generateFileName(fileName, new File(TUIConfig.getImageBaseDir()));
if (pasterFile == null) {
return null;
}
TUIMultimediaFileUtil.saveFileFromUri(TUIMultimediaPlugin.getAppContext(), uri, pasterFile.getPath());
return pasterFile.getPath();
}
private String savePasterIcon(Bitmap bitmap) {
String time = System.nanoTime() + "_" + Math.abs(new Random().nextInt());
String fileName = "paster_icon_" + time;
File pasterFile = TUIMultimediaFileUtil.generateFileName(fileName, new File(TUIConfig.getImageBaseDir()));
if (pasterFile == null) {
return null;
}
TUIMultimediaFileUtil.saveBitmap(pasterFile.getPath(), bitmap);
return pasterFile.getPath();
}
private Bitmap createIcon(Bitmap image) {
int side = Math.max(image.getWidth(), image.getHeight());
float scale = USER_PASTER_ICON_MAX_SIZE * 1.0f / side;
if (scale >= 1.0f) {
return null;
}
return Bitmap.createScaledBitmap(image, (int) (image.getWidth() * scale),
(int) (image.getHeight() * scale), true);
}
}
@@ -0,0 +1,226 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.net.Uri;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.activity.result.ActivityResultCaller;
import androidx.annotation.NonNull;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
import com.google.android.material.tabs.TabLayout.Tab;
import com.google.android.material.tabs.TabLayoutMediator;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
import com.tencent.qcloud.tuikit.timcommon.util.ActivityResultResolver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterItem;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterType;
@SuppressLint("ViewConstructor")
public class PicturePasterPanelView extends RelativeLayout {
private final String TAG = PicturePasterPanelView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final PicturePasterManager mPicturePasterManager;
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedItem = new TUIMultimediaData<>(null);
private final OnTabSelectedListener onTabSelectedListener = new OnTabSelectedListener() {
@Override
public void onTabSelected(Tab tab) {
configTabSelectedStatus(tab, true);
}
@Override
public void onTabUnselected(Tab tab) {
configTabSelectedStatus(tab, false);
}
@Override
public void onTabReselected(Tab tab) {
}
};
private PicturePasterTypeAdapter mPasterTypeAdapter;
private PicturePasterInfo mPicturePasterInfo;
private PicturePasterSelectListener mPicturePasterSelectListener;
private final TUIMultimediaDataObserver<ItemPosition> mOnSelectItemChanged = new TUIMultimediaDataObserver<ItemPosition>() {
@Override
public void onChanged(ItemPosition itemPosition) {
PicturePasterItem pasterItem = mPicturePasterManager.getPasterItem(itemPosition);
if (pasterItem == null) {
return;
}
if (pasterItem.isFileSelector()) {
selectPicturePasterFromLocalFile(itemPosition);
} else {
if (mPicturePasterSelectListener != null) {
mPicturePasterSelectListener
.PicturePasterSelectCompeted(mPicturePasterManager.getPaster(itemPosition));
}
}
}
};
public PicturePasterPanelView(@NonNull Context context,
@NonNull PicturePasterManager picturePasterManager) {
super(context);
mContext = context;
mPicturePasterManager = picturePasterManager;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
mTuiDataSelectedItem.observe(mOnSelectItemChanged);
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
removeAllViews();
super.onDetachedFromWindow();
mTuiDataSelectedItem.removeObserver(mOnSelectItemChanged);
}
public void selectPaster(PicturePasterSelectListener picturePasterSelectListener) {
mPicturePasterSelectListener = picturePasterSelectListener;
}
private void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_picture_paster_panel_view, this);
mPicturePasterInfo = mPicturePasterManager.getPicturePasterSetInfo();
if (mPicturePasterInfo == null) {
LiteavLog.i(TAG, "get picture paster set info null");
return;
}
ViewPager2 beautyPager = findViewById(R.id.picture_paster_panel_view_pager);
mPasterTypeAdapter = new PicturePasterTypeAdapter(mContext, mPicturePasterInfo,
mTuiDataSelectedItem);
beautyPager.setAdapter(mPasterTypeAdapter);
TabLayout tabLayout = findViewById(R.id.picture_paster_panel_tab_layout);
tabLayout.addOnTabSelectedListener(onTabSelectedListener);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
new TabLayoutMediator(tabLayout, beautyPager, this::configTab).attach();
}
private void configTab(@NonNull Tab tab, int position) {
if (mPicturePasterInfo == null) {
return;
}
PicturePasterType typeInfo = mPicturePasterInfo.getPasterType(position);
Bitmap pasterIcon = typeInfo != null ? typeInfo.getPasterIcon() : null;
if (pasterIcon != null) {
configTabImage(tab, pasterIcon);
return;
}
configTabText(tab, typeInfo != null ? typeInfo.getTypeName() : "");
}
private void configTabText(Tab tab, String text) {
TextView customTextView = new TextView(getContext());
customTextView.setGravity(Gravity.CENTER);
customTextView.setText(text);
customTextView.setMaxLines(1);
customTextView.setTextColor(
TUIMultimediaResourceUtils.getResources().getColor(R.color.multimedia_plugin_record_beauty_type_text_color_normal));
float textSize = TUIMultimediaResourceUtils.getResources().getDimension(R.dimen.multimedia_plugin_common_text_size);
customTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
customTextView.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
tab.setCustomView(customTextView);
}
private void configTabImage(Tab tab, Bitmap image) {
View tabView = LayoutInflater.from(getContext())
.inflate(R.layout.multimedia_plugin_edit_picture_paster_type_image_item, null);
ImageView imageView = tabView.findViewById(R.id.paster_type_icon);
imageView.setImageBitmap(image);
tab.setCustomView(tabView);
}
private void configTabSelectedStatus(Tab tab, boolean isSelected) {
View customView = tab.getCustomView();
if (customView instanceof TextView) {
configTabSelectedText((TextView) customView, isSelected);
} else {
configTabSelectedImage(customView, isSelected);
}
}
private void configTabSelectedText(TextView tabTextView, boolean isSelected) {
if (tabTextView == null) {
return;
}
int textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_normal;
int textColorFontStyle = Typeface.NORMAL;
if (isSelected) {
textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_selected;
textColorFontStyle = Typeface.BOLD;
}
tabTextView.setTextColor(
TUIMultimediaResourceUtils.getResources().getColor(textColorResourceId));
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
tabTextView.setTypeface(Typeface.create(fontName, textColorFontStyle));
}
private void configTabSelectedImage(View tabView, boolean isSelected) {
if (tabView == null) {
return;
}
ImageView backgroundImage = tabView.findViewById(R.id.paster_type_icon_background);
if (isSelected) {
backgroundImage.setVisibility(VISIBLE);
} else {
backgroundImage.setVisibility(INVISIBLE);
}
}
private void selectPicturePasterFromLocalFile(ItemPosition itemPosition) {
ActivityResultResolver.getSingleContent((ActivityResultCaller) mContext,
new String[]{ActivityResultResolver.CONTENT_TYPE_IMAGE, ActivityResultResolver.CONTENT_TYPE_ALL},
new TUIValueCallback<Uri>() {
@Override
public void onSuccess(Uri uri) {
if (mPicturePasterManager.addUserPasterFromUri(uri, itemPosition)) {
mPasterTypeAdapter.notifySubDataSetChanged(itemPosition.pasterTypeIndex);
}
}
@Override
public void onError(int errorCode, String errorMessage) {
}
});
}
public interface PicturePasterSelectListener {
void PicturePasterSelectCompeted(Bitmap bitmap);
}
}
@@ -0,0 +1,93 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
import org.jetbrains.annotations.Nullable;
public class PicturePasterTypeAdapter extends FragmentStateAdapter {
private final Context mContext;
private final PicturePasterInfo mPasterInfo;
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedBeautyItem;
private final PasterTypeItemFragment[] mPasterTypeFragmentList;
public PicturePasterTypeAdapter(@NonNull Context context, @NonNull PicturePasterInfo pasterInfo,
@NonNull TUIMultimediaData<ItemPosition> tuiDataSelectItem) {
super((FragmentActivity) context);
mPasterInfo = pasterInfo;
mContext = context;
mTuiDataSelectedBeautyItem = tuiDataSelectItem;
mPasterTypeFragmentList = new PasterTypeItemFragment[pasterInfo.getPasterTypeSize()];
}
@NonNull
@Override
public Fragment createFragment(int position) {
PasterTypeItemFragment fragment = new PasterTypeItemFragment(mContext, mPasterInfo, position,
mTuiDataSelectedBeautyItem);
if (position >= 0 && position < mPasterInfo.getPasterTypeSize()) {
mPasterTypeFragmentList[position] = fragment;
}
return fragment;
}
@Override
public int getItemCount() {
return mPasterInfo.getPasterTypeSize();
}
public void notifySubDataSetChanged(int position) {
if (mPasterTypeFragmentList[position] != null) {
mPasterTypeFragmentList[position].notifyDataSetChanged();
}
}
public static class PasterTypeItemFragment extends Fragment {
private final PicturePasterInfo mPasterInfo;
private final Context mContext;
private final int mPasterTypeIndex;
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedBeautyItem;
private BaseAdapter mAdapter;
public PasterTypeItemFragment(Context context, PicturePasterInfo pasterInfo, int typeIndex,
TUIMultimediaData<ItemPosition> tuiDataSelectedBeautyItem) {
mContext = context;
mPasterInfo = pasterInfo;
mPasterTypeIndex = typeIndex;
mTuiDataSelectedBeautyItem = tuiDataSelectedBeautyItem;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.multimedia_plugin_edit_picture_paster_type_fragment, container, false);
GridView gridView = view.findViewById(R.id.grid_view);
mAdapter = new PicturePasterItemAdapter(mContext, mPasterInfo, mPasterTypeIndex,
mTuiDataSelectedBeautyItem);
gridView.setAdapter(mAdapter);
return view;
}
public void notifyDataSetChanged() {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
}
}
@@ -0,0 +1,116 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.tencent.liteav.base.util.LiteavLog;
import java.io.FileWriter;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class PicturePasterInfo {
@SerializedName("paster_type_list")
private List<PicturePasterType> mPasterTypeItemList;
public void addPasterItem(ItemPosition position, PicturePasterItem pasterItemInfo) {
if (position.pasterTypeIndex < 0) {
return;
}
if (mPasterTypeItemList == null) {
mPasterTypeItemList = new LinkedList<>();
}
while (position.pasterTypeIndex + 1 - mPasterTypeItemList.size() > 0) {
mPasterTypeItemList.add(new PicturePasterType());
}
mPasterTypeItemList.get(position.pasterTypeIndex).addPasterItem(position.pasterItemIndex, pasterItemInfo);
}
public int getPasterTypeSize() {
return mPasterTypeItemList != null ? mPasterTypeItemList.size() : 0;
}
public PicturePasterType getPasterType(int pasterTypeIndex) {
if (pasterTypeIndex < 0 || pasterTypeIndex >= getPasterTypeSize()) {
return null;
}
return mPasterTypeItemList.get(pasterTypeIndex);
}
public PicturePasterItem getPasterItem(ItemPosition itemPosition) {
PicturePasterType typeInfo = getPasterType(itemPosition.pasterTypeIndex);
return typeInfo != null ? typeInfo.getPasterItem(itemPosition.pasterItemIndex) : null;
}
public void append(PicturePasterInfo otherSetInfo) {
if (otherSetInfo == null) {
return;
}
List<PicturePasterType> otherPasterTypeItemList = otherSetInfo.mPasterTypeItemList;
if (otherPasterTypeItemList == null) {
return;
}
for (int i = 0; i < otherPasterTypeItemList.size(); i++) {
PicturePasterType typeItemInfo = otherPasterTypeItemList.get(i);
if (typeItemInfo == null) {
continue;
}
List<PicturePasterItem> itemInfoList = typeItemInfo.getPasterItemList();
if (itemInfoList == null) {
continue;
}
PicturePasterType pasterType = getPasterType(i);
int firstFileSelectorIndex = pasterType != null ? pasterType.getFirstFileSelectorIndex() : 0;
ItemPosition position = new ItemPosition(i, firstFileSelectorIndex + 1);
Collections.reverse(itemInfoList);
for (PicturePasterItem itemInfo : itemInfoList) {
addPasterItem(position, itemInfo);
}
}
}
public void saveToJsonFile(String filePath) {
Gson gson = new Gson();
try {
FileWriter fileWriter = new FileWriter(filePath);
gson.toJson(this, fileWriter);
fileWriter.flush();
fileWriter.close();
} catch (Exception e) {
LiteavLog.e("save json file error", e.toString());
}
}
public void release() {
if (mPasterTypeItemList == null) {
return;
}
for (PicturePasterType pasterTypeItemInfo : mPasterTypeItemList) {
if (pasterTypeItemInfo != null) {
pasterTypeItemInfo.release();
}
}
}
public static class ItemPosition {
public int pasterTypeIndex;
public int pasterItemIndex;
public ItemPosition(int pasterTypeIndex, int pasterItemIndex) {
this.pasterTypeIndex = pasterTypeIndex;
this.pasterItemIndex = pasterItemIndex;
}
public boolean equals(ItemPosition other) {
return pasterTypeIndex == other.pasterTypeIndex && pasterItemIndex == other.pasterItemIndex;
}
}
}
@@ -0,0 +1,69 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data;
import android.graphics.Bitmap;
import com.google.gson.annotations.SerializedName;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
public class PicturePasterItem {
private final String TAG = PicturePasterItem.class.getSimpleName() + "_" + hashCode();
@SerializedName("item_image_path")
private String mImageFilePath;
@SerializedName("item_icon_path")
private String mIconFilePath;
@SerializedName("item_is_file_selector")
private boolean mIsFileSelector = false;
transient private Bitmap mImage;
transient private Bitmap mIcon;
public PicturePasterItem(String imageFilePath, String iconFilePath) {
mImageFilePath = imageFilePath;
mIconFilePath = iconFilePath;
}
public PicturePasterItem(Bitmap pasterImage, Bitmap pasterIcon) {
mImage = pasterImage;
mIcon = pasterIcon;
}
public Bitmap getPasterImage() {
if (mImage == null) {
mImage = TUIMultimediaFileUtil.decodeBitmap(mImageFilePath);
}
return mImage;
}
public Bitmap getPasterIcon() {
if (mIcon != null) {
return mIcon;
}
mIcon = TUIMultimediaFileUtil.decodeBitmap(mIconFilePath);
if (mIcon != null) {
return mIcon;
}
return getPasterImage();
}
public boolean isFileSelector() {
return mIsFileSelector;
}
public void recycleBitmap() {
if (mImage != null) {
mImage.recycle();
mImage = null;
}
if (mIcon != null) {
mIcon.recycle();
mIcon = null;
}
}
}
@@ -0,0 +1,85 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data;
import android.graphics.Bitmap;
import com.google.gson.annotations.SerializedName;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import java.util.LinkedList;
import java.util.List;
public class PicturePasterType {
@SerializedName("type_name")
private String mTypeName;
@SerializedName("type_icon_path")
private String mTypeIconPath;
@SerializedName("paster_item_list")
private List<PicturePasterItem> mPasterItemList;
transient private Bitmap mIcon;
public void addPasterItem(int itemIndex, PicturePasterItem pasterItemInfo) {
if (itemIndex < 0) {
return;
}
if (mPasterItemList == null) {
mPasterItemList = new LinkedList<>();
}
mPasterItemList.add(itemIndex, pasterItemInfo);
}
public PicturePasterItem getPasterItem(int itemIndex) {
if (itemIndex < 0 || itemIndex >= mPasterItemList.size()) {
return null;
}
return mPasterItemList.get(itemIndex);
}
public List<PicturePasterItem> getPasterItemList() {
return mPasterItemList;
}
public int getFirstFileSelectorIndex() {
for (int i = 0; i < mPasterItemList.size(); i++) {
PicturePasterItem pasterItemInfo = mPasterItemList.get(i);
if (pasterItemInfo != null && pasterItemInfo.isFileSelector()) {
return i;
}
}
return -1;
}
public int getItemSize() {
return mPasterItemList.size();
}
public String getTypeName() {
if (mTypeName.startsWith(TUIMultimediaResourceUtils.STRING_RESOURCE_PREFIX)) {
return TUIMultimediaResourceUtils.getString(mTypeName);
}
return mTypeName;
}
public Bitmap getPasterIcon() {
if (mIcon != null) {
return mIcon;
}
mIcon = TUIMultimediaFileUtil.decodeBitmap(mTypeIconPath);
return mIcon;
}
public void release() {
if (mPasterItemList == null) {
return;
}
for (PicturePasterItem pasterItemInfo : mPasterItemList) {
if (pasterItemInfo != null) {
pasterItemInfo.recycleBitmap();
}
}
}
}
@@ -0,0 +1,116 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaScrollViewAdapter;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
public class SubtitleColorItemAdapter extends TUIMultimediaScrollViewAdapter {
private static final int[] COLOR_ARRAY = {Color.WHITE, Color.BLACK, Color.RED, Color.GREEN, Color.BLUE, Color.CYAN,
Color.DKGRAY, Color.GRAY, Color.LTGRAY, Color.MAGENTA, Color.YELLOW};
private static final int CIRCLE_SHAPE_SIZE_DP = 18;
private static final int SELECT_CIRCLE_SHAPE_SIZE_DP = 25;
private final Context mContext;
private final TUIMultimediaData<Integer> mTuiDataSelectColor;
public SubtitleColorItemAdapter(Context context, TUIMultimediaData<Integer> tuiDataSelectColor) {
mContext = context;
mTuiDataSelectColor = tuiDataSelectColor;
mSelectPosition = getColorIndex(tuiDataSelectColor.get());
}
public static int getColorIndex(int color) {
for (int i = 0; i < COLOR_ARRAY.length; i++) {
if (COLOR_ARRAY[i] == color) {
return i;
}
}
return 0;
}
@Override
public void setSelectPosition(int position) {
if (mSelectPosition != position) {
mSelectPosition = position;
if (mTuiDataSelectColor.get() != COLOR_ARRAY[mSelectPosition]) {
mTuiDataSelectColor.set(COLOR_ARRAY[mSelectPosition]);
}
notifyDataSetChanged();
}
}
@Override
public int getCount() {
return COLOR_ARRAY.length;
}
@Override
public Object getItem(int position) {
return COLOR_ARRAY[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(R.layout.multimedia_plugin_edit_subtitle_color_item, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.innerCircleShape.setImageResource(R.drawable.multimedia_plugin_subtitle_color_selector_circle_shape);
GradientDrawable circleShape = (GradientDrawable) holder.innerCircleShape.getDrawable();
circleShape.setColor(COLOR_ARRAY[position]);
int innerCircleShapeSize = (mSelectPosition == position ? SELECT_CIRCLE_SHAPE_SIZE_DP : CIRCLE_SHAPE_SIZE_DP);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) holder.innerCircleShape
.getLayoutParams();
layoutParams.width = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize);
layoutParams.height = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize);
holder.innerCircleShape.setLayoutParams(layoutParams);
layoutParams = (RelativeLayout.LayoutParams) holder.outerCircleShape.getLayoutParams();
layoutParams.width = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize + 4);
layoutParams.height = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize + 4);
holder.outerCircleShape.setLayoutParams(layoutParams);
convertView.setOnClickListener(v -> {
if (mSelectPosition != position) {
mSelectPosition = position;
mTuiDataSelectColor.set(COLOR_ARRAY[mSelectPosition]);
notifyDataSetChanged();
}
});
return convertView;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView outerCircleShape;
private final ImageView innerCircleShape;
public ViewHolder(View itemView) {
super(itemView);
outerCircleShape = itemView.findViewById(R.id.outer_color_shape);
innerCircleShape = itemView.findViewById(R.id.inner_color_shape);
}
}
}
@@ -0,0 +1,35 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
import java.util.LinkedList;
import java.util.List;
public class SubtitleInfo {
public List<String> textList = new LinkedList<>();
public int textColor;
public int textSize;
public String getText() {
StringBuilder text = new StringBuilder();
for (String textItem : textList) {
text.append(textItem);
}
return text.toString();
}
public boolean equals(SubtitleInfo subtitleInfo) {
if (subtitleInfo == null) {
return false;
}
if (textColor != subtitleInfo.textColor) {
return false;
}
if (textSize != subtitleInfo.textSize) {
return false;
}
return textList.equals(subtitleInfo.textList);
}
}
@@ -0,0 +1,190 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIHorizontalScrollView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
public class SubtitlePanelView extends RelativeLayout {
private final static int MAX_SHOW_LINES = 8;
private final String TAG = SubtitlePanelView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaData<Integer> mTuiDataSelectedTextColor = new TUIMultimediaData<>(Color.WHITE);
private EditText mInputEditText;
private View mDecorView;
private boolean mIsSoftKeypadShow = false;
private SubtitleInputListener mSubtitleInputListener;
private final OnGlobalLayoutListener onGlobalLayoutListener = this::softKeypadLayoutListener;
public SubtitlePanelView(Context context) {
super(context);
mContext = context;
}
public void inputText(SubtitleInfo subtitleInfo, SubtitleInputListener subtitleInputListener) {
mSubtitleInputListener = subtitleInputListener;
String text = subtitleInfo != null ? subtitleInfo.getText() : "";
mInputEditText.setText(text);
mInputEditText.setSelection(text.length());
if (subtitleInfo != null) {
mInputEditText.setTextColor(subtitleInfo.textColor);
mTuiDataSelectedTextColor.set(subtitleInfo.textColor);
}
showInputView();
}
public int getTextViewWidth() {
if (mInputEditText != null) {
return mInputEditText.getLayoutParams().width;
}
return 720;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
removeAllViews();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mInputEditText.getLayoutParams();
layoutParams.width = getWidth() * 2 / 3;
layoutParams.height = LayoutParams.WRAP_CONTENT;
layoutParams.topMargin = getHeight() / 6;
layoutParams.leftMargin = getWidth() / 6;
mInputEditText.setLayoutParams(layoutParams);
mInputEditText.requestFocus();
}
private void initView() {
View root = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_subtitle_input_view, this, true);
mDecorView = ((Activity) mContext).getWindow().getDecorView();
mInputEditText = findViewById(R.id.subtitle_input_edit_text);
mInputEditText.setVisibility(VISIBLE);
mInputEditText.requestFocus();
mInputEditText.setLines(MAX_SHOW_LINES);
TUIHorizontalScrollView colorSelectScrollItemView = findViewById(R.id.input_text_color_scroll_view);
colorSelectScrollItemView.setAdapter(new SubtitleColorItemAdapter(mContext, mTuiDataSelectedTextColor));
mTuiDataSelectedTextColor.observe(color -> {
mInputEditText.setTextColor(color);
colorSelectScrollItemView.setClicked(SubtitleColorItemAdapter.getColorIndex(color));
});
root.setOnClickListener(v -> completeTextInput(true));
findViewById(R.id.subtitle_input_confirm).setOnClickListener(v -> completeTextInput(true));
findViewById(R.id.subtitle_input_cancel).setOnClickListener(v -> completeTextInput(false));
}
private void softKeypadLayoutListener() {
if (mDecorView == null) {
return;
}
Rect r = new Rect();
mDecorView.getWindowVisibleDisplayFrame(r);
int screenHeight = mDecorView.getRootView().getHeight();
int keypadHeight = screenHeight - r.bottom;
if (keypadHeight < screenHeight * 0.15) {
if (mIsSoftKeypadShow) {
mIsSoftKeypadShow = false;
completeTextInput(true);
}
return;
}
mIsSoftKeypadShow = true;
softKeypadLayoutListener(keypadHeight);
}
private void softKeypadLayoutListener(int keypadHeight) {
LinearLayout colorLayout = findViewById(R.id.color_select_layout);
if (colorLayout != null) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) colorLayout.getLayoutParams();
layoutParams.bottomMargin = keypadHeight + 30;
colorLayout.setLayoutParams(layoutParams);
}
}
private void completeTextInput(boolean isConfirm) {
LiteavLog.i(TAG, (isConfirm ? "confirm" : "cancel") + " text input");
SubtitleInfo subtitleInfo = null;
if (isConfirm) {
subtitleInfo = getSubtitleInfo();
}
hideInputView();
if (mSubtitleInputListener != null) {
mSubtitleInputListener.onSubtitleInputCompeted(subtitleInfo);
}
}
private SubtitleInfo getSubtitleInfo() {
SubtitleInfo subtitleInfo = new SubtitleInfo();
String text = mInputEditText.getText().toString();
int lineCount = mInputEditText.getLineCount();
for (int i = 0; i < lineCount; i++) {
int start = mInputEditText.getLayout().getLineStart(i);
int end = mInputEditText.getLayout().getLineEnd(i);
String lineText = text.substring(start, end);
subtitleInfo.textList.add(lineText);
}
subtitleInfo.textColor = mTuiDataSelectedTextColor.get();
subtitleInfo.textSize = (int) mInputEditText.getTextSize();
return subtitleInfo;
}
private void hideInputView() {
mIsSoftKeypadShow = false;
if (mDecorView != null) {
mDecorView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
}
InputMethodManager inputMethodManager = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
}
setVisibility(GONE);
}
private void showInputView() {
setVisibility(VISIBLE);
if (mDecorView != null) {
mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
InputMethodManager inputMethodManager = (InputMethodManager) mContext
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.showSoftInput(mInputEditText, InputMethodManager.SHOW_IMPLICIT);
}
}
public interface SubtitleInputListener {
void onSubtitleInputCompeted(SubtitleInfo subtitleInfo);
}
}
@@ -0,0 +1,120 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
import static com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType.SUBTITLE_PASTER;
import static com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView.Status.STATUS_EDIT;
import static com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView.Status.STATUS_INIT;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerViewGroup;
@SuppressLint("ViewConstructor")
public class SubtitlePasterFloatLayerView extends FloatLayerView {
private final String TAG = SubtitlePasterFloatLayerView.class + "_" + hashCode();
private final SubtitlePanelView mSubtitlePanelView;
private final FloatLayerViewGroup mFloatLayerViewGroup;
private SubtitleInfo mSubtitleTextInfo;
private final TUIMultimediaDataObserver<Status> mOnStatusChanged = status -> {
switch (status) {
case STATUS_EDIT:
setVisibility(INVISIBLE);
onEditText();
break;
case STATUS_DELETE:
onDeleted();
break;
default:
break;
}
};
public SubtitlePasterFloatLayerView(Context context, FloatLayerViewGroup floatLayerViewGroup,
SubtitleInfo tuiDataTextInfo, SubtitlePanelView tuiDataSubtitlePanelView) {
super(context);
mSubtitlePanelView = tuiDataSubtitlePanelView;
mSubtitleTextInfo = tuiDataTextInfo;
setPasterType(SUBTITLE_PASTER);
mFloatLayerViewGroup = floatLayerViewGroup;
mFloatLayerViewGroup.addFloatLayerView(this);
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
generatorSubtitlePaster(mSubtitleTextInfo);
addObserve();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
removeObserve();
}
private void onEditText() {
mSubtitlePanelView.inputText(mSubtitleTextInfo, subtitleInfo -> {
if (subtitleInfo != null) {
mSubtitleTextInfo = subtitleInfo;
generatorSubtitlePaster(subtitleInfo);
}
invalidate();
setVisibility(VISIBLE);
mTuiDataStatus.set(STATUS_INIT);
});
}
private void generatorSubtitlePaster(SubtitleInfo subtitleInfo) {
LiteavLog.i(TAG, "set subtitle text");
if (subtitleInfo == null) {
return;
}
if (subtitleInfo.getText().isEmpty()) {
onDeleted();
return;
}
LiteavLog.i(TAG, "set subtitle text = " + subtitleInfo.getText());
SubtitlePasterGenerator pasterGenerator = new SubtitlePasterGenerator();
pasterGenerator.setSubtitleInfo(subtitleInfo);
Bitmap bitmap = pasterGenerator.createTextBitmap();
setImageBitmap(bitmap);
int subtitleWidth = mSubtitlePanelView.getTextViewWidth();
if (bitmap != null) {
float scale = Math.max(bitmap.getHeight() * 1.0f / getSubtitleMaxHeight(),
bitmap.getWidth() * 1.0f / subtitleWidth);
scale = Math.max(scale, pasterGenerator.getScale());
if (mTuiDataStatus.get() != STATUS_EDIT) {
setImageScale(1.0f / scale);
}
}
}
private void onDeleted() {
mFloatLayerViewGroup.removeFloatLayerView(this);
}
private void addObserve() {
mTuiDataStatus.observe(mOnStatusChanged);
}
private void removeObserve() {
mTuiDataStatus.removeObserver(mOnStatusChanged);
}
private int getSubtitleMaxHeight() {
return (int) (TUIMultimediaResourceUtils.getScreenSize(TUIMultimediaPlugin.getAppContext()).y * 2 / 3);
}
}
@@ -0,0 +1,120 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import java.util.ArrayList;
import java.util.List;
public class SubtitlePasterGenerator {
private static final float TEXT_BITMAP_SCALE = 4.0f;
private static final int LINE_SPACING_DP = 2;
private final String TAG = SubtitlePasterGenerator.class.getSimpleName() + "_" + hashCode();
private Bitmap mPasterBitmap;
private Paint mPaint;
private SubtitleInfo mParams;
public void setSubtitleInfo(@NonNull SubtitleInfo params) {
mParams = params;
if (mPasterBitmap != null && mPasterBitmap.isRecycled()) {
return;
}
initPaint();
if (mPasterBitmap == null && !mParams.getText().isEmpty()) {
int lineTextHeight = (int) (getFontHeight() +
TUIMultimediaResourceUtils.dip2px(TUIMultimediaPlugin.getAppContext(), LINE_SPACING_DP));
int height = lineTextHeight * mParams.textList.size();
int width = 0;
for (String text : mParams.textList) {
int lineWidth = (int) mPaint.measureText(text);
if (lineWidth > width) {
width = lineWidth;
}
}
LiteavLog.i(TAG, "subtitle bitmap width = " + width + " height = " + height);
mPasterBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
}
public float getScale() {
return TEXT_BITMAP_SCALE;
}
@Nullable
public Bitmap createTextBitmap() {
if (mPasterBitmap == null || mPasterBitmap.isRecycled()) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(
mPasterBitmap.getWidth(), mPasterBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(mPasterBitmap, 0, 0, mPaint);
drawText(canvas);
if (!mPasterBitmap.isRecycled()) {
mPasterBitmap.recycle();
mPasterBitmap = null;
}
return bitmap;
}
private void drawText(@NonNull Canvas canvas) {
List<TextParams> list = locateText();
for (TextParams params : list) {
canvas.drawText(params.text, params.x, params.y, mPaint);
}
}
@NonNull
private List<TextParams> locateText() {
List<TextParams> list = new ArrayList<>();
List<String> text = mParams.textList;
float baseX = 0;
float baseY = getFontBaseHeight()
- TUIMultimediaResourceUtils.dip2px(TUIMultimediaPlugin.getAppContext(), LINE_SPACING_DP) * 1.0f / 2;
int lineTextHeight = (int) (getFontHeight() +
TUIMultimediaResourceUtils.dip2px(TUIMultimediaPlugin.getAppContext(), LINE_SPACING_DP));
for (int i = 0; i < text.size(); i++) {
list.add(new TextParams(text.get(i), baseX, baseY + lineTextHeight * i));
}
return list;
}
private void initPaint() {
mPaint = new Paint();
mPaint.setColor(mParams.textColor != 0 ? mParams.textColor : Color.WHITE);
mPaint.setTextSize(mParams.textSize * TEXT_BITMAP_SCALE);
mPaint.setAntiAlias(true);
}
private float getFontHeight() {
Paint.FontMetrics metrics = new Paint.FontMetrics();
mPaint.getFontMetrics(metrics);
return metrics.bottom - metrics.top;
}
private float getFontBaseHeight() {
Paint.FontMetrics metrics = new Paint.FontMetrics();
mPaint.getFontMetrics(metrics);
return metrics.bottom - metrics.top - metrics.descent;
}
private static class TextParams {
public float x, y;
public String text;
public TextParams(String text, float x, float y) {
this.x = x;
this.y = y;
this.text = text;
}
}
}
@@ -0,0 +1,64 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick;
import android.app.Activity;
import android.net.Uri;
import androidx.activity.result.ActivityResultCaller;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuikit.tuichat.interfaces.AlbumPickerListener;
import com.tencent.qcloud.tuikit.tuichat.interfaces.IAlbumPicker;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaMediaProcessor;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker.AlbumPickerActivity;
import java.util.ArrayList;
public class TUIMultimediaAlbumPicker extends IAlbumPicker {
private static final String TAG = "TUIMultimediaAlbumPicker";
@Override
public void pickMedia(ActivityResultCaller activityResultCaller, AlbumPickerListener listener) {
if (activityResultCaller == null || listener == null) {
return;
}
TUICore.startActivityForResult(activityResultCaller, AlbumPickerActivity.class, null, result -> {
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
ArrayList<Uri> uris = result.getData().getParcelableArrayListExtra("data");
ArrayList<Uri> transcodeData = result.getData().getParcelableArrayListExtra("transcodeData");
if ((uris == null || uris.isEmpty()) && (transcodeData == null || transcodeData.isEmpty())) {
listener.onCancel();
return;
}
if (transcodeData != null && !transcodeData.isEmpty()) {
for (Uri uri : transcodeData) {
listener.onOriginalMediaPicked(uri);
}
TUIMultimediaMediaProcessor.getInstance().transcodeMedia(transcodeData, new TUIMultimediaMediaProcessor.TUIMultimediaMediaTranscodeListener() {
@Override
public void onTranscodeFinished(TUIMultimediaMediaProcessor.TranscodeResult transcodeResult) {
if (transcodeResult.errorCode == 0) {
listener.onFinished(transcodeResult.originalUri, transcodeResult.transcodeMediaUri);
} else {
listener.onFinished(transcodeResult.originalUri, null);
}
}
@Override
public void onTranscodeProgress(Uri originalUri, int transcodeProgress) {
listener.onProgress(originalUri, transcodeProgress);
}
});
}
if (uris != null && !uris.isEmpty()) {
for (Uri uri : uris) {
listener.onFinished(uri, null);
}
}
}
});
}
}
@@ -0,0 +1,94 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.permission;
import static android.Manifest.permission;
import android.Manifest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.tencent.imsdk.common.IMContext;
import com.tencent.imsdk.common.SystemUtil;
import com.tencent.qcloud.tuicore.ServiceInitializer;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.permission.PermissionCallback;
import com.tencent.qcloud.tuicore.permission.PermissionRequester;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
public class ImageVideoPermissionRequester {
private static final String TAG = "ImageVideoPermissionReq";
public static boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (PermissionRequester.newInstance(permission.READ_MEDIA_IMAGES, permission.READ_MEDIA_VIDEO).has()) {
return true;
}
}
if (Build.VERSION.SDK_INT >= 34) {
if (PermissionRequester.newInstance("android.permission.READ_MEDIA_VISUAL_USER_SELECTED").has()) {
return true;
}
}
return PermissionRequester.newInstance(permission.READ_EXTERNAL_STORAGE).has();
}
public static boolean checkVisualPermission() {
if (Build.VERSION.SDK_INT >= 34) {
return PermissionRequester.newInstance("android.permission.READ_MEDIA_VISUAL_USER_SELECTED").has();
}
return false;
}
public static void requestPermissions(PermissionCallback callback) {
String title = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_media_title, getAppName());
String description = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_media_reason);
String tip = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_media_tip, getAppName());
if (Build.VERSION.SDK_INT >= 34) {
PermissionRequester.newInstance("android.permission.READ_MEDIA_VISUAL_USER_SELECTED", permission.READ_MEDIA_IMAGES, permission.READ_MEDIA_VIDEO)
.title(title)
.description(description)
.settingsTip(tip)
.callback(callback)
.request();
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
PermissionRequester.newInstance(permission.READ_MEDIA_IMAGES, permission.READ_MEDIA_VIDEO)
.title(title)
.description(description)
.settingsTip(tip)
.callback(callback)
.request();
} else {
title = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_storage_title, getAppName());
description = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_storage_reason);
tip = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_storage_tip, getAppName());
PermissionRequester.newInstance(permission.READ_EXTERNAL_STORAGE)
.title(title)
.description(description)
.settingsTip(tip)
.callback(callback)
.request();
}
}
public static String getAppName() {
String appName = "";
Context context = ServiceInitializer.getAppContext();
PackageManager packageManager = context.getPackageManager();
try {
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
packageManager.getApplicationLabel(applicationInfo);
CharSequence labelCharSequence = applicationInfo.loadLabel(packageManager);
if (labelCharSequence != null && labelCharSequence.length() > 0) {
appName = labelCharSequence.toString();
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "getAppName exception:" + e.getMessage());
}
return appName;
}
}
@@ -0,0 +1,281 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
import com.tencent.qcloud.tuicore.permission.PermissionCallback;
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaCore;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BaseBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BucketBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.VideoBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.AlbumClickListener;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.OnSelectedBucketChangedListener;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.ui.picker.AlbumGridView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.permission.ImageVideoPermissionRequester;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.previewer.AlbumPreviewFragment;
import java.util.ArrayList;
import java.util.List;
public class AlbumPickerActivity extends AppCompatActivity {
private static final String TAG = "AlbumSelectorActivity";
private static final int DEFAULT_ANIM_DURATION = 200;
private View closeButton;
private AlbumGridView albumGridView;
private BucketListPop bucketListPop;
private View bucketView;
private TextView bucketNameTv;
private ImageView bucketNameIv;
private TextView fullImageTv;
private ImageView fullImageCheckbox;
private TextView previewButton;
private TextView sendTv;
private boolean inAnimation = false;
private boolean isFullImage = false;
private BucketBean currentBucket;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkPermissionAndLoad();
setContentView(R.layout.multimedia_plugin_picker_selector_layout);
setNavigationBar();
closeButton = findViewById(R.id.close_button);
albumGridView = findViewById(R.id.album_grid_view);
bucketView = findViewById(R.id.bucket_name_layout);
bucketNameTv = findViewById(R.id.bucket_name_tv);
bucketNameIv = findViewById(R.id.bucket_name_iv);
bucketListPop = findViewById(R.id.bucket_list_pop);
fullImageTv = findViewById(R.id.full_image_text);
fullImageCheckbox = findViewById(R.id.full_image_check_box);
previewButton = findViewById(R.id.preview_button);
sendTv = findViewById(R.id.send_button);
setOnClickListener();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
private void checkPermissionAndLoad() {
if (ImageVideoPermissionRequester.checkPermission()) {
loadImages();
} else {
ImageVideoPermissionRequester.requestPermissions(new PermissionCallback() {
@Override
public void onGranted() {
loadImages();
}
});
}
}
private void setNavigationBar() {
int color = getResources().getColor(R.color.multimedia_plugin_picker_dark_color);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().setStatusBarColor(color);
getWindow().setNavigationBarColor(color);
}
}
private void setOnClickListener() {
albumGridView.setClickListener(new AlbumClickListener() {
@Override
public void onClick(BaseBean bean) {
startPreview(bean);
}
@Override
public void onSelectChanged(BaseBean bean) {
setFooterButtonState();
}
});
bucketListPop.setOnSelectedBucketChangedListener(new OnSelectedBucketChangedListener() {
@Override
public void onSelectedBucketChanged(BucketBean bucketBean) {
currentBucket = bucketBean;
bucketNameTv.setText(bucketBean.bucketName);
albumGridView.setBucket(bucketBean);
}
});
closeButton.setOnClickListener((v) -> {
if (bucketListPop.isShowing()) {
hideBucketList();
return;
}
finish();
});
bucketView.setOnClickListener((v) -> {
if (inAnimation) {
return;
}
if (bucketListPop.isShowing()) {
hideBucketList();
} else {
showBucketList();
}
});
previewButton.setOnClickListener((v) -> {
if (albumGridView.getSelectedPhotoList().isEmpty()) {
return;
}
startPreview(null);
});
fullImageTv.setOnClickListener((v) -> toggleFullImage());
fullImageCheckbox.setOnClickListener((v) -> toggleFullImage());
sendTv.setOnClickListener((v) -> setSelectionResult());
}
private void setFooterButtonState() {
List<BaseBean> selectedSet = albumGridView.getSelectedPhotoList();
if (!selectedSet.isEmpty()) {
previewButton.setTextColor(getResources().getColor(R.color.multimedia_plugin_picker_white_color));
sendTv.setEnabled(true);
sendTv.setText(getResources().getString(R.string.multimedia_plugin_picker_send) + "(" + selectedSet.size() + ")");
previewButton.setText(getResources().getString(R.string.multimedia_plugin_picker_preview) + "(" + selectedSet.size() + ")");
} else {
previewButton.setTextColor(getResources().getColor(R.color.multimedia_plugin_picker_light_gray));
sendTv.setEnabled(false);
sendTv.setText(R.string.multimedia_plugin_picker_send);
previewButton.setText(R.string.multimedia_plugin_picker_preview);
}
}
private void startPreview(BaseBean previewBean) {
AlbumPreviewFragment fragment = new AlbumPreviewFragment();
fragment.setAlbumGridView(albumGridView);
fragment.setFullImage(this.isFullImage);
fragment.setPreviewBean(previewBean);
fragment.setBucketBean(currentBucket);
fragment.setOnSelectClickListener((v) -> setFooterButtonState());
fragment.setOnSendClickListener((v) -> setSelectionResult());
fragment.setOnFullImageClickListener((v) -> toggleFullImage());
fragment.show(getSupportFragmentManager(), "AlbumPreview");
}
private void toggleFullImage() {
this.isFullImage = !this.isFullImage;
if (isFullImage) {
fullImageCheckbox.setSelected(true);
} else {
fullImageCheckbox.setSelected(false);
}
}
private void showBucketList() {
bucketListPop.show();
startAnim();
}
private void hideBucketList() {
bucketListPop.dismiss();
startAnim();
}
private void startAnim() {
inAnimation = true;
bucketNameIv.animate().rotationBy(180).setDuration(DEFAULT_ANIM_DURATION).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationPause(Animator animation) {
inAnimation = false;
}
@Override
public void onAnimationEnd(Animator animation) {
inAnimation = false;
}
@Override
public void onAnimationCancel(Animator animation) {
inAnimation = false;
}
});
}
private void loadImages() {
TUIMultimediaCore.getBucketBeanList(new TUIValueCallback<List<BucketBean>>() {
@Override
public void onSuccess(List<BucketBean> bucketBeanList) {
if (bucketBeanList.isEmpty()) {
return;
}
currentBucket = bucketBeanList.get(0);
bucketListPop.setSelectedBucketBean(currentBucket);
bucketListPop.setData(bucketBeanList);
albumGridView.setBucket(currentBucket);
}
@Override
public void onError(int errorCode, String errorMessage) {
TUIChatLog.e(TAG, "getBucketBeanList error: " + errorMessage);
}
});
}
private void setSelectionResult() {
boolean isFullImage = this.isFullImage;
List<BaseBean> selectedList = albumGridView.getSelectedPhotoList();
if (isFullImage) {
ArrayList<Uri> originalUris = new ArrayList<>(selectedList.size());
for (BaseBean baseBean : selectedList) {
originalUris.add(baseBean.getFinalUri());
}
Intent intent = new Intent();
intent.putParcelableArrayListExtra("data", originalUris);
setResult(RESULT_OK, intent);
finish();
} else {
ArrayList<Uri> transcodeList = new ArrayList<>();
ArrayList<Uri> originalUris = new ArrayList<>();
for (BaseBean baseBean : selectedList) {
if (baseBean instanceof VideoBean) {
if (baseBean.editedUri == null) {
transcodeList.add(baseBean.uri);
} else {
originalUris.add(baseBean.editedUri);
}
} else {
originalUris.add(baseBean.getFinalUri());
}
}
Intent intent = new Intent();
intent.putParcelableArrayListExtra("data", originalUris);
intent.putParcelableArrayListExtra("transcodeData", transcodeList);
setResult(RESULT_OK, intent);
finish();
}
}
}
@@ -0,0 +1,227 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import com.bumptech.glide.Glide;
import com.tencent.qcloud.tuicore.TUIThemeManager;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BaseBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BucketBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.OnSelectedBucketChangedListener;
import java.util.List;
public class BucketListPop extends FrameLayout {
private static final int DEFAULT_ANIM_DURATION = 200;
private RecyclerView bucketList;
private BucketAdapter adapter;
private LinearLayoutManager layoutManager;
private List<BucketBean> data;
private OnSelectedBucketChangedListener onSelectedBucketChangedListener;
private int listHeight = -1;
private boolean isShowing = false;
private BucketBean bucketBean;
public BucketListPop(@NonNull Context context) {
super(context);
init();
}
public BucketListPop(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public BucketListPop(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void setSelectedBucketBean(BucketBean bucketBean) {
this.bucketBean = bucketBean;
}
public void setData(List<BucketBean> data) {
this.data = data;
adapter.notifyDataSetChanged();
}
public void setOnSelectedBucketChangedListener(OnSelectedBucketChangedListener listener) {
this.onSelectedBucketChangedListener = listener;
}
private void init() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.multimedia_plugin_picker_bucket_pop_layout, this, true);
bucketList = view.findViewById(R.id.bucket_list);
SimpleItemAnimator animator = (SimpleItemAnimator) bucketList.getItemAnimator();
if (animator != null) {
animator.setSupportsChangeAnimations(false);
}
bucketList.setItemAnimator(null);
adapter = new BucketAdapter();
layoutManager = new LinearLayoutManager(getContext());
bucketList.setAdapter(adapter);
bucketList.setLayoutManager(layoutManager);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
setAlpha(0);
}
public void show() {
setVisibility(VISIBLE);
post(() -> {
if (listHeight < 0) {
listHeight = bucketList.getHeight();
}
changeHeightWithAnim(0, listHeight, null);
isShowing = true;
this.animate().alpha(1).setDuration(DEFAULT_ANIM_DURATION).start();
});
}
public void dismiss() {
changeHeightWithAnim(bucketList.getHeight(), 0, () -> {
isShowing = false;
setVisibility(GONE);
});
this.animate().alpha(0).setDuration(DEFAULT_ANIM_DURATION).start();
}
private void changeHeightWithAnim(int from, int to, Runnable onFinished) {
ValueAnimator animator = ValueAnimator.ofInt(from, to);
animator.setDuration(DEFAULT_ANIM_DURATION);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams layoutParams = bucketList.getLayoutParams();
layoutParams.height = (int) animation.getAnimatedValue();
bucketList.setLayoutParams(layoutParams);
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
if (onFinished != null) {
onFinished.run();
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (onFinished != null) {
onFinished.run();
}
}
});
animator.start();
}
public boolean isShowing() {
return isShowing;
}
class BucketAdapter extends RecyclerView.Adapter<BucketViewHolder> {
@NonNull
@Override
public BucketViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.multimedia_plugin_picker_bucket_pop_list_item_layout, parent, false);
return new BucketViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull BucketViewHolder holder, int position) {
BucketBean bucketBean = data.get(position);
holder.bucketBean = bucketBean;
String bucketName = bucketBean.bucketName;
List<BaseBean> beanList = bucketBean.albumList;
if (BucketListPop.this.bucketBean == bucketBean) {
holder.selectedIcon.setVisibility(VISIBLE);
} else {
holder.selectedIcon.setVisibility(GONE);
}
int size;
BaseBean bean = null;
if (beanList == null || beanList.isEmpty()) {
size = 0;
} else {
size = beanList.size();
bean = beanList.get(0);
Glide.with(holder.bucketIcon).asBitmap().load(bean).into(holder.bucketIcon);
}
holder.bucketNameTv.setText(bucketName + "");
holder.bucketCountTv.setText("(" + size + ")");
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
BucketListPop.this.bucketBean = bucketBean;
if (onSelectedBucketChangedListener != null) {
onSelectedBucketChangedListener.onSelectedBucketChanged(bucketBean);
}
notifyItemRangeChanged(0, getItemCount());
dismiss();
}
});
}
@Override
public int getItemCount() {
if (data == null) {
return 0;
}
return data.size();
}
}
static class BucketViewHolder extends RecyclerView.ViewHolder {
ImageView bucketIcon;
TextView bucketNameTv;
TextView bucketCountTv;
ImageView selectedIcon;
BucketBean bucketBean;
public BucketViewHolder(@NonNull View itemView) {
super(itemView);
bucketIcon = itemView.findViewById(R.id.bucket_icon);
bucketNameTv = itemView.findViewById(R.id.bucket_name_tv);
bucketCountTv = itemView.findViewById(R.id.bucket_count_tv);
selectedIcon = itemView.findViewById(R.id.selected_icon);
Drawable drawable = selectedIcon.getDrawable();
if (drawable instanceof VectorDrawable) {
int color = itemView.getContext().getResources().getColor(
TUIThemeManager.getAttrResId(itemView.getContext(), com.tencent.qcloud.tuicore.R.attr.core_primary_color));
ColorFilter filter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
drawable.setColorFilter(filter);
}
}
}
}
@@ -0,0 +1,412 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.previewer;
import android.app.Dialog;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.VideoView;
import androidx.activity.result.ActivityResultCaller;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDialog;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BucketBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.AlbumClickListener;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.ui.picker.AlbumGridView;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.ui.previewer.SelectedPhotosView;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.utils.TUIMultimediaCoreUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaMediaProcessor;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BaseBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.ImageBean;
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.VideoBean;
import java.util.List;
public class AlbumPreviewFragment extends DialogFragment {
private static final String TAG = "AlbumPreviewActivity";
private static final int TYPE_IMAGE = 0;
private static final int TYPE_VIDEO = 1;
private View rootView;
private ImageView backButton;
private TextView titleTv;
private View selectButton;
private ImageView selectCheckbox;
private TextView editButton;
private TextView sendButton;
private ImageView fullImageCheckbox;
private View fullImageButton;
private PreviewAdapter adapter;
private ViewPager2 viewPager2;
private SelectedPhotosView selectedPhotosView;
private BaseBean currentBean;
private AlbumGridView albumGridView;
private View.OnClickListener onSendClickListener;
private View.OnClickListener onFullImageClickListener;
private View.OnClickListener onSelectClickListener;
private List<BaseBean> previewList;
private boolean isFullImage = false;
private BaseBean previewBean;
private BucketBean bucketBean;
public void setOnSendClickListener(View.OnClickListener onSendClickListener) {
this.onSendClickListener = onSendClickListener;
}
public void setOnFullImageClickListener(View.OnClickListener onFullImageClickListener) {
this.onFullImageClickListener = onFullImageClickListener;
}
public void setOnSelectClickListener(View.OnClickListener onSelectClickListener) {
this.onSelectClickListener = onSelectClickListener;
}
public void setBucketBean(BucketBean bucketBean) {
this.bucketBean = bucketBean;
}
public void setPreviewBean(BaseBean previewBean) {
this.previewBean = previewBean;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
setRetainInstance(true);
Dialog dialog = new AppCompatDialog(getContext(), R.style.AlbumTransparentPopStyle);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
Window window = dialog.getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable());
window.getDecorView().setPadding(0, 0, 0, 0);
int color = getResources().getColor(R.color.multimedia_plugin_picker_dark_color);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(color);
window.setNavigationBarColor(color);
}
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(lp);
}
return dialog;
}
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.multimedia_plugin_picker_preview_layout, container, false);
backButton = rootView.findViewById(R.id.back_button);
backButton.getDrawable().setAutoMirrored(true);
titleTv = rootView.findViewById(R.id.preview_title);
selectedPhotosView = rootView.findViewById(R.id.selected_list);
selectButton = rootView.findViewById(R.id.select_button);
selectCheckbox = rootView.findViewById(R.id.select_checkbox);
sendButton = rootView.findViewById(R.id.send_button);
fullImageCheckbox = rootView.findViewById(R.id.full_image_checkbox);
fullImageButton = rootView.findViewById(R.id.full_image_button);
editButton = rootView.findViewById(R.id.edit_button);
setData();
setOnClickListener();
return rootView;
}
public void setAlbumGridView(AlbumGridView albumGridView) {
this.albumGridView = albumGridView;
}
public void setFullImage(boolean fullImage) {
this.isFullImage = fullImage;
}
private void setData() {
BaseBean previewBean = this.previewBean;
currentBean = previewBean;
if (previewBean == null) {
previewList = albumGridView.getSelectedPhotoList();
currentBean = previewList.get(0);
} else {
previewList = bucketBean.albumList;
}
adapter = new PreviewAdapter();
adapter.data = previewList;
viewPager2 = rootView.findViewById(R.id.view_pager);
viewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
viewPager2.setAdapter(adapter);
int index = previewList.indexOf(previewBean);
titleTv.setText(index + 1 + "/" + previewList.size());
viewPager2.setCurrentItem(index, false);
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
titleTv.setText(position + 1 + "/" + previewList.size());
currentBean = previewList.get(position);
selectedPhotosView.switchPhoto(currentBean);
setSelectCheckboxBg();
}
});
selectedPhotosView.setPhotoList(albumGridView.getSelectedPhotoList());
selectedPhotosView.switchPhoto(currentBean);
selectedPhotosView.setAlbumClickListener(new AlbumClickListener() {
@Override
public void onClick(BaseBean bean) {
switchToPage(bean);
}
@Override
public void onSelectChanged(BaseBean bean) {
}
});
setFullImageCheckboxState();
setSelectCheckboxBg();
}
private void setFullImageCheckboxState() {
if (this.isFullImage) {
fullImageCheckbox.setSelected(true);
} else {
fullImageCheckbox.setSelected(false);
}
}
private void switchToPage(BaseBean bean) {
List<BaseBean> beanList = adapter.data;
int index = beanList.indexOf(bean);
if (index >= 0) {
currentBean = bean;
viewPager2.setCurrentItem(index, false);
}
}
private void setOnClickListener() {
backButton.setOnClickListener((v) -> dismiss());
selectButton.setOnClickListener((v) -> toggleSelected());
fullImageButton.setOnClickListener((v) -> toggleFullImage());
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (albumGridView.getSelectedPhotoList().isEmpty()) {
albumGridView.setSelected(currentBean, true);
}
if (onSendClickListener != null) {
onSendClickListener.onClick(v);
}
}
});
selectedPhotosView.setClickListener(new AlbumClickListener() {
@Override
public void onClick(BaseBean bean) {
if (!isAdded()) {
return;
}
switchToPage(bean);
}
@Override
public void onSelectChanged(BaseBean bean) {
}
});
editButton.setOnClickListener((v) -> {
Context context = getContext();
if (!(context instanceof ActivityResultCaller) || !isAdded()) {
return;
}
Uri editUri = currentBean.getFinalUri();
TUIMultimediaMediaProcessor.getInstance().editMedia(context, editUri, uri -> {
if (uri != null) {
for (BaseBean baseBean : previewList) {
if (baseBean.getFinalUri() == editUri) {
baseBean.editedUri = uri;
albumGridView.freshPhoto(baseBean);
selectedPhotosView.freshPhoto(baseBean);
adapter.freshPhoto(baseBean);
break;
}
}
}
});
});
}
private void toggleFullImage() {
this.isFullImage = !this.isFullImage;
setFullImageCheckboxState();
if (onFullImageClickListener != null) {
onFullImageClickListener.onClick(fullImageButton);
}
}
private void toggleSelected() {
boolean valid = TUIMultimediaCoreUtil.checkSize(currentBean.getFinalUri());
if (!valid) {
return;
}
if (currentBean.isSelected) {
albumGridView.setSelected(currentBean, false);
selectedPhotosView.removePhoto(currentBean);
} else {
albumGridView.setSelected(currentBean, true);
selectedPhotosView.addPhoto(currentBean);
}
if (onSelectClickListener != null) {
onSelectClickListener.onClick(selectButton);
}
setSelectCheckboxBg();
}
private void setSelectCheckboxBg() {
if (currentBean.isSelected) {
selectCheckbox.setBackgroundResource(R.drawable.multimedia_plugin_picker_preview_selected_checkbox_bg);
selectCheckbox.setImageResource(R.drawable.multimedia_plugin_picker_preview_selected_tick_icon);
} else {
selectCheckbox.setBackgroundResource(R.drawable.multimedia_plugin_picker_select_gray_ring_bg);
selectCheckbox.setImageResource(0);
}
List<BaseBean> selectedSet = albumGridView.getSelectedPhotoList();
if (selectedSet.isEmpty()) {
sendButton.setText(getString(R.string.multimedia_plugin_picker_send));
} else {
sendButton.setText(getString(R.string.multimedia_plugin_picker_send) + "(" + selectedSet.size() + ")");
}
if (!selectedSet.isEmpty()) {
selectedPhotosView.setVisibility(View.VISIBLE);
} else {
selectedPhotosView.setVisibility(View.GONE);
}
}
static class PreviewAdapter extends RecyclerView.Adapter<PreviewViewHolder> {
public List<BaseBean> data;
@Override
public PreviewViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == TYPE_VIDEO) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.multimedia_plugin_picker_preview_video_item_layout, parent, false);
return new PreviewVideoHolder(view);
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.multimedia_plugin_picker_preview_image_item_layout, parent, false);
return new PreviewImageHolder(view);
}
}
@Override
public void onBindViewHolder(PreviewViewHolder holder, int position) {
BaseBean bean = data.get(position);
holder.bind(bean);
}
@Override
public int getItemCount() {
if (data == null) {
return 0;
} else {
return data.size();
}
}
@Override
public int getItemViewType(int position) {
BaseBean baseBean = data.get(position);
if (baseBean instanceof VideoBean) {
return TYPE_VIDEO;
} else {
return TYPE_IMAGE;
}
}
public void freshPhoto(BaseBean bean) {
if (data != null && bean != null) {
int index = data.indexOf(bean);
if (index != -1) {
notifyItemChanged(index);
}
}
}
}
static class PreviewViewHolder extends RecyclerView.ViewHolder {
public PreviewViewHolder(View itemView) {
super(itemView);
}
public void bind(BaseBean bean) {}
}
static class PreviewImageHolder extends PreviewViewHolder {
ImageView imageView;
public PreviewImageHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_view);
}
@Override
public void bind(BaseBean bean) {
Glide.with(itemView.getContext())
.load(bean.getFinalUri())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.dontTransform()
.dontAnimate()
.into(imageView);
}
}
static class PreviewVideoHolder extends PreviewViewHolder {
VideoView videoView;
View playButton;
ImageView previewImage;
public PreviewVideoHolder(View itemView) {
super(itemView);
videoView = itemView.findViewById(R.id.video_view);
playButton = itemView.findViewById(R.id.play_button);
previewImage = itemView.findViewById(R.id.video_preview_image_view);
}
@Override
public void bind(BaseBean bean) {
itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(@NonNull View v) {}
@Override
public void onViewDetachedFromWindow(@NonNull View v) {
videoView.resume();
playButton.setVisibility(View.VISIBLE);
previewImage.setVisibility(View.VISIBLE);
}
});
Uri uri = bean.getFinalUri();
Glide.with(itemView.getContext()).load(uri).into(previewImage);
videoView.setVideoURI(uri);
playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
playButton.setVisibility(View.GONE);
previewImage.setVisibility(View.GONE);
videoView.start();
}
});
}
}
}
@@ -0,0 +1,25 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFullScreen;
public class TUIMultimediaRecordActivity extends TUIMultimediaFullScreen {
private final String TAG = TUIMultimediaRecordActivity.class.getSimpleName() + "_" + hashCode();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
LiteavLog.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.multimedia_plugin_record_activity);
TUIMultimediaRecordFragment fragment = new TUIMultimediaRecordFragment(this);
fragment.setArguments(getIntent().getExtras());
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fl_record_fragment_container, fragment)
.commit();
}
}
@@ -0,0 +1,359 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.liteav.beauty.TXBeautyManager;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants.VideoQuality;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
import com.tencent.rtmp.TXLiveConstants;
import com.tencent.rtmp.ui.TXCloudVideoView;
import com.tencent.ugc.TXRecordCommon;
import com.tencent.ugc.TXRecordCommon.ITXVideoRecordListener;
import com.tencent.ugc.TXRecordCommon.TXRecordResult;
import com.tencent.ugc.TXUGCRecord;
import java.io.FileOutputStream;
import java.util.Timer;
import java.util.TimerTask;
public class TUIMultimediaRecordCore {
private final static int PROCESS_TIME_INTERVAL_MS = 40;
private final static float DEFAULT_VOLUME = 0.5f;
private final static int DEFAULT_VIDEO_GOP = 3;
private final static int LOW_QUALITY_BITRATE = 1000;
private final static int MEDIUM_QUALITY_BITRATE = 3000;
private final static int HIGH_QUALITY_BITRATE = 5000;
private final static int EDIT_BITRATE = 12000;
private final static int LOW_QUALITY_FPS = 25;
private final static int MEDIUM_QUALITY_FPS = 25;
private final static int HIGH_QUALITY_FPS = 30;
private final static int LOW_QUALITY_RESOLUTION = TXRecordCommon.VIDEO_RESOLUTION_720_1280;
private final static int MEDIUM_QUALITY_RESOLUTION = TXRecordCommon.VIDEO_RESOLUTION_720_1280;
private final static int HIGH_QUALITY_RESOLUTION = TXRecordCommon.VIDEO_RESOLUTION_1080_1920;
private final static int DEFAULT_MAX_RECORD_DURATION_MS = 15000;
private final static int DEFAULT_MIN_RECORD_DURATION_MS = 2000;
private final String TAG = TUIMultimediaRecordCore.class.getSimpleName() + "_" + hashCode();
private final TXUGCRecord mRecordSDK;
private final RecordInfo mRecordInfo;
private TXBeautyManager mBeautyManager;
private Bitmap mFilterBitmap;
private VideoQuality mVideoQuality = VideoQuality.LOW;
private Boolean mIsNeedEdit = true;
private int mMaxDuration = DEFAULT_MAX_RECORD_DURATION_MS;
private int mMinDuration = DEFAULT_MIN_RECORD_DURATION_MS;
private Timer mRecordProcessTimer;
private long mRecordProgress;
private final ITXVideoRecordListener mVideoRecordListener = new ITXVideoRecordListener() {
@Override
public void onRecordEvent(int eventCode, Bundle bundle) {
LiteavLog.i(TAG, "onRecordEvent code = " + eventCode);
}
@Override
public void onRecordProgress(long progress) {
LiteavLog.i(TAG, "onRecordProgress progress = " + progress);
if (progress > 0) {
startProcessTimer(progress);
}
}
@Override
public void onRecordComplete(TXRecordResult txRecordResult) {
LiteavLog.i(TAG, "onRecordComplete retCode = " + txRecordResult);
stopProcessTimer();
if (txRecordResult.retCode == TXRecordCommon.RECORD_RESULT_OK ||
txRecordResult.retCode == TXRecordCommon.RECORD_RESULT_OK_REACHED_MAXDURATION) {
mRecordInfo.recordResult.isSuccess = true;
mRecordInfo.recordResult.type = TUIMultimediaConstants.RECORD_TYPE_VIDEO;
mRecordInfo.recordResult.path = txRecordResult.videoPath;
} else {
mRecordInfo.recordResult.isSuccess = false;
}
mRecordInfo.recordResult.code = txRecordResult.retCode;
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.STOP);
mRecordSDK.getPartsManager().deleteAllParts();
toggleTorch(false);
LiteavLog.i(TAG, "onRecordComplete finish");
}
};
public TUIMultimediaRecordCore(Context context, RecordInfo recordInfo) {
mRecordSDK = TXUGCRecord.getInstance(context);
mRecordInfo = recordInfo;
}
public void release() {
LiteavLog.i(TAG, "release");
mRecordSDK.setVideoRecordListener(null);
mRecordSDK.getPartsManager().deleteAllParts();
setFilterAndStrength(null, 0);
setBeautyStyleAndLevel(0, 0);
setWhitenessLevel(0);
setRuddyLevel(0);
mRecordSDK.release();
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.IDLE);
stopProcessTimer();
mBeautyManager = null;
}
public void startCameraPreview(TXCloudVideoView videoView) {
Log.i(TAG, "start camera preview. videoView " + videoView);
TXRecordCommon.TXUGCCustomConfig customConfig = new TXRecordCommon.TXUGCCustomConfig();
initRecordConfig(customConfig);
mRecordSDK.startCameraCustomPreview(customConfig, videoView);
mRecordSDK.setRecordSpeed(TXRecordCommon.RECORD_SPEED_NORMAL);
mRecordSDK.setHomeOrientation(TXLiveConstants.VIDEO_ANGLE_HOME_DOWN);
mRecordSDK.setRenderRotation(TXLiveConstants.RENDER_ROTATION_PORTRAIT);
mRecordSDK.setAspectRatio(TXRecordCommon.VIDEO_ASPECT_RATIO_9_16);
mRecordSDK.setVideoRenderMode(TXRecordCommon.VIDEO_RENDER_MODE_ADJUST_RESOLUTION);
mRecordSDK.setMicVolume(DEFAULT_VOLUME);
}
public void stopCameraPreview() {
LiteavLog.i(TAG, "stop camera preview");
mRecordSDK.stopCameraPreview();
}
public void startRecord(String videoFilePath) {
if (mRecordInfo.tuiDataRecordStatus.get() == RecordStatus.RECORDING) {
LiteavLog.e(TAG, "start record, but current status is recording.");
return;
}
LiteavLog.i(TAG, "start record. vide file Path is " + videoFilePath);
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.RECORDING);
mRecordSDK.setVideoRecordListener(mVideoRecordListener);
mRecordSDK.startRecord(videoFilePath, null);
}
public void stopRecord() {
LiteavLog.i(TAG, "stop record.current stats is " + mRecordInfo.tuiDataRecordStatus.get());
if (mRecordInfo.tuiDataRecordStatus.get() != RecordStatus.RECORDING) {
return;
}
mRecordSDK.stopRecord();
stopProcessTimer();
}
public void setAspectRatio(int aspectRatio) {
LiteavLog.i(TAG, "set aspect ration is " + aspectRatio);
mRecordInfo.tuiDataAspectRatio.set(aspectRatio);
mRecordSDK.setAspectRatio(aspectRatio);
}
public void takePhoto(String photoFilePath) {
LiteavLog.i(TAG, "take photo.current status is " + mRecordInfo.tuiDataRecordStatus.get());
if (mRecordInfo.tuiDataRecordStatus.get() == RecordStatus.RECORDING) {
return;
}
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.TAKE_PHOTOING);
mRecordSDK.snapshot(bitmap -> {
LiteavLog.i(TAG, "onSnapshot. snap file Path is " + photoFilePath);
toggleTorch(false);
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(photoFilePath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
mRecordInfo.recordResult.type = TUIMultimediaConstants.RECORD_TYPE_PHOTO;
mRecordInfo.recordResult.path = photoFilePath;
mRecordInfo.recordResult.isSuccess = true;
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.STOP);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
public void switchCamera(boolean isFront) {
LiteavLog.i(TAG, "switch camera. is front " + isFront);
if (isFront) {
toggleTorch(false);
}
mRecordInfo.tuiDataIsFontCamera.set(isFront);
mRecordSDK.switchCamera(isFront);
}
public boolean toggleTorch(boolean enable) {
LiteavLog.i(TAG, "toggleTorch enable is " + enable);
mRecordInfo.tuiDataIsFlashOn.set(enable);
return mRecordSDK.toggleTorch(enable);
}
public int getMaxZoom() {
return mRecordSDK.getMaxZoom();
}
public boolean setZoom(int value) {
LiteavLog.i(TAG, "set zoom value = " + value);
return mRecordSDK.setZoom(value);
}
public void setFocusPosition(float eventX, float eventY) {
LiteavLog.i(TAG, "set focus position [" + eventX + "," + eventY + "]");
mRecordSDK.setFocusPosition(eventX, eventY);
}
public void setBeautyStyleAndLevel(int style, float level) {
LiteavLog.i(TAG, "set beauty style:" + style + " level:" + level);
TXBeautyManager beautyManager = getBeautyManager();
if (beautyManager == null) {
return;
}
if (style >= 3 || style < 0) {
return;
}
beautyManager.setBeautyStyle(style);
beautyManager.setBeautyLevel(level);
}
public void setWhitenessLevel(float whitenessLevel) {
LiteavLog.i(TAG, "set whiteness level:" + whitenessLevel);
TXBeautyManager beautyManager = getBeautyManager();
if (beautyManager != null) {
beautyManager.setWhitenessLevel(whitenessLevel);
}
}
public void setRuddyLevel(float ruddyLevel) {
LiteavLog.i(TAG, "set ruddy level:" + ruddyLevel);
TXBeautyManager beautyManager = getBeautyManager();
if (beautyManager != null) {
beautyManager.setRuddyLevel(ruddyLevel);
}
}
public void setFilterAndStrength(Bitmap bitmap, int strength) {
LiteavLog.i(TAG, "set filter bitmap:" + bitmap + " strength:" + strength);
TXBeautyManager beautyManager = getBeautyManager();
if (beautyManager == null) {
return;
}
if (mFilterBitmap != bitmap) {
beautyManager.setFilter(bitmap);
mFilterBitmap = bitmap;
}
beautyManager.setFilterStrength(strength / 10.0f);
}
public void setFilter(Bitmap leftBitmap, float leftIntensity, Bitmap rightBitmap,
float rightIntensity, float leftRatio) {
LiteavLog.i(TAG, "set filter. left intensity is " + leftIntensity
+ " right intensity is " + " left ration is " + leftRatio);
mRecordSDK.setFilter(leftBitmap, leftIntensity, rightBitmap, rightIntensity, leftRatio);
mFilterBitmap = null;
}
public void setMinDuration(int duration) {
mMinDuration = duration;
}
public int getMaxDuration() {
return mMaxDuration;
}
public void setMaxDuration(int duration) {
mMaxDuration = duration;
}
public void setVideoQuality(VideoQuality videoQuality) {
LiteavLog.i(TAG, "set video quality " + videoQuality);
mVideoQuality = videoQuality;
}
public void setIsNeedEdit(Boolean isNeedEdit) {
mIsNeedEdit = isNeedEdit;
}
private void initRecordConfig(TXRecordCommon.TXUGCCustomConfig customConfig) {
setBaseVideoEncodeParamWithQuality(mVideoQuality, customConfig);
customConfig.videoGop = DEFAULT_VIDEO_GOP;
customConfig.profile = TXRecordCommon.RECORD_PROFILE_HIGH;
customConfig.maxDuration = mMaxDuration;
customConfig.minDuration = mMinDuration;
customConfig.touchFocus = false;
customConfig.needEdit = false;
customConfig.isFront = mRecordInfo.tuiDataIsFontCamera.get();
}
private void setBaseVideoEncodeParamWithQuality(VideoQuality quality,
TXRecordCommon.TXUGCCustomConfig customConfig) {
switch (quality) {
case HIGH:
customConfig.videoFps = HIGH_QUALITY_FPS;
customConfig.videoResolution = HIGH_QUALITY_RESOLUTION;
customConfig.videoBitrate = mIsNeedEdit ? EDIT_BITRATE : HIGH_QUALITY_BITRATE;
break;
case MEDIUM:
customConfig.videoFps = MEDIUM_QUALITY_FPS;
customConfig.videoResolution = MEDIUM_QUALITY_RESOLUTION;
customConfig.videoBitrate = mIsNeedEdit ? EDIT_BITRATE : MEDIUM_QUALITY_BITRATE;
break;
case LOW:
default:
customConfig.videoFps = LOW_QUALITY_FPS;
customConfig.videoResolution = LOW_QUALITY_RESOLUTION;
customConfig.videoBitrate = mIsNeedEdit ? EDIT_BITRATE : LOW_QUALITY_BITRATE;
break;
}
}
private void startProcessTimer(long progress) {
if (mRecordProcessTimer != null) {
return;
}
mRecordProgress = progress;
mRecordProcessTimer = new Timer();
mRecordProcessTimer.schedule(new TimerTask() {
@Override
public void run() {
mRecordProgress += PROCESS_TIME_INTERVAL_MS;
mRecordInfo.tuiDataRecordProcess.set(mRecordProgress * 1.0f / mMaxDuration);
}
}, PROCESS_TIME_INTERVAL_MS, PROCESS_TIME_INTERVAL_MS);
}
private void stopProcessTimer() {
if (mRecordProcessTimer != null) {
mRecordProcessTimer.cancel();
mRecordProcessTimer = null;
}
}
private TXBeautyManager getBeautyManager() {
if (mBeautyManager != null) {
return mBeautyManager;
}
if (mRecordSDK == null) {
return null;
}
mBeautyManager = mRecordSDK.getBeautyManager();
if (mBeautyManager != null) {
mBeautyManager.setBeautyStyle(TXBeautyManager.TXBeautyStyleSmooth);
}
return mBeautyManager;
}
}
@@ -0,0 +1,180 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPictureEditFragment;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaVideoEditFragment;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordResult;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.RecordFunctionView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.RecordVideoView;
import com.tencent.ugc.TXRecordCommon;
public class TUIMultimediaRecordFragment extends Fragment {
private final String TAG = TUIMultimediaRecordFragment.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaRecordCore mTUIMultimediaRecordCore;
private final RecordInfo mRecordInfo;
private RecordVideoView mRecordVideoView;
private RecordFunctionView mRecordFunctionView;
private RelativeLayout mVideoViewContainer;
private RelativeLayout mRecordFunctionContainer;
private boolean isFlashOnWhenPause;
private final TUIMultimediaDataObserver<RecordStatus> mRecordStatusObserver = new TUIMultimediaDataObserver<RecordStatus>() {
@Override
public void onChanged(RecordStatus recordStatus) {
LiteavLog.i(TAG, "record status onChanged. current status is " + recordStatus
+ " record result : " + mRecordInfo.recordResult.isSuccess);
if (recordStatus == RecordStatus.STOP && mRecordInfo.recordResult.isSuccess) {
editRecordFile(mRecordInfo.recordResult);
}
}
};
public TUIMultimediaRecordFragment(Context context) {
mContext = context;
mRecordInfo = new RecordInfo();
mTUIMultimediaRecordCore = new TUIMultimediaRecordCore(context, mRecordInfo);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initParameters();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.multimedia_plugin_record_fragment, container, false);
mVideoViewContainer = contentView.findViewById(R.id.rl_video_view_container);
mRecordFunctionContainer = contentView.findViewById(R.id.rl_function_view_container);
addObserver();
addChildView();
return contentView;
}
@Override
public void onDestroyView() {
LiteavLog.i(TAG, "onDestroyView");
super.onDestroyView();
removeChildView();
removeObserver();
}
@Override
public void onDestroy() {
LiteavLog.i(TAG, "onDestroy");
super.onDestroy();
mTUIMultimediaRecordCore.release();
}
@Override
public void onResume() {
super.onResume();
mTUIMultimediaRecordCore.toggleTorch(isFlashOnWhenPause);
}
@Override
public void onPause() {
super.onPause();
isFlashOnWhenPause = mRecordInfo.tuiDataIsFlashOn.get();
mTUIMultimediaRecordCore.toggleTorch(false);
}
private void addObserver() {
mRecordInfo.tuiDataRecordStatus.observe(mRecordStatusObserver);
}
private void removeObserver() {
mRecordInfo.tuiDataRecordStatus.removeObserver(mRecordStatusObserver);
}
private void initParameters() {
Bundle arguments = getArguments();
if (arguments != null) {
mRecordInfo.recodeType = arguments.getInt(TUIMultimediaConstants.PARAM_NAME_RECORD_TYPE,
TUIMultimediaConstants.RECORD_TYPE_VIDEO);
}
mTUIMultimediaRecordCore.setMaxDuration(TUIMultimediaIConfig.getInstance().getMaxRecordDurationMs());
mTUIMultimediaRecordCore.setMinDuration(TUIMultimediaIConfig.getInstance().getMinRecordDurationMs());
mTUIMultimediaRecordCore.setVideoQuality(TUIMultimediaIConfig.getInstance().getVideoQuality());
mTUIMultimediaRecordCore.setIsNeedEdit(true);
}
private void removeChildView() {
mVideoViewContainer.removeAllViews();
mRecordFunctionContainer.removeAllViews();
}
private void addChildView() {
mVideoViewContainer.removeAllViews();
if (mRecordVideoView == null) {
mRecordVideoView = new RecordVideoView(mContext, mTUIMultimediaRecordCore, mRecordInfo);
}
mVideoViewContainer
.addView(mRecordVideoView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mRecordFunctionContainer.removeAllViews();
if (mRecordFunctionView == null) {
mRecordFunctionView = new RecordFunctionView(mContext, mTUIMultimediaRecordCore, mRecordInfo);
}
mRecordFunctionContainer
.addView(mRecordFunctionView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
private void editRecordFile(RecordResult recordResult) {
LiteavLog.i(TAG,"editRecordFile file path: " + recordResult.path);
Fragment fragment;
if (recordResult.type == TUIMultimediaConstants.RECORD_TYPE_PHOTO) {
fragment = new TUIMultimediaPictureEditFragment(mContext);
} else {
fragment = new TUIMultimediaVideoEditFragment(mContext);
}
Bundle bundle = new Bundle();
bundle.putString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH, recordResult.path);
bundle.putFloat(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_RATIO,
convertAspectRation(mRecordInfo.tuiDataAspectRatio.get()));
bundle.putBoolean(TUIMultimediaConstants.PARAM_NAME_IS_RECODE_FILE, true);
fragment.setArguments(bundle);
((AppCompatActivity) mContext).getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fl_record_fragment_container, fragment)
.addToBackStack(null).commit();
}
private float convertAspectRation(int aspectRatio) {
switch (aspectRatio) {
case TXRecordCommon.VIDEO_ASPECT_RATIO_16_9:
return 16.0f / 9.0f;
case TXRecordCommon.VIDEO_ASPECT_RATIO_3_4:
return 3.0f / 4.0f;
case TXRecordCommon.VIDEO_ASPECT_RATIO_4_3:
return 4.0f / 3.0f;
case TXRecordCommon.VIDEO_ASPECT_RATIO_1_1:
return 1.0f;
case TXRecordCommon.VIDEO_ASPECT_RATIO_9_16:
default:
return 9.0f / 16.0f;
}
}
}
@@ -0,0 +1,55 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
import android.net.Uri;
import android.os.Bundle;
import androidx.activity.result.ActivityResultCaller;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuikit.timcommon.util.FileUtil;
import com.tencent.qcloud.tuikit.tuichat.interfaces.IMultimediaRecorder;
import com.tencent.qcloud.tuikit.tuichat.interfaces.MultimediaRecorderListener;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
public class TUIMultimediaRecorder extends IMultimediaRecorder {
@Override
public void openRecorder(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
Bundle bundle = new Bundle();
openMultimediaRecord(activityResultCaller, bundle, listener);
}
@Override
public void openCamera(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
Bundle bundle = new Bundle();
bundle.putInt(TUIMultimediaConstants.PARAM_NAME_RECORD_TYPE, TUIMultimediaConstants.RECORD_TYPE_PHOTO);
openMultimediaRecord(activityResultCaller, bundle, listener);
}
private void openMultimediaRecord(ActivityResultCaller activityResultCaller, Bundle bundle,MultimediaRecorderListener listener) {
if (activityResultCaller == null || listener == null) {
return;
}
TUICore.startActivityForResult(activityResultCaller, TUIMultimediaRecordActivity.class, bundle, result -> {
Bundle resultBundle = null;
if (result.getData() != null) {
resultBundle = result.getData().getExtras();
}
String recordPath = null;
if (resultBundle != null) {
recordPath = resultBundle.getString(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH);
}
Uri recordUri = null;
if (recordPath != null) {
recordUri = FileUtil.getUriFromPath(recordPath);
}
if (recordUri != null) {
listener.onSuccess(recordUri);
} else {
listener.onFailed(-1, "");
}
});
}
}
@@ -0,0 +1,72 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import java.util.List;
public class BeautyInfo {
private static final String DEFAULT_BEAUTY_DATA = "file:///asset/config/beauty_info_default_data.json";
public final TUIMultimediaData<BeautyItem> tuiDataSelectedBeautyItem = new TUIMultimediaData<>(null);
private final String TAG = BeautyInfo.class.getSimpleName() + "_" + hashCode();
@SerializedName("beauty_type_list")
private List<BeautyType> mBeautyTypeList;
public static BeautyInfo CreateDefaultBeautyInfo() {
LiteavLog.i("BeautyInfo", "create default beautyInfo");
String json = TUIMultimediaFileUtil.readTextFromFile(DEFAULT_BEAUTY_DATA);
Gson gson = new Gson();
return gson.fromJson(json, BeautyInfo.class);
}
public BeautyType getBeautyType(int typeIndex) {
if (mBeautyTypeList == null || typeIndex >= mBeautyTypeList.size()) {
return null;
}
return mBeautyTypeList.get(typeIndex);
}
public void setSelectedItemIndex(int typeIndex, int itemIndex) {
LiteavLog.i(TAG, "set selected item index.type index = " + typeIndex + " item index = " + itemIndex);
BeautyType BeautyType = getBeautyType(typeIndex);
if (BeautyType == null) {
return;
}
BeautyType.setSelectedItemIndex(itemIndex);
BeautyItem beautyItem = BeautyType.getSelectBeautyItem();
if (beautyItem != tuiDataSelectedBeautyItem.get()) {
tuiDataSelectedBeautyItem.set(beautyItem);
}
}
public int getItemTypeIndex(BeautyItem beautyItem) {
for (int i = 0; i < mBeautyTypeList.size(); i++) {
if (mBeautyTypeList.get(i).isContainItem(beautyItem)) {
return i;
}
}
return 0;
}
public int getBeautyTypeSize() {
return mBeautyTypeList != null ? mBeautyTypeList.size() : 0;
}
public int getItemSize(int typeIndex) {
BeautyType beautyType = getBeautyType(typeIndex);
return beautyType != null ? beautyType.getItemSize() : 0;
}
public int getTypeIndexWithType(BeautyInnerType beautyType) {
if (beautyType == BeautyInnerType.BEAUTY_FILTER) {
return 1;
} else {
return 0;
}
}
}
@@ -0,0 +1,26 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
public enum BeautyInnerType {
BEAUTY_NONE(0),
BEAUTY_SMOOTH(10),
BEAUTY_NATURAL(11),
BEAUTY_PITU(12),
BEAUTY_WHITE(13),
BEAUTY_RUDDY(14),
BEAUTY_FILTER(20);
private final int mValue;
BeautyInnerType(int value) {
this.mValue = value;
}
public static BeautyInnerType fromInteger(int x) {
for (BeautyInnerType type : BeautyInnerType.values()) {
if (type.mValue == x) {
return type;
}
}
return BEAUTY_NONE;
}
}
@@ -0,0 +1,66 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.TypedValue;
import com.google.gson.annotations.SerializedName;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
public class BeautyItem {
@SerializedName("item_type")
private int mItemType;
@SerializedName("item_level")
private int mItemLevel = 5;
@SerializedName("item_name")
private String mItemName;
@SerializedName("item_icon_normal")
private String mItemIconResName;
@SerializedName("item_filter_bitmap")
private String mFilterBitmapResource;
private Bitmap mFilterBitmap;
public Bitmap getFilterBitmap() {
if (mFilterBitmapResource == null || mFilterBitmapResource.isEmpty() || !mFilterBitmapResource
.startsWith("@")) {
return null;
}
if (mFilterBitmap != null) {
return mFilterBitmap;
}
TypedValue value = new TypedValue();
int resId = TUIMultimediaResourceUtils.getDrawableId(mFilterBitmapResource);
TUIMultimediaResourceUtils.getResources().openRawResource(resId, value);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inTargetDensity = value.density;
mFilterBitmap = BitmapFactory.decodeResource(TUIMultimediaResourceUtils.getResources(), resId, opts);
return mFilterBitmap;
}
public int getLevel() {
return mItemLevel;
}
public void setLevel(int level) {
mItemLevel = level;
}
public String getName() {
if (mItemName.startsWith(TUIMultimediaResourceUtils.STRING_RESOURCE_PREFIX)) {
return TUIMultimediaResourceUtils.getString(mItemName);
}
return mItemName;
}
public BeautyInnerType getInnerType() {
return BeautyInnerType.fromInteger(mItemType);
}
public String getIconResName() {
return mItemIconResName;
}
}
@@ -0,0 +1,55 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
import com.google.gson.annotations.SerializedName;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import java.util.LinkedList;
import java.util.List;
public class BeautyType {
@SerializedName("beauty_type_name")
private String mTabName;
@SerializedName("beauty_item_list")
private List<BeautyItem> mBeautyItemList = new LinkedList<>();
private int mSelectedItemIndex = 1;
public BeautyItem getSelectBeautyItem() {
if (mBeautyItemList == null || mSelectedItemIndex >= mBeautyItemList.size()) {
return null;
}
return mBeautyItemList.get(mSelectedItemIndex);
}
public String getName() {
return TUIMultimediaResourceUtils.getString(mTabName);
}
public int getItemSize() {
return mBeautyItemList != null ? mBeautyItemList.size() : 0;
}
public BeautyItem getItem(int index) {
if (mBeautyItemList == null || index >= mBeautyItemList.size()) {
return null;
}
return mBeautyItemList.get(index);
}
public int getSelectedItemIndex() {
return mSelectedItemIndex;
}
public void setSelectedItemIndex(int index) {
mSelectedItemIndex = index;
}
public BeautyItem getSelectedItem() {
return getItem(mSelectedItemIndex);
}
public boolean isContainItem(BeautyItem item) {
return mBeautyItemList != null && mBeautyItemList.contains(item);
}
}
@@ -0,0 +1,30 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.ugc.TXRecordCommon;
public class RecordInfo {
public TUIMultimediaData<RecordStatus> tuiDataRecordStatus = new TUIMultimediaData<>(RecordStatus.IDLE);
public TUIMultimediaData<Float> tuiDataRecordProcess = new TUIMultimediaData<>(0.0f);
public TUIMultimediaData<Integer> tuiDataAspectRatio = new TUIMultimediaData<>(TXRecordCommon.VIDEO_ASPECT_RATIO_9_16);
public TUIMultimediaData<Boolean> tuiDataIsFontCamera = new TUIMultimediaData<>(false);
public TUIMultimediaData<Boolean> tuiDataIsFlashOn = new TUIMultimediaData<>(false);
public TUIMultimediaData<Boolean> tuiDataShowBeautyView = new TUIMultimediaData<>(false);
public RecordResult recordResult = new RecordResult();
public BeautyInfo beautyInfo;
public int recodeType = TUIMultimediaConstants.RECORD_TYPE_VIDEO;
public enum RecordStatus {
IDLE, STOP, RECORDING,TAKE_PHOTOING
}
static public class RecordResult {
public String path;
public int type;
public boolean isSuccess;
public int code;
}
}
@@ -0,0 +1,78 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.ColorInt;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import org.jetbrains.annotations.Nullable;
public class ProgressRingView extends View {
private static final int PAINT_WIDTH = 10;
private static final int ZERO_PROGRESS_PAINT_WIDTH = 4;
private static final int TOTAL_RING_ANGEL = 360;
private static final int RING_START_ANGEL = -90;
private float mProgress = 0;
private RectF mRectF;
private Paint mProgressPaint;
private Paint mBackgroundPaint;
private int mProgressColor = Color.BLUE;
public ProgressRingView(Context context) {
super(context);
init();
}
public ProgressRingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mProgressPaint = new Paint();
mProgressPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setColor(TUIMultimediaIConfig.getInstance().getThemeColor());
mBackgroundPaint = new Paint();
mBackgroundPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
mBackgroundPaint.setStyle(Paint.Style.STROKE);
mBackgroundPaint.setAntiAlias(true);
mRectF = new RectF();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mProgress > 0) {
mProgressPaint.setStrokeWidth(PAINT_WIDTH);
mBackgroundPaint.setStrokeWidth(PAINT_WIDTH);
mBackgroundPaint.setColor(Color.GRAY);
} else {
mProgressPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
mBackgroundPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
mBackgroundPaint.setColor(Color.WHITE);
}
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = Math.min(centerX, centerY) - PAINT_WIDTH / 2;
mRectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
canvas.drawOval(mRectF, mBackgroundPaint);
canvas.drawArc(mRectF, RING_START_ANGEL, mProgress * TOTAL_RING_ANGEL, false, mProgressPaint);
}
public void setProgress(float progress) {
mProgress = progress;
postInvalidate();
}
}
@@ -0,0 +1,74 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
import com.tencent.ugc.TXRecordCommon;
@SuppressLint("ViewConstructor")
public class RecordAspectView extends RelativeLayout {
private final String TAG = RecordAspectView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaRecordCore mRecordCore;
private final RecordInfo mRecordInfo;
private ImageView mImageAspectCurr;
private int mFirstAspect;
public RecordAspectView(Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
super(context);
mContext = context;
mRecordCore = recordCore;
mRecordInfo = recordInfo;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
removeAllViews();
}
private void initView() {
View root = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_aspect_view, this, true);
mImageAspectCurr = findViewById(R.id.iv_aspect);
selectAnotherAspect(mRecordInfo.tuiDataAspectRatio.get());
root.setOnClickListener(v -> selectAnotherAspect(mFirstAspect));
}
private void selectAnotherAspect(int targetScale) {
mRecordCore.setAspectRatio(targetScale);
switch (targetScale) {
case TXRecordCommon.VIDEO_ASPECT_RATIO_9_16:
mImageAspectCurr.setImageResource(R.drawable.multimedia_plugin_record_aspect_916);
mFirstAspect = TXRecordCommon.VIDEO_ASPECT_RATIO_3_4;
break;
case TXRecordCommon.VIDEO_ASPECT_RATIO_3_4:
mImageAspectCurr.setImageResource(R.drawable.multimedia_plugin_record_aspect_34);
mFirstAspect = TXRecordCommon.VIDEO_ASPECT_RATIO_9_16;
break;
// case TXRecordCommon.VIDEO_ASPECT_RATIO_FULL_SRCREEN:
// mImageAspectCurr.setImageResource(R.drawable.multimedia_plugin_ic_aspect_11);
// mFirstAspect = TXRecordCommon.VIDEO_ASPECT_RATIO_9_16;
// break;
default:
break;
}
}
}
@@ -0,0 +1,133 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
public class RecordButtonView extends RelativeLayout implements View.OnTouchListener {
private final Context mContext;
private View mButtonInnerView;
private ProgressRingView mButtonOuterProgressRingView;
private OnTouchListener mOnTouchListener;
private boolean mIsOnlySupportTakePhoto = false;
public RecordButtonView(Context context) {
super(context);
mContext = context;
initViews();
}
public RecordButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initViews();
}
public RecordButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initViews();
}
public void setIsOnlySupportTakePhoto(boolean isOnlySupportTakePhoto) {
mIsOnlySupportTakePhoto = isOnlySupportTakePhoto;
}
public void setProcess(float process) {
mButtonOuterProgressRingView.setProgress(process);
}
private void initViews() {
View rootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_start_record_button,
this, true);
mButtonOuterProgressRingView = rootView.findViewById(R.id.record_button_ring_progress_bar);
mButtonOuterProgressRingView.setVisibility(VISIBLE);
super.setOnTouchListener(this);
mButtonInnerView = findViewById(R.id.record_button_inner_circle);
}
@Override
public void setOnTouchListener(OnTouchListener listener) {
mOnTouchListener = listener;
}
@Override
public boolean performClick() {
return super.performClick();
}
@Override
public boolean onTouch(View view, @NonNull MotionEvent motionEvent) {
int action = motionEvent.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
startTakePhotoAnim();
break;
}
case MotionEvent.ACTION_UP: {
endTakePhotoAnim();
break;
}
}
if (mOnTouchListener != null) {
return mOnTouchListener.onTouch(view, motionEvent);
}
return false;
}
private void startTakePhotoAnim() {
ViewGroup rootLayout = findViewById(R.id.layout_compose_record_btn);
float scale = 1.0f;
if (!mIsOnlySupportTakePhoto) {
scale = (Math.min(rootLayout.getWidth(), rootLayout.getHeight()) - 5) * 1.0f /
Math.min(mButtonOuterProgressRingView.getWidth(), mButtonOuterProgressRingView.getHeight());
}
ObjectAnimator btnBkgZoomOutXAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleX", scale);
ObjectAnimator btnBkgZoomOutYAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleY", scale);
ObjectAnimator btnZoomInXAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleX", 0.5f);
ObjectAnimator btnZoomInYAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleY", 0.5f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(80);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.play(btnBkgZoomOutXAn)
.with(btnBkgZoomOutYAn)
.with(btnZoomInXAn)
.with(btnZoomInYAn);
animatorSet.start();
}
public void endTakePhotoAnim() {
ObjectAnimator btnBkgZoomInXAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleX", 1f);
ObjectAnimator btnBkgZoomIntYAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleY", 1f);
ObjectAnimator btnZoomInXAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleX", 1f);
ObjectAnimator btnZoomInYAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleY", 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(80);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.play(btnBkgZoomInXAn)
.with(btnBkgZoomIntYAn)
.with(btnZoomInXAn)
.with(btnZoomInYAn);
animatorSet.start();
}
}
@@ -0,0 +1,140 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Point;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
import com.tencent.ugc.TXRecordCommon;
@SuppressLint("ViewConstructor")
public class RecordFunctionView extends LinearLayout {
private final static int DEFAULT_OPERATION_VIEW_HEIGHT_DP = 215;
private final static int MIN_OPERATION_VIEW_HEIGHT_DP = 155;
private final static int DEFAULT_OPERATION_TIPS_VIEW_HEIGHT_DP = 40;
private final Context mContext;
private final TUIMultimediaRecordCore mRecordCore;
private final RecordInfo mRecordInfo;
private RecordSettingView mRecordSettingView;
private RecordOperationView mRecordOperationView;
private RelativeLayout mOperationViewContainer;
private final TUIMultimediaDataObserver<Boolean> mIsShowBeautyView = new TUIMultimediaDataObserver<Boolean>() {
@Override
public void onChanged(Boolean isShowBeautyView) {
if (mOperationViewContainer != null) {
mOperationViewContainer.setVisibility(isShowBeautyView ? GONE : VISIBLE);
}
}
};
private RelativeLayout mSettingViewContainer;
private final TUIMultimediaDataObserver<RecordStatus> mRecodeStatusOnChanged = new TUIMultimediaDataObserver<RecordStatus>() {
@Override
public void onChanged(RecordStatus recordStatus) {
if (mSettingViewContainer != null) {
mSettingViewContainer.setVisibility((recordStatus == RecordStatus.RECORDING
|| recordStatus == RecordStatus.TAKE_PHOTOING) ? GONE : VISIBLE);
}
}
};
public RecordFunctionView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
super(context);
mContext = context;
mRecordCore = recordCore;
mRecordInfo = recordInfo;
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
initView();
addObserver();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeObserver();
removeAllViews();
}
@Override
public void removeAllViews() {
if (mOperationViewContainer != null) {
mOperationViewContainer.removeAllViews();
}
if (mSettingViewContainer != null) {
mSettingViewContainer.removeAllViews();
}
super.removeAllViews();
}
public void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_function_view, this, true);
initRecordOperationView();
mSettingViewContainer = findViewById(R.id.rl_setting_view_container);
mSettingViewContainer.removeAllViews();
if (mRecordSettingView == null) {
mRecordSettingView = new RecordSettingView(mContext, mRecordCore, mRecordInfo);
}
mSettingViewContainer.addView(mRecordSettingView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
private void initRecordOperationView() {
mOperationViewContainer = findViewById(R.id.rl_operation_view_container);
mOperationViewContainer.removeAllViews();
if (mRecordOperationView == null) {
mRecordOperationView = new RecordOperationView(mContext, mRecordCore, mRecordInfo);
}
adjustRecordOperationViewPosition(mRecordInfo.tuiDataAspectRatio.get());
}
private void adjustRecordOperationViewPosition(int aspectRation) {
if (mOperationViewContainer == null || mRecordOperationView == null) {
return;
}
mOperationViewContainer.removeAllViews();
Point screenSize = TUIMultimediaResourceUtils.getScreenSize(mContext);
int viewHeight = TUIMultimediaResourceUtils.dip2px(mContext, DEFAULT_OPERATION_VIEW_HEIGHT_DP);
if (aspectRation == TXRecordCommon.VIDEO_ASPECT_RATIO_9_16) {
viewHeight = screenSize.y - screenSize.x * 16 / 9 + TUIMultimediaResourceUtils
.dip2px(mContext, DEFAULT_OPERATION_TIPS_VIEW_HEIGHT_DP);
} else if (aspectRation == TXRecordCommon.VIDEO_ASPECT_RATIO_3_4) {
viewHeight = screenSize.y / 2 - (screenSize.x * 4 / 3) / 2 + TUIMultimediaResourceUtils
.dip2px(mContext, DEFAULT_OPERATION_TIPS_VIEW_HEIGHT_DP);
}
int minViewHeight = TUIMultimediaResourceUtils.dip2px(mContext, MIN_OPERATION_VIEW_HEIGHT_DP);
viewHeight = Math.max(viewHeight, minViewHeight);
LayoutParams linearLayoutCompat = new LayoutParams(MATCH_PARENT, viewHeight);
mOperationViewContainer.addView(mRecordOperationView, linearLayoutCompat);
}
private void addObserver() {
mRecordInfo.tuiDataShowBeautyView.observe(mIsShowBeautyView);
mRecordInfo.tuiDataRecordStatus.observe(mRecodeStatusOnChanged);
mRecordInfo.tuiDataAspectRatio.observe(this::adjustRecordOperationViewPosition);
}
private void removeObserver() {
mRecordInfo.tuiDataShowBeautyView.removeObserver(mIsShowBeautyView);
mRecordInfo.tuiDataRecordStatus.removeObserver(mRecodeStatusOnChanged);
mRecordInfo.tuiDataAspectRatio.removeObserver(this::adjustRecordOperationViewPosition);
}
}
@@ -0,0 +1,204 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.tencent.imsdk.BuildConfig;
import com.tencent.imsdk.base.ThreadUtils;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaAuthorizationPrompter;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
import com.tencent.ugc.TXRecordCommon;
@SuppressLint("ViewConstructor")
public class RecordOperationView extends LinearLayout {
private final String TAG = RecordOperationView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaRecordCore mRecordCore;
private final RecordInfo mRecordInfo;
private RecordButtonView mRecordButtonView;
private TextView mRecordOperationTipsView;
private TextView mRecordTimeTextView;
private Float mLastRecordProcess = 0.0f;
private String mLastRecordTime;
private final TUIMultimediaDataObserver<RecordStatus> mRecordStatusObserver = new TUIMultimediaDataObserver<RecordStatus>() {
@Override
public void onChanged(RecordStatus recordStatus) {
if (recordStatus == RecordStatus.RECORDING || recordStatus == RecordStatus.TAKE_PHOTOING) {
findViewById(R.id.cancel_record_button).setVisibility(INVISIBLE);
findViewById(R.id.record_switch_camera).setVisibility(INVISIBLE);
hideRecordOperationTips();
} else {
mRecordButtonView.setProcess(0);
mLastRecordProcess = 0.0f;
mLastRecordTime = null;
if (recordStatus == RecordStatus.STOP && !mRecordInfo.recordResult.isSuccess
&& mRecordInfo.recordResult.code == TXRecordCommon.RECORD_RESULT_OK_LESS_THAN_MINDURATION) {
mRecordCore.takePhoto(
TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE));
} else {
findViewById(R.id.cancel_record_button).setVisibility(VISIBLE);
findViewById(R.id.record_switch_camera).setVisibility(VISIBLE);
}
}
}
};
private final TUIMultimediaDataObserver<Float> mRecordProcessObserver = new TUIMultimediaDataObserver<Float>() {
@Override
public void onChanged(Float process) {
LiteavLog.i(TAG, "record process is " + process);
if (process > mLastRecordProcess) {
mLastRecordProcess = process;
mRecordButtonView.setProcess(process);
setRecordTime(process);
}
}
};
private boolean isNeedShowOperationTipsView = true;
public RecordOperationView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
super(context);
mContext = context;
mRecordCore = recordCore;
mRecordInfo = recordInfo;
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
initView();
addObserver();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeObserver();
removeAllViews();
}
public void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_operation_view, this, true);
findViewById(R.id.record_switch_camera).setOnClickListener(v -> {
boolean is_front_camera = mRecordInfo.tuiDataIsFontCamera.get();
mRecordCore.switchCamera(!is_front_camera);
});
findViewById(R.id.cancel_record_button).setOnClickListener(v -> {
((Activity) mContext).setResult(Activity.RESULT_CANCELED, null);
((Activity) mContext).finish();
});
mRecordTimeTextView = findViewById(R.id.record_time);
mRecordOperationTipsView = findViewById(R.id.record_operation_tips);
mRecordOperationTipsView.setVisibility(isNeedShowOperationTipsView ? VISIBLE : INVISIBLE);
mRecordButtonView = findViewById(R.id.start_record_button);
if (mRecordInfo.recodeType == TUIMultimediaConstants.RECORD_TYPE_VIDEO) {
initRecordButtonForVideoRecode();
} else {
initRecordButtonForTakePhoto();
}
}
@SuppressLint("ClickableViewAccessibility")
private void initRecordButtonForVideoRecode() {
mRecordButtonView.setOnLongClickListener(v -> {
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
return false;
}
mRecordCore.startRecord(TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.RECODE_FILE));
return false;
});
mRecordButtonView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP) {
mRecordCore.stopRecord();
mRecordTimeTextView.setVisibility(INVISIBLE);
}
return false;
});
mRecordButtonView.setOnClickListener(
v -> {
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
return;
}
mRecordCore.takePhoto(
TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE));
});
mRecordOperationTipsView.setText(R.string.multimedia_plugin_record_operation_tips);
mRecordButtonView.setIsOnlySupportTakePhoto(false);
}
@SuppressLint("ClickableViewAccessibility")
private void initRecordButtonForTakePhoto() {
mRecordButtonView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.TAKE_PHOTOING);
}
return false;
});
mRecordButtonView.setOnClickListener(
v -> {
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.IDLE);
return;
}
mRecordCore.takePhoto(
TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE));
});
mRecordOperationTipsView.setText(R.string.multimedia_plugin_record_take_phone_operation_tips);
mRecordButtonView.setIsOnlySupportTakePhoto(true);
}
private void setRecordTime(float process) {
int maxDuration = mRecordCore.getMaxDuration();
String recordTime = TUIMultimediaResourceUtils.secondsToTimeString((long) (process * maxDuration));
if (recordTime.equals(mLastRecordTime)) {
return;
}
mLastRecordTime = recordTime;
ThreadUtils.getUiThreadHandler().post(() -> {
mRecordTimeTextView.setText(mLastRecordTime);
});
}
private void addObserver() {
mRecordInfo.tuiDataRecordStatus.observe(mRecordStatusObserver);
mRecordInfo.tuiDataRecordProcess.observe(mRecordProcessObserver);
}
private void removeObserver() {
mRecordInfo.tuiDataRecordStatus.removeObserver(mRecordStatusObserver);
mRecordInfo.tuiDataRecordProcess.removeObserver(mRecordProcessObserver);
}
private void hideRecordOperationTips() {
if (mRecordOperationTipsView != null) {
mRecordOperationTipsView.setVisibility(INVISIBLE);
}
isNeedShowOperationTipsView = false;
}
}
@@ -0,0 +1,245 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.content.Context;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty.BeautyFilterScrollView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty.BeautyPanelView;
public class RecordSettingView extends RelativeLayout {
private static final int SCROLL_FILTER_RIGHT_LEFT_MARGIN_SP = 15;
private static final int SETTING_ITEM_VIEW_BOTTOM_MARGIN_SP = 24;
private final String TAG = RecordSettingView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaRecordCore mRecordCore;
private RecordInfo mRecordInfo;
private BeautyPanelView mBeautyPanelView;
private BeautyFilterScrollView mBeautyFilterScrollView;
private SettingItemViewHolder mSettingItemTorch;
private RecordAspectView mAspectView;
private LinearLayout mLayoutSetting;
private RelativeLayout mBeautyPanelViewContainer;
private RelativeLayout mScrollFilterViewContainer;
private final TUIMultimediaDataObserver<Boolean> mFlashStatusObserver = isOnAndFront -> {
if (mSettingItemTorch == null) {
return;
}
LiteavLog.i(TAG,"is front camera:" + mRecordInfo.tuiDataIsFontCamera.get()
+ " is flash on: " + mRecordInfo.tuiDataIsFlashOn.get());
if (mRecordInfo.tuiDataIsFontCamera.get()) {
mSettingItemTorch.setIconRes(R.drawable.multimedia_plugin_record_torch_close_disable);
mSettingItemTorch.setClickable(false);
return;
}
int resId = mRecordInfo.tuiDataIsFlashOn.get() ? R.drawable.multimedia_plugin_record_torch_open : R.drawable.multimedia_plugin_record_torch_close;
mSettingItemTorch.setIconRes(resId);
mSettingItemTorch.setClickable(true);
};
public RecordSettingView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
super(context);
mContext = context;
mRecordCore = recordCore;
mRecordInfo = recordInfo;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
addObserver();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
removeAllViews();
removeObserver();
}
@Override
public void removeAllViews() {
if (mBeautyPanelViewContainer != null) {
mBeautyPanelViewContainer.removeAllViews();
}
if (mScrollFilterViewContainer != null) {
mScrollFilterViewContainer.removeAllViews();
}
mLayoutSetting.removeAllViews();
super.removeAllViews();
}
public void initView() {
View rootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_setting_view, this, true);
rootView.setOnClickListener(v -> {
if (mBeautyPanelView != null) {
mBeautyPanelView.setVisibility(GONE);
}
});
mLayoutSetting = findViewById(R.id.layout_setting_item);
initTorchView();
initBeautyView();
initScrollBeautyFilterView();
initAspectView();
}
private void initTorchView() {
if (!TUIMultimediaIConfig.getInstance().isSupportRecordTorch()) {
return;
}
mSettingItemTorch = addSettingItem(R.drawable.multimedia_plugin_record_torch_close, R.string.multimedia_plugin_torch);
mSettingItemTorch.setOnClickListener(v -> {
boolean isFlashOn = mRecordInfo.tuiDataIsFlashOn.get();
mRecordCore.toggleTorch(!isFlashOn);
});
}
private void initBeautyView() {
if (!TUIMultimediaIConfig.getInstance().isSupportRecordBeauty()) {
return;
}
SettingItemViewHolder mBeautyItemTorch = addSettingItem(R.drawable.multimedia_plugin_record_beauty, R.string.multimedia_plugin_beauty);
mBeautyItemTorch.setOnClickListener(v ->
mBeautyPanelView.setVisibility(!mRecordInfo.tuiDataShowBeautyView.get() ? VISIBLE : INVISIBLE));
if (mRecordInfo.beautyInfo == null) {
mRecordInfo.beautyInfo = BeautyInfo.CreateDefaultBeautyInfo();
}
if (mBeautyPanelView == null) {
mBeautyPanelView = new BeautyPanelView(getContext(), mRecordCore, mRecordInfo);
mBeautyPanelView.setVisibility(INVISIBLE);
}
mBeautyPanelViewContainer = findViewById(R.id.rl_beauty_panel_view_container);
mBeautyPanelViewContainer.removeAllViews();
mBeautyPanelViewContainer
.addView(mBeautyPanelView, new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
}
private void initScrollBeautyFilterView() {
if (!TUIMultimediaIConfig.getInstance().isSupportRecordScrollFilter()) {
return;
}
if (mRecordInfo.beautyInfo == null) {
mRecordInfo.beautyInfo = BeautyInfo.CreateDefaultBeautyInfo();
}
if (mBeautyFilterScrollView == null) {
mBeautyFilterScrollView = new BeautyFilterScrollView(mContext, mRecordCore, mRecordInfo.beautyInfo);
}
mScrollFilterViewContainer = findViewById(R.id.rl_beauty_filter_scroll_view_container);
mScrollFilterViewContainer.removeAllViews();
RelativeLayout.LayoutParams layoutParams =
new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.leftMargin = (int) TUIMultimediaResourceUtils.spToPx(mContext, SCROLL_FILTER_RIGHT_LEFT_MARGIN_SP);
layoutParams.rightMargin = (int) TUIMultimediaResourceUtils.spToPx(mContext, SCROLL_FILTER_RIGHT_LEFT_MARGIN_SP);
mScrollFilterViewContainer.addView(mBeautyFilterScrollView, layoutParams);
mBeautyFilterScrollView.setOnClickListener(v -> {
if (mBeautyPanelView != null) {
mBeautyPanelView.setVisibility(GONE);
}
});
}
private void initAspectView() {
if (!TUIMultimediaIConfig.getInstance().isSupportRecordAspect()) {
return;
}
if (mAspectView == null) {
mAspectView = new RecordAspectView(getContext(), mRecordCore, mRecordInfo);
}
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
layoutParams.bottomMargin = TUIMultimediaResourceUtils.dip2px(mContext, SETTING_ITEM_VIEW_BOTTOM_MARGIN_SP);
layoutParams.gravity = Gravity.CENTER;
mLayoutSetting.addView(mAspectView,layoutParams);
}
private SettingItemViewHolder addSettingItem(int iconResId, int titleResId) {
View view = LayoutInflater.from(mContext)
.inflate(R.layout.multimedia_plugin_record_setting_item_view, this, false);
SettingItemViewHolder settingItemViewHolder = new SettingItemViewHolder(view);
settingItemViewHolder.setIconRes(iconResId);
settingItemViewHolder.setTitle(titleResId);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
layoutParams.bottomMargin = TUIMultimediaResourceUtils.dip2px(mContext, SETTING_ITEM_VIEW_BOTTOM_MARGIN_SP);
layoutParams.gravity = Gravity.CENTER;
mLayoutSetting.addView(view,layoutParams);
return settingItemViewHolder;
}
private void addObserver() {
mRecordInfo.tuiDataIsFontCamera.observe(mFlashStatusObserver);
mRecordInfo.tuiDataIsFlashOn.observe(mFlashStatusObserver);
}
private void removeObserver() {
mRecordInfo.tuiDataIsFontCamera.removeObserver(mFlashStatusObserver);
mRecordInfo.tuiDataIsFlashOn.removeObserver(mFlashStatusObserver);
}
static class SettingItemViewHolder{
private final View mRootView;
private final ImageView mIcon;
private final TextView mTitle;
public SettingItemViewHolder(View rootView) {
mRootView = rootView;
mIcon = mRootView.findViewById(R.id.icon);
mTitle = mRootView.findViewById(R.id.title);
}
public void setIconRes(int iconResId) {
mIcon.setBackgroundResource(iconResId);
}
public void setTitle(int titleResId) {
mTitle.setText(TUIMultimediaResourceUtils.getString(titleResId));
}
public void setOnClickListener(OnClickListener onClickListener) {
mRootView.setOnClickListener(onClickListener);
}
public void setClickable(boolean clickable) {
mRootView.setClickable(clickable);
}
}
}
@@ -0,0 +1,90 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Point;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
import com.tencent.rtmp.ui.TXCloudVideoView;
import com.tencent.ugc.TXRecordCommon;
@SuppressLint("ViewConstructor")
public class RecordVideoView extends FrameLayout {
private final String TAG = RecordVideoView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaRecordCore mRecordCore;
private final RecordInfo mRecordInfo;
private TXCloudVideoView mVideoView;
private final TUIMultimediaDataObserver<Integer> mAspectRatioObserver = this::setVideoViewSize;
public RecordVideoView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
super(context);
mContext = context;
mRecordCore = recordCore;
mRecordInfo = recordInfo;
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initView();
addObserver();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
mRecordCore.stopCameraPreview();
removeObserver();
removeAllViews();
}
public void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_video_play_view, this, true);
mVideoView = findViewById(R.id.record_video_view);
setVideoViewSize(mRecordInfo.tuiDataAspectRatio.get());
mRecordCore.startCameraPreview(mVideoView);
}
public void addObserver() {
mRecordInfo.tuiDataAspectRatio.observe(mAspectRatioObserver);
}
public void removeObserver() {
mRecordInfo.tuiDataAspectRatio.removeObserver(mAspectRatioObserver);
}
private void setVideoViewSize(int aspectRatio) {
Point screenSize = TUIMultimediaResourceUtils.getScreenSize(mContext);
LiteavLog.i(TAG, "screenSize = " + screenSize);
switch (aspectRatio) {
case TXRecordCommon.VIDEO_ASPECT_RATIO_9_16:
mVideoView.setLayoutParams(new FrameLayout.LayoutParams(screenSize.x, screenSize.x * 16 / 9));
break;
case TXRecordCommon.VIDEO_ASPECT_RATIO_3_4:
FrameLayout.LayoutParams layoutParams = (LayoutParams) mVideoView.getLayoutParams();
layoutParams.width = screenSize.x;
layoutParams.height = screenSize.x * 4 / 3;
layoutParams.topMargin = (screenSize.y - layoutParams.height) / 2;
mVideoView.setLayoutParams(layoutParams);
break;
// case TXRecordCommon.VIDEO_ASPECT_RATIO_FULL_SRCREEN:
// mVideoView.setLayoutParams(new FrameLayout.LayoutParams(screenSize.x, screenSize.y));
// break;
default:
break;
}
}
}
@@ -0,0 +1,321 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInnerType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyItem;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyType;
@SuppressLint("ViewConstructor")
public class BeautyFilterScrollView extends RelativeLayout implements View.OnTouchListener {
private final String TAG = BeautyFilterScrollView.class + "_" + hashCode();
private final TUIMultimediaRecordCore mRecordCore;
private final BeautyInfo mBeautyInfo;
private final int mFilterIndex;
private final BeautyType mBeautyFilterType;
private TextView mFilterNameTextView;
private FrameLayout mMaskLayout;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private int mLeftIndex = 0;
private int mRightIndex = 1;
private int mLastLeftIndex = -1;
private int mLastRightIndex = -1;
private float mLeftBitmapRatio;
private float mMoveRatio;
private boolean mStartScroll;
private boolean mMoveRight;
private boolean mIsNeedChange;
private boolean mIsDoingAnimator;
private float mLastScaleFactor;
private float mScaleFactor;
private final OnScaleGestureListener mOnScaleGestureListener = new OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
return BeautyFilterScrollView.this.onScale(detector);
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mLastScaleFactor = detector.getScaleFactor();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
};
private Bitmap mLeftBitmap;
private Bitmap mRightBitmap;
private final OnGestureListener mOnGestureListener = new OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
mStartScroll = false;
return false;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
BeautyFilterScrollView.this.performClick();
mRecordCore.setFocusPosition(e.getX(), e.getY());
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return BeautyFilterScrollView.this.onScroll(e1, e2);
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return true;
}
};
@NonNull
private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
mIsDoingAnimator = true;
float leftRatio = (float) valueAnimator.getAnimatedValue();
mFilterNameTextView.setVisibility(View.INVISIBLE);
BeautyItem leftFilterItem = mBeautyFilterType.getItem(mLeftIndex);
BeautyItem rightFilterItem = mBeautyFilterType.getItem(mRightIndex);
float leftSpecialRatio = (leftFilterItem != null ? leftFilterItem.getLevel() : 0) / 10.f;
float rightSpecialRatio = (rightFilterItem != null ? rightFilterItem.getLevel() : 0) / 10.f;
mRecordCore.setFilter(mLeftBitmap, leftSpecialRatio, mRightBitmap, rightSpecialRatio, leftRatio);
if (leftRatio == 0 || leftRatio == 1) {
mLeftBitmapRatio = leftRatio;
mIsDoingAnimator = false;
if (mIsNeedChange) {
mIsNeedChange = false;
mBeautyInfo.setSelectedItemIndex(mFilterIndex, (leftRatio == 0 ? mRightIndex : mLeftIndex));
doTextAnimator();
}
}
}
};
public BeautyFilterScrollView(@NonNull Context context, TUIMultimediaRecordCore recordCore, BeautyInfo beautyInfo) {
super(context);
mRecordCore = recordCore;
mBeautyInfo = beautyInfo;
mFilterIndex = mBeautyInfo.getTypeIndexWithType(BeautyInnerType.BEAUTY_FILTER);
mBeautyFilterType = mBeautyInfo.getBeautyType(mFilterIndex);
}
@SuppressLint("ClickableViewAccessibility")
private void initViews() {
inflate(getContext(), R.layout.multimedia_plugin_record_scroll_filter_view, this);
mMaskLayout = findViewById(R.id.fl_filter_mask);
mMaskLayout.setOnTouchListener(this);
mFilterNameTextView = findViewById(R.id.tv_filter_name);
mFilterNameTextView.setVisibility(INVISIBLE);
mGestureDetector = new GestureDetector(getContext(), mOnGestureListener);
mScaleGestureDetector = new ScaleGestureDetector(getContext(), mOnScaleGestureListener);
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
initViews();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
super.onDetachedFromWindow();
}
@Override
public boolean onTouch(View v, @NonNull MotionEvent event) {
if (v != mMaskLayout) {
return false;
}
int pointerCount = event.getPointerCount();
if (pointerCount >= 2) {
mScaleGestureDetector.onTouchEvent(event);
return true;
} else if (pointerCount == 1) {
mGestureDetector.onTouchEvent(event);
if (mStartScroll && event.getAction() == MotionEvent.ACTION_UP) {
doFilterAnimator();
}
return true;
}
return false;
}
private void doFilterAnimator() {
LiteavLog.i(TAG, "doFilterAnimator");
int filterSelectIndex = mBeautyFilterType.getSelectedItemIndex();
ValueAnimator mFilterAnimator;
if (mMoveRatio >= 0.2f) {
mIsNeedChange = true;
if (mMoveRight) {
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 1);
} else {
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 0);
}
} else {
if (filterSelectIndex == mLeftIndex) {
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 1);
} else {
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 0);
}
}
mFilterAnimator.addUpdateListener(mAnimatorUpdateListener);
mFilterAnimator.start();
}
private ValueAnimator generateValueAnimator(float start, float end) {
ValueAnimator animator = ValueAnimator.ofFloat(start, end);
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(400);
return animator;
}
public void doTextAnimator() {
LiteavLog.i(TAG, "doTextAnimator");
BeautyItem beautyItem = mBeautyFilterType.getSelectedItem();
if (beautyItem == null) {
return;
}
LiteavLog.i(TAG, "doTextAnimator text = " + beautyItem.getName());
mFilterNameTextView.setText(beautyItem.getName());
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0.3f);
alphaAnimation.setDuration(1500);
alphaAnimation.setInterpolator(new LinearInterpolator());
alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mFilterNameTextView.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
mFilterNameTextView.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
mFilterNameTextView.startAnimation(alphaAnimation);
}
public boolean onScroll(@NonNull MotionEvent downEvent, @NonNull MotionEvent moveEvent) {
if (mIsDoingAnimator) {
return true;
}
int filterSelectIndex = mBeautyFilterType.getSelectedItemIndex();
boolean moveRight = moveEvent.getX() > downEvent.getX();
if (moveRight && filterSelectIndex == 0) {
return true;
} else if (!moveRight && filterSelectIndex == mBeautyInfo.getItemSize(mFilterIndex) - 1) {
return true;
} else {
mStartScroll = true;
if (moveRight) {
mLeftIndex = filterSelectIndex - 1;
mRightIndex = filterSelectIndex;
} else {
mLeftIndex = filterSelectIndex;
mRightIndex = filterSelectIndex + 1;
}
if (mLastLeftIndex != mLeftIndex) {
BeautyItem beautyItem = mBeautyFilterType.getItem(mLeftIndex);
mLeftBitmap = beautyItem != null ? beautyItem.getFilterBitmap() : null;
mLastLeftIndex = mLeftIndex;
}
if (mLastRightIndex != mRightIndex) {
BeautyItem beautyItem = mBeautyFilterType.getItem(mRightIndex);
mRightBitmap = beautyItem != null ? beautyItem.getFilterBitmap() : null;
mLastRightIndex = mRightIndex;
}
int width = mMaskLayout.getWidth();
float dis = moveEvent.getX() - downEvent.getX();
float leftBitmapRatio = Math.abs(dis) / (width * 1.0f);
mMoveRatio = leftBitmapRatio;
if (!moveRight) {
leftBitmapRatio = 1 - leftBitmapRatio;
}
mMoveRight = moveRight;
mLeftBitmapRatio = leftBitmapRatio;
BeautyItem leftFilterItem = mBeautyFilterType.getItem(mLeftIndex);
BeautyItem rightFilterItem = mBeautyFilterType.getItem(mRightIndex);
float leftSpecialRatio = (leftFilterItem != null ? leftFilterItem.getLevel() : 0) / 10.f;
float rightSpecialRatio = (rightFilterItem != null ? rightFilterItem.getLevel() : 0) / 10.f;
mRecordCore.setFilter(mLeftBitmap, leftSpecialRatio, mRightBitmap, rightSpecialRatio, leftBitmapRatio);
return true;
}
}
public boolean onScale(@NonNull ScaleGestureDetector detector) {
LiteavLog.i(TAG, "onScale");
int maxZoom = mRecordCore.getMaxZoom();
if (maxZoom == 0) {
Log.i(TAG, "camera not support zoom");
return false;
}
float factorOffset = detector.getScaleFactor() - mLastScaleFactor;
mScaleFactor += factorOffset;
mLastScaleFactor = detector.getScaleFactor();
if (mScaleFactor < 0) {
mScaleFactor = 0;
}
if (mScaleFactor > 1) {
mScaleFactor = 1;
}
int zoomValue = Math.round(mScaleFactor * maxZoom);
mRecordCore.setZoom(zoomValue);
return false;
}
}
@@ -0,0 +1,122 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaScrollViewAdapter;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyItem;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyType;
public class BeautyItemAdapter extends TUIMultimediaScrollViewAdapter {
private final Context mContext;
private final BeautyInfo mBeautyInfo;
private final int mTypeIndex;
private ViewHolder mLastSelectedView;
public BeautyItemAdapter(Context context, BeautyInfo beautyInfo, int typeIndex) {
mContext = context;
mBeautyInfo = beautyInfo;
mTypeIndex = typeIndex;
}
@Override
public int getCount() {
BeautyType beautyType = mBeautyInfo.getBeautyType(mTypeIndex);
return beautyType != null ? beautyType.getItemSize() : 0;
}
@Override
public BeautyItem getItem(int position) {
BeautyType beautyType = mBeautyInfo.getBeautyType(mTypeIndex);
return beautyType != null ? beautyType.getItem(position) : null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext)
.inflate(getViewResourceId(), parent, false);
holder = new ViewHolder(convertView, mContext);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
BeautyType beautyType = mBeautyInfo.getBeautyType(mTypeIndex);
BeautyItem beautyItem = beautyType != null ? beautyType.getItem(position) : null;
if (beautyItem != null) {
holder.title.setText(beautyItem.getName());
TUIMultimediaResourceUtils.setImageResource(holder.icon, beautyItem.getIconResName());
}
holder.setSelected(mSelectPosition == position && mSelectPosition != 0);
if (mSelectPosition == position) {
mLastSelectedView = holder;
}
convertView.setOnClickListener(v -> {
if (mSelectPosition != position) {
mSelectPosition = position;
holder.setSelected(mSelectPosition != 0);
mBeautyInfo.setSelectedItemIndex(mTypeIndex, position);
if (mLastSelectedView != null) {
mLastSelectedView.setSelected(false);
}
mLastSelectedView = holder;
}
});
return convertView;
}
private int getViewResourceId() {
String typeName = mBeautyInfo.getBeautyType(mTypeIndex).getName();
if (typeName.equals(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_setting_panel_filter))) {
return R.layout.multimedia_plugin_record_beauty_filter_item_view;
} else {
return R.layout.multimedia_plugin_record_beauty_item_view;
}
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private final View root;
private Context context;
private final ImageView icon;
private final TextView title;
private final ImageView background;
public ViewHolder(View itemView, Context context) {
super(itemView);
this.context = context;
root = itemView;
icon = itemView.findViewById(R.id.beauty_iv_icon);
title = itemView.findViewById(R.id.beauty_tv_title);
background = itemView.findViewById(R.id.select_background);
}
public void setSelected(boolean selected) {
if (selected) {
background.setBackground(
TUIMultimediaResourceUtils.getDrawable(context, R.drawable.multimedia_plugin_beauty_backgroud_selected,
TUIMultimediaIConfig.getInstance().getThemeColor()));
} else {
background.setBackground(null);
}
}
}
}
@@ -0,0 +1,245 @@
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Typeface;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
import com.google.android.material.tabs.TabLayout.Tab;
import com.google.android.material.tabs.TabLayoutMediator;
import com.tencent.liteav.base.util.LiteavLog;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInnerType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyItem;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyType;
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
@SuppressLint("ViewConstructor")
public class BeautyPanelView extends RelativeLayout {
private final String TAG = BeautyPanelView.class.getSimpleName() + "_" + hashCode();
private final Context mContext;
private final TUIMultimediaRecordCore mRecordCore;
private final BeautyInfo mBeautyInfo;
private final RecordInfo mRecordInfo;
private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int beautyLevel = progress * 10 / seekBar.getMax();
BeautyItem beautyItem = mBeautyInfo.tuiDataSelectedBeautyItem.get();
if (beautyItem != null) {
beautyItem.setLevel(beautyLevel);
setBeauty(beautyItem);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
private final OnTabSelectedListener onTabSelectedListener = new OnTabSelectedListener() {
@Override
public void onTabSelected(Tab tab) {
changeTabSelectedStatus(tab, true);
BeautyType beautyType = mBeautyInfo.getBeautyType(tab.getPosition());
if (beautyType == null) {
return;
}
mBeautyInfo.setSelectedItemIndex(tab.getPosition(), beautyType.getSelectedItemIndex());
}
@Override
public void onTabUnselected(Tab tab) {
changeTabSelectedStatus(tab, false);
}
@Override
public void onTabReselected(Tab tab) {
}
};
private TabLayout mBeautyTypeTabView;
private SeekBar mStrengthSettingSeekBar;
private View mLayoutStrengthSetting;
TUIMultimediaDataObserver<BeautyItem> mOnSelectedBeautyItemChanged = beautyItem -> {
updateBeautyTab(beautyItem);
updateSeekBar(beautyItem);
setBeauty(beautyItem);
};
public BeautyPanelView(Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
super(context);
mContext = context;
mRecordCore = recordCore;
mRecordInfo = recordInfo;
mBeautyInfo = recordInfo.beautyInfo;
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
mRecordInfo.tuiDataShowBeautyView.set(visibility == VISIBLE);
}
@Override
public void onAttachedToWindow() {
LiteavLog.i(TAG, "onAttachedToWindow");
super.onAttachedToWindow();
mBeautyInfo.tuiDataSelectedBeautyItem.observe(mOnSelectedBeautyItemChanged);
initView();
}
@Override
public void onDetachedFromWindow() {
LiteavLog.i(TAG, "onDetachedFromWindow");
mStrengthSettingSeekBar.setOnSeekBarChangeListener(null);
mBeautyTypeTabView.removeOnTabSelectedListener(onTabSelectedListener);
mBeautyInfo.tuiDataSelectedBeautyItem.removeObserver(mOnSelectedBeautyItemChanged);
removeAllViews();
super.onDetachedFromWindow();
}
private void initView() {
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_beauty_panel_view, this);
initBeautySettingView();
}
private void initBeautySettingView() {
mStrengthSettingSeekBar = findViewById(R.id.beauty_strength_seek_bar);
mStrengthSettingSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
mLayoutStrengthSetting = findViewById(R.id.rl_beauty_strength_setting);
ViewPager2 beautyPager = findViewById(R.id.record_view_pager);
BeautyTypeAdapter adapter = new BeautyTypeAdapter(mContext, mBeautyInfo);
beautyPager.setAdapter(adapter);
beautyPager.setUserInputEnabled(false);
mBeautyTypeTabView = findViewById(R.id.beauty_type_tab_view);
mBeautyTypeTabView.addOnTabSelectedListener(onTabSelectedListener);
mBeautyTypeTabView.setTabMode(TabLayout.MODE_SCROLLABLE);
new TabLayoutMediator(mBeautyTypeTabView, beautyPager, this::setTabText).attach();
}
private void setTabText(@NonNull Tab tab, int position) {
BeautyType beautyType = mBeautyInfo.getBeautyType(position);
TextView customTextView = new TextView(getContext());
customTextView.setGravity(Gravity.CENTER);
String text = beautyType != null ? beautyType.getName() + " " : "";
customTextView.setText(text);
customTextView.setMaxLines(1);
customTextView.setTextColor(
TUIMultimediaResourceUtils.getResources().getColor(R.color.multimedia_plugin_record_beauty_type_text_color_normal));
float textSize = TUIMultimediaResourceUtils.getResources().getDimension(R.dimen.multimedia_plugin_common_tab_text_size);
customTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
customTextView.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
tab.setCustomView(customTextView);
}
private void changeTabSelectedStatus(Tab tab, boolean isSelected) {
TextView tabTextView = (TextView) tab.getCustomView();
if (tabTextView == null) {
return;
}
int textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_normal;
int textColorFontStyle = Typeface.NORMAL;
if (isSelected) {
textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_selected;
textColorFontStyle = Typeface.BOLD;
}
tabTextView.setTextColor(
TUIMultimediaResourceUtils.getResources().getColor(textColorResourceId));
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
tabTextView.setTypeface(Typeface.create(fontName, textColorFontStyle));
}
private void updateBeautyTab(BeautyItem beautyItem) {
if (beautyItem == null) {
return;
}
int beautyTypeIndex = mBeautyInfo.getItemTypeIndex(beautyItem);
if (mBeautyTypeTabView.getSelectedTabPosition() == beautyTypeIndex) {
return;
}
Tab tab = mBeautyTypeTabView.getTabAt(beautyTypeIndex);
if (tab != null) {
tab.select();
}
}
private void updateSeekBar(BeautyItem beautyItem) {
if (beautyItem == null) {
return;
}
int level = beautyItem.getLevel();
if (level < 0) {
mLayoutStrengthSetting.setVisibility(INVISIBLE);
} else {
mLayoutStrengthSetting.setVisibility(VISIBLE);
mStrengthSettingSeekBar.setProgress(level * mStrengthSettingSeekBar.getMax() / 10);
}
}
private void setBeauty(BeautyItem beautyItem) {
if (beautyItem == null) {
return;
}
BeautyInnerType beautyType = beautyItem.getInnerType();
int level = beautyItem.getLevel();
LiteavLog.i(TAG, "set Beauty. beautyType " + beautyType
+ " beautyName " + beautyItem.getName() + " level " + level);
switch (beautyType) {
case BEAUTY_SMOOTH:
mRecordCore.setBeautyStyleAndLevel(0, level);
break;
case BEAUTY_NATURAL:
mRecordCore.setBeautyStyleAndLevel(1, level);
break;
case BEAUTY_PITU:
mRecordCore.setBeautyStyleAndLevel(2, level);
break;
case BEAUTY_WHITE:
mRecordCore.setWhitenessLevel(level);
break;
case BEAUTY_RUDDY:
mRecordCore.setRuddyLevel(level);
break;
case BEAUTY_FILTER:
mRecordCore.setFilterAndStrength(beautyItem.getFilterBitmap(), level);
break;
case BEAUTY_NONE:
mRecordCore.setWhitenessLevel(0);
mRecordCore.setRuddyLevel(0);
mRecordCore.setBeautyStyleAndLevel(1, 0);
mRecordCore.setBeautyStyleAndLevel(2, 0);
mRecordCore.setBeautyStyleAndLevel(3, 0);
break;
}
}
}

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