1:修改启动方式,
2:修改图标显示
@@ -142,13 +142,16 @@
|
|||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:screenOrientation="behind"
|
android:screenOrientation="behind"
|
||||||
android:launchMode="singleTask">
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/main_SplashThemeImage">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.xscm.action.LAUNCH_PAGE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!-- <intent-filter>-->
|
||||||
|
<!-- <action android:name="com.xscm.action.LAUNCH_PAGE" />-->
|
||||||
|
<!-- <category android:name="android.intent.category.DEFAULT" />-->
|
||||||
|
<!-- </intent-filter>-->
|
||||||
</activity>
|
</activity>
|
||||||
<!-- 配置APP ID -->
|
<!-- 配置APP ID -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package com.xscm.midi;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.PersistableBundle;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -18,6 +19,15 @@ public class LaunchPageActivity extends BaseAppCompatActivity<ActivityLaunchPage
|
|||||||
private Handler handler;
|
private Handler handler;
|
||||||
private PolicyDialog policyDialog;
|
private PolicyDialog policyDialog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
|
||||||
|
super.onCreate(savedInstanceState, persistentState);
|
||||||
|
if (!isTaskRoot()) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initData() {
|
protected void initData() {
|
||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<group android:scaleX="0.56"
|
|
||||||
android:scaleY="0.56"
|
|
||||||
android:translateX="23.76"
|
|
||||||
android:translateY="23.76">
|
|
||||||
<path android:fillColor="#3DDC84"
|
|
||||||
android:pathData="M0,0h108v108h-108z"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
||||||
android:strokeWidth="1"
|
|
||||||
android:strokeColor="#00000000" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_app.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round_app.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_app.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round_app.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_app.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round_app.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_app.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round_app.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_app.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_app.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
@@ -10,7 +10,26 @@
|
|||||||
<item name="android:windowBackground">@color/color_F9FAFA</item>
|
<item name="android:windowBackground">@color/color_F9FAFA</item>
|
||||||
<item name="android:windowTranslucentStatus">false</item>
|
<item name="android:windowTranslucentStatus">false</item>
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="main_SplashThemeImage" parent="AppTheme.NoActionBar">
|
||||||
|
<item name="android:windowFullscreen">true</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
</style>
|
||||||
|
<!--StartActivity Style 冷启动效果-->
|
||||||
|
<style name="AppTheme.NoActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:windowBackground">@mipmap/screen</item>
|
||||||
|
</style>
|
||||||
|
<!-- 在 styles.xml 中添加透明主题 -->
|
||||||
|
<style name="TransparentTheme" parent="AppTheme">
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowIsFloating">true</item>
|
||||||
|
<item name="android:backgroundDimEnabled">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="LauncherAppTheme" parent="AppTheme">
|
<style name="LauncherAppTheme" parent="AppTheme">
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.xscm.moduleutil.dialog.giftLottery;
|
package com.xscm.moduleutil.dialog.giftLottery;
|
||||||
|
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
|
|
||||||
import com.xscm.moduleutil.R;
|
import com.xscm.moduleutil.R;
|
||||||
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
|
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
|
||||||
@@ -23,6 +26,30 @@ public class TourClubDialogFragment extends BaseMvpDialogFragment<GiftLotteryPre
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initDialogStyle(Window window) {
|
||||||
|
super.initDialogStyle(window);
|
||||||
|
window.setGravity(Gravity.BOTTOM);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
Window window = getDialog().getWindow();
|
||||||
|
if (window != null) {
|
||||||
|
// 获取屏幕高度
|
||||||
|
android.util.DisplayMetrics displayMetrics = new android.util.DisplayMetrics();
|
||||||
|
requireActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||||
|
int screenHeight = displayMetrics.heightPixels;
|
||||||
|
// 设置高度为屏幕高度的100%(全屏)
|
||||||
|
int heightInPx = (int) (screenHeight * 0.79);
|
||||||
|
;
|
||||||
|
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, heightInPx);
|
||||||
|
window.setBackgroundDrawableResource(android.R.color.transparent);
|
||||||
|
|
||||||
|
// 可选:设置动画样式(从底部弹出)
|
||||||
|
window.setWindowAnimations(R.style.CommonShowDialogBottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
protected void initView() {
|
protected void initView() {
|
||||||
mBinding.tvJc.setOnClickListener(this::onClick);
|
mBinding.tvJc.setOnClickListener(this::onClick);
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -67,6 +69,8 @@ import okhttp3.ResponseBody;
|
|||||||
|
|
||||||
|
|
||||||
public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
||||||
|
private PlaybackManager playbackManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailed(int i, @Nullable String s) {
|
public void onFailed(int i, @Nullable String s) {
|
||||||
|
|
||||||
@@ -108,6 +112,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleVideoComplete() {
|
private void handleVideoComplete() {
|
||||||
// if (isDestroyed) return;
|
// if (isDestroyed) return;
|
||||||
|
|
||||||
@@ -118,9 +123,49 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
} else {
|
} else {
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
// playNextFromQueue(); // 播放下一项
|
// playNextFromQueue(); // 播放下一项
|
||||||
mainHandler.postDelayed(this::playNextFromQueue, 50);
|
currentProcessingCount = Math.max(0, currentProcessingCount - 1);
|
||||||
|
|
||||||
|
// 内存检查和清理
|
||||||
|
if (playQueue.size() % 10 == 0) { // 每处理10个动画检查一次内存
|
||||||
|
performLightCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟处理下一个,避免过于频繁
|
||||||
|
mainHandler.postDelayed(() -> {
|
||||||
|
smartCheckAndStartPlayback();
|
||||||
|
}, PROCESSING_DELAY);
|
||||||
|
// mainHandler.postDelayed(this::playNextFromQueue, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void performLightCleanup() {
|
||||||
|
// 只清理不需要的缓存,保留正在使用的资源
|
||||||
|
Runtime runtime = Runtime.getRuntime();
|
||||||
|
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
|
long maxMemory = runtime.maxMemory();
|
||||||
|
double memoryUsage = (double) usedMemory / maxMemory;
|
||||||
|
|
||||||
|
// 内存使用超过70%时进行轻量级清理
|
||||||
|
if (memoryUsage > 0.7) {
|
||||||
|
// 清理SVGA缓存中较旧的项目
|
||||||
|
if (svgaCache.size() > 1) {
|
||||||
|
// 保留最近使用的1个缓存项
|
||||||
|
String oldestKey = null;
|
||||||
|
for (String key : svgaCache.keySet()) {
|
||||||
|
if (oldestKey == null) {
|
||||||
|
oldestKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldestKey != null) {
|
||||||
|
svgaCache.remove(oldestKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建议进行垃圾回收
|
||||||
|
System.gc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoDestroy() {
|
public void onVideoDestroy() {
|
||||||
|
|
||||||
@@ -129,7 +174,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
public enum RenderType {SVGA, MP4}
|
public enum RenderType {SVGA, MP4}
|
||||||
|
|
||||||
private RenderType renderType;
|
private RenderType renderType;
|
||||||
// private ExoPlayer exoPlayer;
|
// private ExoPlayer exoPlayer;
|
||||||
// private PlayerView playerView;
|
// private PlayerView playerView;
|
||||||
private SVGAImageView svgaSurface;
|
private SVGAImageView svgaSurface;
|
||||||
private SVGAImageView svgaSurface2;
|
private SVGAImageView svgaSurface2;
|
||||||
@@ -183,30 +228,14 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
svgaSurface2.setVisibility(View.GONE);
|
svgaSurface2.setVisibility(View.GONE);
|
||||||
addView(svgaSurface2);
|
addView(svgaSurface2);
|
||||||
|
|
||||||
// // 初始化 GLSurfaceView
|
// 初始化播放管理器
|
||||||
// glSurfaceView = new GLSurfaceView(getContext());
|
playbackManager = new PlaybackManager(mainHandler);
|
||||||
// glSurfaceView.setEGLContextClientVersion(2);
|
|
||||||
// renderer = new ChannelSplitRenderer1();
|
|
||||||
// glSurfaceView.setRenderer(renderer);
|
|
||||||
// glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
|
|
||||||
// glSurfaceView.setVisibility(View.GONE); // 默认隐藏
|
|
||||||
// addView(glSurfaceView);
|
|
||||||
|
|
||||||
// 初始化 ExoPlayer
|
|
||||||
// if (!isDestroyed) {
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// exoPlayer = new ExoPlayer.Builder(getContext()).build();
|
|
||||||
// playerView.setPlayer(exoPlayer);
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// LogUtils.e("AvatarFrameView", "Failed to initialize ExoPlayer: " + e.getMessage());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (mBinding != null) {
|
if (mBinding != null) {
|
||||||
mBinding.playView.setAnimListener(this);
|
mBinding.playView.setAnimListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFileExtension(String url) {
|
private String getFileExtension(String url) {
|
||||||
if (url == null || url.isEmpty()) return "";
|
if (url == null || url.isEmpty()) return "";
|
||||||
int dotIndex = url.lastIndexOf(".");
|
int dotIndex = url.lastIndexOf(".");
|
||||||
@@ -215,6 +244,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playNextFromQueue() {
|
private void playNextFromQueue() {
|
||||||
// if (isDestroyed) return;
|
// if (isDestroyed) return;
|
||||||
// 确保在主线程中执行
|
// 确保在主线程中执行
|
||||||
@@ -233,49 +263,108 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
clearQueue();
|
clearQueue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 关键:即使 isPlaying 为 true,也要检查实际播放状态
|
// 检查是否可以开始新的播放
|
||||||
if (isPlaying && isActuallyPlaying()) {
|
if (!playbackManager.canStartNewPlayback()) {
|
||||||
Logger.d("AvatarFrameView", "Already playing, skip");
|
Logger.d("AvatarFrameView", "Max concurrent playbacks reached, waiting...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关键:即使 isPlaying 为 true,也要检查实际播放状态
|
||||||
|
// if (isPlaying && isActuallyPlaying()) {
|
||||||
|
// Logger.d("AvatarFrameView", "Already playing, skip");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
PlayItem item = playQueue.poll();
|
PlayItem item = playQueue.poll();
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
|
// 通知开始播放
|
||||||
|
playbackManager.onStartPlayback();
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
Logger.d("AvatarFrameView", "Playing item, remaining queue size: " + playQueue.size());
|
|
||||||
RenderType type = null;
|
|
||||||
String ext = getFileExtension(item.url);
|
|
||||||
if ("svga".equalsIgnoreCase(ext)) {
|
|
||||||
type = RenderType.SVGA;
|
|
||||||
} else if ("mp4".equalsIgnoreCase(ext)) {
|
|
||||||
type = RenderType.MP4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null) {
|
// 处理播放项目
|
||||||
isPlaying = false;
|
processPlayItem(item);
|
||||||
playNextFromQueue(); // 跳过无效项
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearPrevious();
|
// isPlaying = true;
|
||||||
renderType = type;
|
// Logger.d("AvatarFrameView", "Playing item, remaining queue size: " + playQueue.size());
|
||||||
mType = item.type;
|
// RenderType type = null;
|
||||||
|
// String ext = getFileExtension(item.url);
|
||||||
switch (type) {
|
// if ("svga".equalsIgnoreCase(ext)) {
|
||||||
case SVGA:
|
// type = RenderType.SVGA;
|
||||||
mBinding.playView.stopPlay();
|
// } else if ("mp4".equalsIgnoreCase(ext)) {
|
||||||
mBinding.playView.setVisibility(View.GONE);
|
// type = RenderType.MP4;
|
||||||
loadSVGA(item.url);
|
// }
|
||||||
break;
|
//
|
||||||
case MP4:
|
// if (type == null) {
|
||||||
mBinding.playView.setVisibility(View.VISIBLE);
|
// isPlaying = false;
|
||||||
downloadAndPlayMp4(item.url);
|
// playNextFromQueue(); // 跳过无效项
|
||||||
break;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// clearPrevious();
|
||||||
|
// renderType = type;
|
||||||
|
// mType = item.type;
|
||||||
|
//
|
||||||
|
// switch (type) {
|
||||||
|
// case SVGA:
|
||||||
|
// mBinding.playView.stopPlay();
|
||||||
|
// mBinding.playView.setVisibility(View.GONE);
|
||||||
|
// loadSVGA(item.url);
|
||||||
|
// break;
|
||||||
|
// case MP4:
|
||||||
|
// mBinding.playView.setVisibility(View.VISIBLE);
|
||||||
|
// downloadAndPlayMp4(item.url);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
Logger.d("AvatarFrameView", "Queue is empty, stop playing");
|
Logger.d("AvatarFrameView", "Queue is empty, stop playing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 添加统一的播放完成处理方法
|
||||||
|
private void onPlaybackComplete() {
|
||||||
|
mainHandler.post(() -> {
|
||||||
|
if (isDestroyed) return;
|
||||||
|
|
||||||
|
// 通知播放管理器播放完成
|
||||||
|
playbackManager.onFinishPlayback();
|
||||||
|
|
||||||
|
// 重置播放状态
|
||||||
|
isPlaying = false;
|
||||||
|
|
||||||
|
// 内存清理检查
|
||||||
|
if (playQueue.size() % 5 == 0) {
|
||||||
|
performLightMemoryCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续处理队列中的下一个项目
|
||||||
|
playNextFromQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPlayItem(PlayItem item) {
|
||||||
|
try {
|
||||||
|
clearPrevious();
|
||||||
|
|
||||||
|
String ext = getFileExtension(item.url);
|
||||||
|
if ("svga".equalsIgnoreCase(ext)) {
|
||||||
|
renderType = RenderType.SVGA;
|
||||||
|
mType = item.type;
|
||||||
|
mBinding.playView.setVisibility(View.GONE);
|
||||||
|
loadSVGA(item.url);
|
||||||
|
} else if ("mp4".equalsIgnoreCase(ext)) {
|
||||||
|
renderType = RenderType.MP4;
|
||||||
|
mType = item.type;
|
||||||
|
mBinding.playView.setVisibility(View.VISIBLE);
|
||||||
|
downloadAndPlayMp4(item.url);
|
||||||
|
} else {
|
||||||
|
// 不支持的格式,直接完成
|
||||||
|
handlePlaybackComplete();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "Error processing play item: " + e.getMessage());
|
||||||
|
handlePlaybackComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 添加实际播放状态检查方法
|
// 添加实际播放状态检查方法
|
||||||
private boolean isActuallyPlaying() {
|
private boolean isActuallyPlaying() {
|
||||||
if (renderType == RenderType.SVGA && svgaSurface != null) {
|
if (renderType == RenderType.SVGA && svgaSurface != null) {
|
||||||
@@ -286,6 +375,11 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// 在 AvatarFrameView 类中添加以下代码
|
||||||
|
|
||||||
|
private static final int MAX_CONCURRENT_PROCESSING = 3; // 同时处理的最大动画数
|
||||||
|
private static final int PROCESSING_DELAY = 100; // 处理间隔(毫秒)
|
||||||
|
private int currentProcessingCount = 0;
|
||||||
|
|
||||||
public void setSource(String url, int type2) {
|
public void setSource(String url, int type2) {
|
||||||
// if (isDestroyed) return;
|
// if (isDestroyed) return;
|
||||||
@@ -306,39 +400,51 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
clearQueue();
|
clearQueue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加到播放队列
|
||||||
|
playQueue.add(new PlayItem(url, type2));
|
||||||
|
Logger.d("AvatarFrameView", "Added to queue, queue size: " + playQueue.size() + ", url: " + url);
|
||||||
|
|
||||||
|
// 如果当前没有在播放,则开始播放
|
||||||
|
if (!isPlaying || !isActuallyPlaying()) {
|
||||||
|
playNextFromQueue();
|
||||||
|
}
|
||||||
|
|
||||||
// 异步处理URL解析等耗时操作
|
// 异步处理URL解析等耗时操作
|
||||||
ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<String>() {
|
// ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<String>() {
|
||||||
@Override
|
// @Override
|
||||||
public String doInBackground() throws Throwable {
|
// public String doInBackground() throws Throwable {
|
||||||
// 在后台线程进行URL有效性检查或其他预处理
|
// // 在后台线程进行URL有效性检查或其他预处理
|
||||||
if (url == null || url.isEmpty()) {
|
// if (url == null || url.isEmpty()) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
return url; // 返回处理后的URL
|
// return url; // 返回处理后的URL
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Override
|
// @Override
|
||||||
public void onSuccess(String processedUrl) {
|
// public void onSuccess(String processedUrl) {
|
||||||
if (processedUrl != null) {
|
// if (processedUrl != null) {
|
||||||
// 使用post方法确保在下一个UI循环中执行
|
// // 使用post方法确保在下一个UI循环中执行
|
||||||
mainHandler.post(() -> {
|
// mainHandler.post(() -> {
|
||||||
// 再次检查状态
|
// // 再次检查状态
|
||||||
if (!isDestroyed && !isPlaying) {
|
// if (!isDestroyed && !isPlaying) {
|
||||||
playQueue.add(new PlayItem(processedUrl, type2));
|
// playQueue.add(new PlayItem(processedUrl, type2));
|
||||||
checkAndStartPlayback();
|
//// checkAndStartPlayback();
|
||||||
} else {
|
// // 智能检查并开始播放
|
||||||
// 如果正在播放,添加到队列中
|
// smartCheckAndStartPlayback();
|
||||||
playQueue.add(new PlayItem(processedUrl, type2));
|
// } else {
|
||||||
}
|
// // 如果正在播放,添加到队列中
|
||||||
});
|
// playQueue.add(new PlayItem(processedUrl, type2));
|
||||||
}
|
// }
|
||||||
}
|
// });
|
||||||
|
// }
|
||||||
@Override
|
// }
|
||||||
public void onFail(Throwable e) {
|
//
|
||||||
LogUtils.e("Error processing gift URL: " + e.getMessage());
|
// @Override
|
||||||
}
|
// public void onFail(Throwable e) {
|
||||||
});
|
// LogUtils.e("Error processing gift URL: " + e.getMessage());
|
||||||
|
// }
|
||||||
|
// });
|
||||||
// 添加到播放队列
|
// 添加到播放队列
|
||||||
// playQueue.offer(new PlayItem(url, type2));
|
// playQueue.offer(new PlayItem(url, type2));
|
||||||
// playQueue.add(new PlayItem(url, type2));
|
// playQueue.add(new PlayItem(url, type2));
|
||||||
@@ -350,8 +456,20 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
// }
|
// }
|
||||||
// 改进播放检查逻辑
|
// 改进播放检查逻辑
|
||||||
// checkAndStartPlayback();
|
// checkAndStartPlayback();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void smartCheckAndStartPlayback() {
|
||||||
|
// 检查是否可以开始新的播放任务
|
||||||
|
if (!playQueue.isEmpty() &&
|
||||||
|
(!isPlaying || !isActuallyPlaying()) &&
|
||||||
|
currentProcessingCount < MAX_CONCURRENT_PROCESSING) {
|
||||||
|
|
||||||
|
currentProcessingCount++;
|
||||||
|
playNextFromQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkAndStartPlayback() {
|
private void checkAndStartPlayback() {
|
||||||
// 如果队列不为空且当前没有在播放,则开始播放
|
// 如果队列不为空且当前没有在播放,则开始播放
|
||||||
@@ -359,6 +477,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
playNextFromQueue();
|
playNextFromQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMemoryLow() {
|
private boolean isMemoryLow() {
|
||||||
Runtime runtime = Runtime.getRuntime();
|
Runtime runtime = Runtime.getRuntime();
|
||||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
@@ -371,6 +490,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
|
|
||||||
|
|
||||||
boolean isTxk = false;
|
boolean isTxk = false;
|
||||||
|
|
||||||
private void downloadAndPlayMp4(String url) {
|
private void downloadAndPlayMp4(String url) {
|
||||||
|
|
||||||
// 提取文件名
|
// 提取文件名
|
||||||
@@ -389,7 +509,8 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
client.newCall(request).enqueue(new Callback() {
|
client.newCall(request).enqueue(new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call call, IOException e) {
|
public void onFailure(Call call, IOException e) {
|
||||||
Log.d("sssssssssss", e.toString());
|
LogUtils.e("MP4下载失败: " + e.toString());
|
||||||
|
onPlaybackComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -402,30 +523,63 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
FileOutputStream fos = new FileOutputStream(downloadedFile);
|
FileOutputStream fos = new FileOutputStream(downloadedFile);
|
||||||
fos.write(responseBody.bytes());
|
fos.write(responseBody.bytes());
|
||||||
fos.close();
|
fos.close();
|
||||||
isTxk=true;
|
isTxk = true;
|
||||||
// 在主线程中播放动画
|
// 在主线程中播放动画
|
||||||
mainHandler.post(() -> {
|
mainHandler.post(() -> {
|
||||||
if (isTxk) {
|
playMp4File(downloadedFile);
|
||||||
mBinding.playView.setLoop(1);
|
// if (isTxk) {
|
||||||
}
|
// mBinding.playView.setLoop(1);
|
||||||
|
// }
|
||||||
mBinding.playView.startPlay(downloadedFile);
|
mBinding.playView.startPlay(downloadedFile);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
mainHandler.post(() -> onPlaybackComplete());
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e("MP4文件保存失败: " + e.getMessage());
|
||||||
|
mainHandler.post(() -> onPlaybackComplete());
|
||||||
}
|
}
|
||||||
|
}else {
|
||||||
|
LogUtils.e("MP4下载响应失败");
|
||||||
|
mainHandler.post(() -> onPlaybackComplete());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
isTxk=true;
|
isTxk = true;
|
||||||
LogUtils.e("有缓存");
|
LogUtils.e("有缓存");
|
||||||
|
playMp4File(file);
|
||||||
// 直接播放缓存文件
|
// 直接播放缓存文件
|
||||||
if (isTxk) {
|
// if (isTxk) {
|
||||||
mBinding.playView.setLoop(1);
|
// mBinding.playView.setLoop(1);
|
||||||
}
|
// }
|
||||||
mBinding.playView.startPlay(file);
|
// mBinding.playView.startPlay(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void playMp4File(File file) {
|
||||||
|
try {
|
||||||
|
if (mBinding != null && file != null && file.exists()) {
|
||||||
|
// 设置播放完成监听
|
||||||
|
mBinding.playView.setAnimListener(new MP4PlaybackCallback());
|
||||||
|
|
||||||
|
// 设置循环次数(根据mType决定)
|
||||||
|
if (mType == 1) {
|
||||||
|
mBinding.playView.setLoop(0); // 无限循环
|
||||||
|
} else {
|
||||||
|
mBinding.playView.setLoop(1); // 播放一次
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始播放
|
||||||
|
mBinding.playView.startPlay(file);
|
||||||
|
} else {
|
||||||
|
onPlaybackComplete();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e("播放MP4文件出错: " + e.getMessage());
|
||||||
|
onPlaybackComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// private void downloadAndPlayMp4(String url) {
|
// private void downloadAndPlayMp4(String url) {
|
||||||
// String filePath = PathUtils.getInternalAppCachePath() + Md5Utils.getStringMD5(url) + ".mp4";
|
// String filePath = PathUtils.getInternalAppCachePath() + Md5Utils.getStringMD5(url) + ".mp4";
|
||||||
@@ -530,12 +684,24 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 其他回调方法保持空实现或按需处理
|
// 其他回调方法保持空实现或按需处理
|
||||||
@Override public void taskStart(@NonNull DownloadTask task, @NonNull Listener1Assist.Listener1Model model) {}
|
@Override
|
||||||
@Override public void retry(@NonNull DownloadTask task, @NonNull ResumeFailedCause cause) {}
|
public void taskStart(@NonNull DownloadTask task, @NonNull Listener1Assist.Listener1Model model) {
|
||||||
@Override public void connected(@NonNull DownloadTask task, int blockCount, long currentOffset, long totalLength) {}
|
}
|
||||||
@Override public void progress(@NonNull DownloadTask task, long currentOffset, long totalLength) {}
|
|
||||||
|
@Override
|
||||||
|
public void retry(@NonNull DownloadTask task, @NonNull ResumeFailedCause cause) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connected(@NonNull DownloadTask task, int blockCount, long currentOffset, long totalLength) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void progress(@NonNull DownloadTask task, long currentOffset, long totalLength) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playMp4(File file) {
|
private void playMp4(File file) {
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
mBinding.playView.startPlay(file);
|
mBinding.playView.startPlay(file);
|
||||||
@@ -548,12 +714,17 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleSVGAComplete(SVGAVideoEntity videoItem, String url) {
|
private void handleSVGAComplete(SVGAVideoEntity videoItem, String url) {
|
||||||
// if (isDestroyed || svgaSurface == null) return;
|
if (svgaSurface == null) {
|
||||||
|
onPlaybackComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 缓存实体(使用弱引用)
|
// 缓存实体(使用弱引用)
|
||||||
|
// 缓存实体(使用弱引用)
|
||||||
|
if (svgaCache.size() < MAX_SVGA_CACHE_SIZE) {
|
||||||
svgaCache.put(url, new WeakReference<>(videoItem));
|
svgaCache.put(url, new WeakReference<>(videoItem));
|
||||||
|
}
|
||||||
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
|
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
|
||||||
svgaSurface.setImageDrawable(drawable);
|
svgaSurface.setImageDrawable(drawable);
|
||||||
svgaSurface.setCallback(new SVGACallback() {
|
svgaSurface.setCallback(new SVGACallback() {
|
||||||
@@ -563,6 +734,11 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRepeat() {
|
public void onRepeat() {
|
||||||
|
// 循环播放处理
|
||||||
|
if (mType != 1) { // 非循环播放
|
||||||
|
svgaSurface.stopAnimation(true);
|
||||||
|
onPlaybackComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -572,23 +748,35 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onFinished() {
|
public void onFinished() {
|
||||||
// if (isDestroyed) return;
|
// if (isDestroyed) return;
|
||||||
|
if (mType == 1) { // 循环播放
|
||||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
// 继续循环播放
|
||||||
mainHandler.post(() -> {
|
|
||||||
isPlaying = false;
|
|
||||||
playNextFromQueue();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
isPlaying = false;
|
onPlaybackComplete();
|
||||||
playNextFromQueue();
|
|
||||||
}
|
}
|
||||||
|
// if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
// mainHandler.post(() -> {
|
||||||
|
// isPlaying = false;
|
||||||
|
// playNextFromQueue();
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// isPlaying = false;
|
||||||
|
// playNextFromQueue();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 设置循环次数
|
||||||
|
if (mType == 1) {
|
||||||
|
svgaSurface.setLoops(0); // 无限循环
|
||||||
|
} else {
|
||||||
|
svgaSurface.setLoops(1); // 播放一次
|
||||||
|
}
|
||||||
svgaSurface.startAnimation();
|
svgaSurface.startAnimation();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.e(TAG, "Error handling SVGA completion: " + e.getMessage());
|
LogUtils.e(TAG, "Error handling SVGA completion: " + e.getMessage());
|
||||||
isPlaying = false;
|
// isPlaying = false;
|
||||||
playNextFromQueue();
|
// playNextFromQueue();
|
||||||
|
|
||||||
|
handlePlaybackComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,6 +825,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
// playNextFromQueue();
|
// playNextFromQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadNewSVGA(String url) {
|
private void loadNewSVGA(String url) {
|
||||||
// if (isDestroyed) return;
|
// if (isDestroyed) return;
|
||||||
|
|
||||||
@@ -674,6 +863,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
playNextFromQueue();
|
playNextFromQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSVGA(String url) {
|
private void loadSVGA(String url) {
|
||||||
// if (isDestroyed || svgaSurface == null) return;
|
// if (isDestroyed || svgaSurface == null) return;
|
||||||
|
|
||||||
@@ -848,6 +1038,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
// if (glSurfaceView != null) glSurfaceView.setVisibility(View.GONE);
|
// if (glSurfaceView != null) glSurfaceView.setVisibility(View.GONE);
|
||||||
// mBinding.playView.setVisibility(View.GONE);
|
// mBinding.playView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 简单的 LRU Cache 实现
|
// 简单的 LRU Cache 实现
|
||||||
private static class LruCache<K, V> extends LinkedHashMap<K, V> {
|
private static class LruCache<K, V> extends LinkedHashMap<K, V> {
|
||||||
private final int maxSize;
|
private final int maxSize;
|
||||||
@@ -862,13 +1053,14 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
return size() > maxSize;
|
return size() > maxSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 释放所有资源
|
* 释放所有资源
|
||||||
*/
|
*/
|
||||||
private void releaseResources() {
|
private void releaseResources() {
|
||||||
LogUtils.d(TAG, "Releasing all resources");
|
LogUtils.d(TAG, "Releasing all resources");
|
||||||
|
|
||||||
if (isDestroyed) return;
|
// if (isDestroyed) return;
|
||||||
// 使用异步线程处理耗时操作
|
// 使用异步线程处理耗时操作
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -922,6 +1114,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
// LogUtils.e(TAG, "Error in releaseResources: " + e.getMessage());
|
// LogUtils.e(TAG, "Error in releaseResources: " + e.getMessage());
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在后台线程执行耗时的清理操作
|
* 在后台线程执行耗时的清理操作
|
||||||
*/
|
*/
|
||||||
@@ -993,34 +1186,16 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
try {
|
try {
|
||||||
// 清空播放队列
|
// 清空播放队列
|
||||||
clearQueue();
|
clearQueue();
|
||||||
|
// 清理播放管理器
|
||||||
|
if (playbackManager != null) {
|
||||||
|
playbackManager.reset();
|
||||||
|
}
|
||||||
// 释放所有资源
|
// 释放所有资源
|
||||||
releaseResources();
|
releaseResources();
|
||||||
|
|
||||||
// 延迟清理 ExoPlayer(避免主线程阻塞)
|
|
||||||
// mainHandler.postDelayed(() -> {
|
|
||||||
// if (exoPlayer != null) {
|
|
||||||
// try {
|
|
||||||
// exoPlayer.release();
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// Logger.e(TAG, "Error releasing ExoPlayer: " + e.getMessage());
|
|
||||||
// }
|
|
||||||
// exoPlayer = null;
|
|
||||||
// }
|
|
||||||
// }, 50);
|
|
||||||
|
|
||||||
// 延迟清理其他资源
|
// 延迟清理其他资源
|
||||||
mainHandler.postDelayed(() -> {
|
mainHandler.postDelayed(() -> {
|
||||||
// 清理 PlayerView
|
|
||||||
// if (playerView != null) {
|
|
||||||
// try {
|
|
||||||
// playerView.setPlayer(null);
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// Logger.e(TAG, "Error releasing PlayerView: " + e.getMessage());
|
|
||||||
// }
|
|
||||||
// playerView = null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 清理 binding
|
// 清理 binding
|
||||||
if (mBinding != null) {
|
if (mBinding != null) {
|
||||||
mBinding = null;
|
mBinding = null;
|
||||||
@@ -1028,7 +1203,6 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 清理 binding
|
// 清理 binding
|
||||||
if (mBinding != null) {
|
if (mBinding != null) {
|
||||||
mBinding = null;
|
mBinding = null;
|
||||||
@@ -1042,13 +1216,42 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
// MemoryOptimizationUtils.forceGC();
|
// MemoryOptimizationUtils.forceGC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearQueue() {
|
public void clearQueue() {
|
||||||
// if (isDestroyed) return;
|
// if (isDestroyed) return;
|
||||||
playQueue.clear();
|
playQueue.clear();
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
|
// 清理播放管理器中的任务
|
||||||
|
if (playbackManager != null) {
|
||||||
|
playbackManager.reset();
|
||||||
|
}
|
||||||
// 清理当前正在播放的内容
|
// 清理当前正在播放的内容
|
||||||
clearPrevious();
|
clearPrevious();
|
||||||
}
|
}
|
||||||
|
// 在类成员变量中添加
|
||||||
|
private static final int PLAYBACK_TIMEOUT = 10000; // 10秒超时
|
||||||
|
private Map<String, Long> playbackStartTimeMap = new HashMap<>();
|
||||||
|
|
||||||
|
// 添加超时检查方法
|
||||||
|
private void startPlaybackTimeout(String url) {
|
||||||
|
playbackStartTimeMap.put(url, System.currentTimeMillis());
|
||||||
|
mainHandler.postDelayed(() -> checkPlaybackTimeout(url), PLAYBACK_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPlaybackTimeout(String url) {
|
||||||
|
Long startTime = playbackStartTimeMap.get(url);
|
||||||
|
if (startTime != null && System.currentTimeMillis() - startTime > PLAYBACK_TIMEOUT) {
|
||||||
|
LogUtils.w(TAG, "Playback timeout: " + url);
|
||||||
|
playbackStartTimeMap.remove(url);
|
||||||
|
|
||||||
|
// 强制结束当前播放并继续下一个
|
||||||
|
handlePlaybackComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelPlaybackTimeout(String url) {
|
||||||
|
playbackStartTimeMap.remove(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class PlayItem {
|
private static class PlayItem {
|
||||||
@@ -1060,6 +1263,7 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭特效
|
* 关闭特效
|
||||||
*/
|
*/
|
||||||
@@ -1129,5 +1333,124 @@ public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|||||||
Log.e(TAG, "停止SVGA动画出错", e);
|
Log.e(TAG, "停止SVGA动画出错", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 在 AvatarFrameView 类中添加以下代码
|
||||||
|
|
||||||
|
// 播放任务管理器
|
||||||
|
// 替换现有的 PlaybackManager 类
|
||||||
|
private static class PlaybackManager {
|
||||||
|
private static final int MAX_CONCURRENT_PLAYBACKS = 3; // 增加并发数
|
||||||
|
private int currentPlaybackCount = 0;
|
||||||
|
private final Handler handler;
|
||||||
|
|
||||||
|
public PlaybackManager(Handler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canStartNewPlayback() {
|
||||||
|
return currentPlaybackCount < MAX_CONCURRENT_PLAYBACKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStartPlayback() {
|
||||||
|
currentPlaybackCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFinishPlayback() {
|
||||||
|
currentPlaybackCount = Math.max(0, currentPlaybackCount - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
currentPlaybackCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 播放任务接口
|
||||||
|
private interface PlaybackTask {
|
||||||
|
void execute();
|
||||||
|
}
|
||||||
|
// 在 AvatarFrameView 类中添加以下代码
|
||||||
|
|
||||||
|
// MP4播放完成监听器
|
||||||
|
private class MP4PlaybackCallback implements IAnimListener {
|
||||||
|
@Override
|
||||||
|
public void onFailed(int i, @Nullable String s) {
|
||||||
|
LogUtils.e(TAG, "MP4 playback failed: " + s);
|
||||||
|
onPlaybackComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoStart() {
|
||||||
|
// 播放开始
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoRender(int i, @Nullable AnimConfig animConfig) {
|
||||||
|
// 视频渲染中
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoComplete() {
|
||||||
|
onPlaybackComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoDestroy() {
|
||||||
|
// 视频销毁
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加统一的播放完成处理方法
|
||||||
|
private void handlePlaybackComplete() {
|
||||||
|
mainHandler.post(() -> {
|
||||||
|
if (isDestroyed) return;
|
||||||
|
|
||||||
|
isPlaying = false;
|
||||||
|
|
||||||
|
// 通知播放管理器任务完成
|
||||||
|
if (playbackManager != null) {
|
||||||
|
playbackManager.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内存检查
|
||||||
|
if (playQueue.size() % 5 == 0) {
|
||||||
|
performLightMemoryCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放下一个
|
||||||
|
playNextFromQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加轻量级内存清理方法
|
||||||
|
private void performLightMemoryCleanup() {
|
||||||
|
Runtime runtime = Runtime.getRuntime();
|
||||||
|
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
|
long maxMemory = runtime.maxMemory();
|
||||||
|
double memoryUsage = (double) usedMemory / maxMemory;
|
||||||
|
|
||||||
|
// 内存使用超过70%时进行清理
|
||||||
|
if (memoryUsage > 0.7) {
|
||||||
|
// 清理SVGA缓存
|
||||||
|
if (svgaCache.size() > 1) {
|
||||||
|
// 保留最新的缓存项
|
||||||
|
Iterator<Map.Entry<String, WeakReference<SVGAVideoEntity>>> iterator =
|
||||||
|
svgaCache.entrySet().iterator();
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
iterator.next(); // 跳过最新的
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
iterator.remove(); // 移除较旧的
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建议进行垃圾回收
|
||||||
|
System.gc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||