1125 lines
38 KiB
Java
1125 lines
38 KiB
Java
package com.xscm.moduleutil.widget;
|
||
|
||
import android.content.Context;
|
||
import android.opengl.GLSurfaceView;
|
||
import android.os.Handler;
|
||
import android.os.Looper;
|
||
import android.util.AttributeSet;
|
||
import android.util.Log;
|
||
import android.view.LayoutInflater;
|
||
import android.view.View;
|
||
import android.widget.FrameLayout;
|
||
|
||
import androidx.annotation.NonNull;
|
||
import androidx.annotation.Nullable;
|
||
import androidx.databinding.DataBindingUtil;
|
||
|
||
import com.blankj.utilcode.util.LogUtils;
|
||
import com.liulishuo.okdownload.DownloadTask;
|
||
import com.liulishuo.okdownload.core.cause.EndCause;
|
||
import com.liulishuo.okdownload.core.cause.ResumeFailedCause;
|
||
import com.liulishuo.okdownload.core.listener.DownloadListener1;
|
||
import com.liulishuo.okdownload.core.listener.assist.Listener1Assist;
|
||
import com.opensource.svgaplayer.SVGACallback;
|
||
import com.opensource.svgaplayer.SVGADrawable;
|
||
import com.opensource.svgaplayer.SVGADynamicEntity;
|
||
import com.opensource.svgaplayer.SVGAImageView;
|
||
import com.opensource.svgaplayer.SVGAParser;
|
||
import com.opensource.svgaplayer.SVGAVideoEntity;
|
||
import com.tencent.qgame.animplayer.inter.IAnimListener;
|
||
import com.xscm.moduleutil.R;
|
||
import com.xscm.moduleutil.databinding.RoomViewSvgaAnimBinding;
|
||
import com.xscm.moduleutil.utils.SpUtil;
|
||
import com.xscm.moduleutil.utils.logger.Logger;
|
||
|
||
import java.io.File;
|
||
import java.io.FileOutputStream;
|
||
import java.io.IOException;
|
||
import java.io.InputStream;
|
||
import java.lang.ref.WeakReference;
|
||
import java.net.URL;
|
||
import java.util.HashMap;
|
||
import java.util.Iterator;
|
||
import java.util.LinkedHashMap;
|
||
import java.util.LinkedList;
|
||
import java.util.Map;
|
||
import java.util.Queue;
|
||
import java.util.concurrent.BlockingQueue;
|
||
import java.util.concurrent.LinkedBlockingQueue;
|
||
|
||
import okhttp3.Call;
|
||
import okhttp3.Callback;
|
||
import okhttp3.OkHttpClient;
|
||
import okhttp3.Request;
|
||
import okhttp3.Response;
|
||
import okhttp3.ResponseBody;
|
||
|
||
|
||
public class AvatarFrameView extends FrameLayout {
|
||
private PlaybackManager playbackManager;
|
||
public enum RenderType {SVGA, MP4}
|
||
|
||
private RenderType renderType;
|
||
private SVGAImageView svgaSurface;
|
||
private SVGAImageView svgaSurface2;
|
||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||
private int mType;//1:循环播放 2:播放一次停止播放
|
||
private final BlockingQueue<PlayItem> playQueue = new LinkedBlockingQueue<>();
|
||
|
||
private boolean isPlaying = false;
|
||
// 添加销毁标记
|
||
private boolean isDestroyed = false;
|
||
private static final String TAG = "AvatarFrameView";
|
||
private RoomViewSvgaAnimBinding mBinding;
|
||
|
||
// 内存监控
|
||
private static final long MAX_MEMORY_THRESHOLD = 300 * 1024 * 1024; // 300MB
|
||
private static final int MAX_SVGA_CACHE_SIZE = 3;
|
||
private final Map<String, WeakReference<SVGAVideoEntity>> svgaCache = new LruCache<>(MAX_SVGA_CACHE_SIZE);
|
||
|
||
|
||
public AvatarFrameView(@NonNull Context context) {
|
||
this(context, null);
|
||
}
|
||
|
||
public AvatarFrameView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||
this(context, attrs, 0);
|
||
}
|
||
|
||
public AvatarFrameView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||
super(context, attrs, defStyleAttr);
|
||
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.room_view_svga_anim, this, true);
|
||
initViews();
|
||
|
||
}
|
||
|
||
private void initViews() {
|
||
// if (isDestroyed) return;
|
||
// 初始化 ExoPlayer View
|
||
// playerView = new PlayerView(getContext());
|
||
// playerView.setUseController(false);
|
||
// playerView.setVisibility(View.GONE);
|
||
// addView(playerView);
|
||
|
||
// 初始化 SVGA View
|
||
svgaSurface = new SVGAImageView(getContext());
|
||
svgaSurface.setVisibility(View.GONE);
|
||
addView(svgaSurface);
|
||
|
||
svgaSurface2 = new SVGAImageView(getContext());
|
||
svgaSurface2.setVisibility(View.GONE);
|
||
addView(svgaSurface2);
|
||
|
||
// 初始化播放管理器
|
||
playbackManager = new PlaybackManager(mainHandler);
|
||
|
||
// 获取 MP4PlaybackCallback 单例实例并设置引用
|
||
// MP4PlaybackCallback callback = MP4PlaybackCallback.getInstance();
|
||
// callback.setAvatarFrameView(this);
|
||
// 设置播放完成监听
|
||
// mBinding.playView.setAnimListener(callback);
|
||
|
||
// if (mBinding != null) {
|
||
// mBinding.playView.setAnimListener(this);
|
||
// }
|
||
|
||
// MP4PlaybackCallback = new IAnimListener() {
|
||
// @Override
|
||
// public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
|
||
// return false;
|
||
// }
|
||
//
|
||
// @Override
|
||
// public void onVideoStart() {
|
||
//
|
||
// }
|
||
//
|
||
// @Override
|
||
// public void onVideoRender(int i, @Nullable AnimConfig animConfig) {
|
||
//
|
||
// }
|
||
//
|
||
// @Override
|
||
// public void onVideoComplete() {
|
||
// onPlaybackComplete();
|
||
// }
|
||
//
|
||
// @Override
|
||
// public void onVideoDestroy() {
|
||
//
|
||
// }
|
||
//
|
||
// @Override
|
||
// public void onFailed(int i, @Nullable String s) {
|
||
// onPlaybackComplete();
|
||
// }
|
||
// };
|
||
// // 设置播放完成监听
|
||
// mBinding.playView.setAnimListener(MP4PlaybackCallback);
|
||
}
|
||
|
||
private String getFileExtension(String url) {
|
||
if (url == null || url.isEmpty()) return "";
|
||
int dotIndex = url.lastIndexOf(".");
|
||
if (dotIndex > 0 && dotIndex < url.length() - 1) {
|
||
return url.substring(dotIndex + 1).toLowerCase(); // 返回 "mp4", "svga" 等
|
||
}
|
||
return "";
|
||
}
|
||
|
||
public void playNextFromQueue() {
|
||
// if (isDestroyed) return;
|
||
// 确保在主线程中执行
|
||
// if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
// mainHandler.post(this::playNextFromQueue);
|
||
// return;
|
||
// }
|
||
|
||
// 检查特效是否开启
|
||
// if (SpUtil.getOpenEffect() != 1) {
|
||
// clearQueue();
|
||
// return;
|
||
// }
|
||
// 检查是否可以开始新的播放
|
||
// if (!playbackManager.canStartNewPlayback()) {
|
||
// Logger.d("AvatarFrameView", "Max concurrent playbacks reached, waiting...");
|
||
// return;
|
||
// }
|
||
|
||
if (!isPlaying || !isActuallyPlaying()) {
|
||
|
||
|
||
PlayItem item = playQueue.poll();
|
||
if (item != null) {
|
||
// 通知开始播放
|
||
playbackManager.onStartPlayback();
|
||
isPlaying = true;
|
||
|
||
// 处理播放项目
|
||
processPlayItem(item);
|
||
|
||
} else {
|
||
isPlaying = false;
|
||
Logger.d("AvatarFrameView", "Queue is empty, stop playing");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加统一的播放完成处理方法
|
||
public void onPlaybackComplete() {
|
||
if (isDestroyed) return;
|
||
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)) {
|
||
mainHandler.post(() -> {
|
||
renderType = RenderType.SVGA;
|
||
mType = item.type;
|
||
if (mBinding != null && mBinding.playView != null) {
|
||
mBinding.playView.setVisibility(View.GONE);
|
||
}
|
||
loadSVGA(item.url);
|
||
});
|
||
} else if ("mp4".equalsIgnoreCase(ext)) {
|
||
mainHandler.post(() -> {
|
||
renderType = RenderType.MP4;
|
||
mType = item.type;
|
||
if (mBinding != null && mBinding.playView != null) {
|
||
mBinding.playView.setVisibility(View.VISIBLE);
|
||
|
||
downloadAndPlayMp4(item.url);
|
||
}else {
|
||
mBinding = DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.room_view_svga_anim, this, true);
|
||
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() {
|
||
if (renderType == RenderType.SVGA && svgaSurface != null) {
|
||
return svgaSurface.isAnimating();
|
||
} else if (renderType == RenderType.MP4 && mBinding != null) {
|
||
// 检查播放器状态
|
||
return mBinding.playView.isRunning();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public boolean isPlaying() {
|
||
if (mBinding != null && mBinding.playView != null) {
|
||
return mBinding.playView.isRunning();
|
||
}
|
||
return true;
|
||
}
|
||
// 在 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) {
|
||
// if (isDestroyed) return;
|
||
// 确保在主线程中执行
|
||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
mainHandler.post(() -> setSource(url, type2));
|
||
return;
|
||
}
|
||
|
||
// 添加到播放队列
|
||
playQueue.add(new PlayItem(url, type2));
|
||
Logger.d("AvatarFrameView", "Added to queue, queue size: " + playQueue.size() + ", url: " + url);
|
||
|
||
if (type2 == 3 || type2 == 1) {
|
||
// playNextFromQueue();
|
||
loadSVGA(url);
|
||
} else {
|
||
|
||
// 如果当前没有在播放,则开始播放
|
||
if (!isPlaying || !isActuallyPlaying()) {
|
||
playNextFromQueue();
|
||
}
|
||
}
|
||
Logger.d("AvatarFrameView", "Added to queue, queue size: " + playQueue.size() + ", url: " + url);
|
||
}
|
||
|
||
private void smartCheckAndStartPlayback() {
|
||
// 检查是否可以开始新的播放任务
|
||
if (!playQueue.isEmpty() &&
|
||
(!isPlaying || !isActuallyPlaying()) &&
|
||
currentProcessingCount < MAX_CONCURRENT_PROCESSING) {
|
||
|
||
currentProcessingCount++;
|
||
playNextFromQueue();
|
||
}
|
||
}
|
||
|
||
private void checkAndStartPlayback() {
|
||
// 如果队列不为空且当前没有在播放,则开始播放
|
||
if (!playQueue.isEmpty() && (!isPlaying || !isActuallyPlaying())) {
|
||
playNextFromQueue();
|
||
}
|
||
}
|
||
|
||
private boolean isMemoryLow() {
|
||
Runtime runtime = Runtime.getRuntime();
|
||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||
long maxMemory = runtime.maxMemory();
|
||
double memoryUsage = (double) usedMemory / maxMemory;
|
||
|
||
// 内存使用超过80%或绝对使用量超过阈值
|
||
return memoryUsage > 0.8 || usedMemory > MAX_MEMORY_THRESHOLD;
|
||
}
|
||
|
||
|
||
boolean isTxk = false;
|
||
|
||
public void downloadAndPlayMp4(String url) {
|
||
|
||
// 提取文件名
|
||
String fileName = url.substring(url.lastIndexOf("/"));
|
||
String filePath = getContext().getCacheDir().getAbsolutePath() + fileName;
|
||
|
||
LogUtils.e("@@@@@filePath: " + filePath.toString());
|
||
File file = new File(filePath);
|
||
|
||
if (!file.exists()) {
|
||
|
||
LogUtils.e("无缓存");
|
||
// 使用OkHttp进行下载
|
||
OkHttpClient client = new OkHttpClient();
|
||
Request request = new Request.Builder()
|
||
.url(url)
|
||
.build();
|
||
|
||
client.newCall(request).enqueue(new Callback() {
|
||
@Override
|
||
public void onFailure(Call call, IOException e) {
|
||
LogUtils.e("MP4下载失败: " + e.toString());
|
||
mainHandler.post(() -> {
|
||
// 检查是否已销毁
|
||
if (!isDestroyed) {
|
||
onPlaybackComplete();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 更简单的优化版本
|
||
@Override
|
||
public void onResponse(Call call, Response response) throws IOException {
|
||
// 在异步回调中首先检查是否已销毁
|
||
if (isDestroyed) {
|
||
LogUtils.w(TAG, "View destroyed before download completed");
|
||
return;
|
||
}
|
||
LogUtils.d("@@@@", "onResponse" + Thread.currentThread().getName());
|
||
if (response.isSuccessful()) {
|
||
try (ResponseBody responseBody = response.body()) {
|
||
if (responseBody != null) {
|
||
// 在后台线程处理文件保存
|
||
|
||
String fileName = url.substring(url.lastIndexOf("/"));
|
||
String filePath = getContext().getCacheDir().getAbsolutePath() + fileName;
|
||
File downloadedFile = new File(filePath);
|
||
|
||
// 使用流式传输避免大文件卡顿
|
||
try (InputStream inputStream = responseBody.byteStream();
|
||
FileOutputStream fos = new FileOutputStream(downloadedFile)) {
|
||
|
||
// 定义缓冲区大小(8KB)
|
||
byte[] buffer = new byte[1024 * 1024];
|
||
int bytesRead;
|
||
|
||
// 流式读取并写入文件
|
||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||
fos.write(buffer, 0, bytesRead);
|
||
}
|
||
|
||
fos.flush();
|
||
isTxk = true;
|
||
mainHandler.post(() -> {
|
||
// 关键:在执行UI操作前再次检查是否已销毁
|
||
if (downloadedFile.exists()) {
|
||
LogUtils.d("@@@@Thread", Thread.currentThread().getName());
|
||
playMp4File(downloadedFile); // 使用正确的文件引用
|
||
} else {
|
||
LogUtils.w(TAG, "View destroyed or file not exist after download");
|
||
onPlaybackComplete();
|
||
}
|
||
});
|
||
} catch (IOException e) {
|
||
LogUtils.e("MP4文件保存失败: " + e.getMessage());
|
||
mainHandler.post(() -> {
|
||
if (!isDestroyed) {
|
||
onPlaybackComplete();
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
} else {
|
||
mainHandler.post(() -> {
|
||
if (!isDestroyed) {
|
||
onPlaybackComplete();
|
||
}
|
||
});
|
||
}
|
||
} catch (Exception e) {
|
||
LogUtils.e("MP4文件保存失败: " + e.getMessage());
|
||
mainHandler.post(() -> {
|
||
if (!isDestroyed) {
|
||
onPlaybackComplete();
|
||
}
|
||
});
|
||
}
|
||
} else {
|
||
LogUtils.e("MP4下载响应失败");
|
||
mainHandler.post(() -> {
|
||
if (!isDestroyed) {
|
||
onPlaybackComplete();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
isTxk = true;
|
||
LogUtils.e("有缓存");
|
||
mainHandler.post(() -> {
|
||
// 检查是否已销毁
|
||
if (file.exists()) {
|
||
playMp4File(file);
|
||
} else {
|
||
LogUtils.w(TAG, "有缓存2222222222222");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
private void playMp4File(File file) {
|
||
try {
|
||
// 双重检查确保组件未被销毁
|
||
if (isDestroyed) {
|
||
LogUtils.w(TAG, "Attempt to play MP4 file after view destroyed");
|
||
onPlaybackComplete();
|
||
return;
|
||
}
|
||
|
||
if (mBinding == null || mBinding.playView == null) {
|
||
LogUtils.w(TAG, "PlayView is null");
|
||
onPlaybackComplete();
|
||
return;
|
||
}
|
||
|
||
if (file != null && file.exists()) {
|
||
// 设置循环次数(根据mType决定)
|
||
if (mType == 1 || mType == 3) {
|
||
mBinding.playView.setLoop(Integer.max(1, 999999999)); // 无限循环
|
||
} else {
|
||
mBinding.playView.setLoop(1); // 播放一次
|
||
}
|
||
|
||
// 开始播放前检查视图状态
|
||
if (!isDestroyed && mBinding != null && mBinding.playView != null) {
|
||
mBinding.playView.startPlay(file);
|
||
} else {
|
||
LogUtils.w(TAG, "View was destroyed before MP4 playback started");
|
||
onPlaybackComplete();
|
||
}
|
||
} else {
|
||
LogUtils.e("播放MP4文件出错: 文件不存在或已损坏");
|
||
onPlaybackComplete();
|
||
}
|
||
} catch (Exception e) {
|
||
LogUtils.e("播放MP4文件出错: " + e.getMessage());
|
||
onPlaybackComplete();
|
||
}
|
||
}
|
||
|
||
private void handleSVGAComplete(SVGAVideoEntity videoItem, String url) {
|
||
if (svgaSurface == null) {
|
||
onPlaybackComplete();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 缓存实体(使用弱引用)
|
||
if (svgaCache.size() < MAX_SVGA_CACHE_SIZE) {
|
||
svgaCache.put(url, new WeakReference<>(videoItem));
|
||
}
|
||
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
|
||
svgaSurface.setImageDrawable(drawable);
|
||
svgaSurface.setCallback(new SVGACallback() {
|
||
@Override
|
||
public void onStep(int i, double v) {
|
||
}
|
||
|
||
@Override
|
||
public void onRepeat() {
|
||
// 循环播放处理
|
||
if (mType != 1) { // 非循环播放
|
||
svgaSurface.stopAnimation();
|
||
svgaSurface.clearAnimation();
|
||
svgaSurface.setImageDrawable(null);
|
||
onPlaybackComplete();
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onPause() {
|
||
}
|
||
|
||
@Override
|
||
public void onFinished() {
|
||
// if (isDestroyed) return;
|
||
if (mType == 1) { // 循环播放
|
||
// 继续循环播放
|
||
} else {
|
||
onPlaybackComplete();
|
||
}
|
||
}
|
||
});
|
||
// 设置循环次数
|
||
if (mType == 1 || mType == 3) {
|
||
svgaSurface.setLoops(0); // 无限循环
|
||
} else {
|
||
svgaSurface.setLoops(1); // 播放一次
|
||
}
|
||
svgaSurface.startAnimation();
|
||
} catch (Exception e) {
|
||
LogUtils.e(TAG, "Error handling SVGA completion: " + e.getMessage());
|
||
// isPlaying = false;
|
||
// playNextFromQueue();
|
||
|
||
handlePlaybackComplete();
|
||
}
|
||
}
|
||
|
||
public void stopAll() {
|
||
if (svgaSurface != null) {
|
||
svgaSurface.stopAnimation();
|
||
svgaSurface.clearAnimation();
|
||
svgaSurface.setImageDrawable(null);
|
||
}
|
||
if (svgaSurface2 != null) {
|
||
svgaSurface2.stopAnimation();
|
||
svgaSurface2.clearAnimation();
|
||
svgaSurface.setImageDrawable(null);
|
||
}
|
||
// 增加空值检查
|
||
if (mBinding != null && mBinding.playView != null) {
|
||
mBinding.playView.stopPlay();
|
||
}
|
||
}
|
||
|
||
private void playCachedSVGA(SVGAVideoEntity videoItem) {
|
||
try {
|
||
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
|
||
svgaSurface.setImageDrawable(drawable);
|
||
svgaSurface.setCallback(new SVGACallback() {
|
||
@Override
|
||
public void onStep(int i, double v) {
|
||
}
|
||
|
||
@Override
|
||
public void onRepeat() {
|
||
}
|
||
|
||
@Override
|
||
public void onPause() {
|
||
}
|
||
|
||
@Override
|
||
public void onFinished() {
|
||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
mainHandler.post(() -> {
|
||
isPlaying = false;
|
||
// 添加延迟确保状态更新
|
||
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
|
||
});
|
||
} else {
|
||
isPlaying = false;
|
||
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
|
||
}
|
||
}
|
||
});
|
||
// 设置循环次数
|
||
if (mType == 1 || mType == 3) {
|
||
svgaSurface.setLoops(0); // 无限循环
|
||
} else {
|
||
svgaSurface.setLoops(1); // 播放一次
|
||
}
|
||
svgaSurface.startAnimation();
|
||
} catch (Exception e) {
|
||
LogUtils.e(TAG, "Error playing cached SVGA: " + e.getMessage());
|
||
isPlaying = false;
|
||
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
|
||
// playNextFromQueue();
|
||
}
|
||
}
|
||
|
||
private void loadNewSVGA(String url) {
|
||
|
||
try {
|
||
new SVGAParser(getContext()).parse(new URL(url), new SVGAParser.ParseCompletion() {
|
||
@Override
|
||
public void onComplete(SVGAVideoEntity videoItem) {
|
||
// if (isDestroyed) return;
|
||
|
||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
mainHandler.post(() -> handleSVGAComplete(videoItem, url));
|
||
} else {
|
||
handleSVGAComplete(videoItem, url);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onError() {
|
||
// if (isDestroyed) return;
|
||
|
||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
mainHandler.post(() -> {
|
||
isPlaying = false;
|
||
playNextFromQueue();
|
||
});
|
||
} else {
|
||
isPlaying = false;
|
||
playNextFromQueue();
|
||
}
|
||
}
|
||
});
|
||
} catch (Exception e) {
|
||
LogUtils.e(TAG, "Error parsing SVGA: " + e.getMessage());
|
||
isPlaying = false;
|
||
playNextFromQueue();
|
||
}
|
||
}
|
||
|
||
private void loadSVGA(String url) {
|
||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
mainHandler.post(() -> loadSVGA(url));
|
||
return;
|
||
}
|
||
|
||
try {
|
||
svgaSurface.setVisibility(View.VISIBLE);
|
||
|
||
// 检查缓存
|
||
WeakReference<SVGAVideoEntity> cachedRef = svgaCache.get(url);
|
||
SVGAVideoEntity cachedEntity = cachedRef != null ? cachedRef.get() : null;
|
||
|
||
if (cachedEntity != null) {
|
||
// 使用缓存的实体
|
||
playCachedSVGA(cachedEntity);
|
||
} else {
|
||
// 加载新的SVGA
|
||
loadNewSVGA(url);
|
||
}
|
||
} catch (Exception e) {
|
||
LogUtils.e(TAG, "Error loading SVGA: " + e.getMessage());
|
||
isPlaying = false;
|
||
playNextFromQueue();
|
||
}
|
||
|
||
svgaSurface.setVisibility(View.VISIBLE);
|
||
try {
|
||
new SVGAParser(getContext()).parse(new URL(url), new SVGAParser.ParseCompletion() {
|
||
@Override
|
||
public void onComplete(SVGAVideoEntity videoItem) {
|
||
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
|
||
svgaSurface.setImageDrawable(drawable);
|
||
svgaSurface.setCallback(new SVGACallback() {
|
||
|
||
@Override
|
||
public void onStep(int i, double v) {
|
||
|
||
}
|
||
|
||
@Override
|
||
public void onRepeat() {
|
||
|
||
}
|
||
|
||
@Override
|
||
public void onPause() {
|
||
|
||
}
|
||
|
||
@Override
|
||
public void onFinished() {
|
||
isPlaying = false;
|
||
playNextFromQueue();
|
||
}
|
||
});
|
||
|
||
svgaSurface.startAnimation();
|
||
}
|
||
|
||
@Override
|
||
public void onError() {
|
||
isPlaying = false;
|
||
playNextFromQueue();
|
||
}
|
||
});
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
|
||
|
||
private void clearPrevious() {
|
||
try {
|
||
// 停止并清理 SVGA 动画
|
||
if (svgaSurface != null && svgaSurface.getDrawable() instanceof SVGADrawable) {
|
||
SVGADrawable drawable = (SVGADrawable) svgaSurface.getDrawable();
|
||
if (drawable != null) {
|
||
drawable.stop();
|
||
// 清理 SVGADrawable 中的资源
|
||
svgaSurface.clearAnimation();
|
||
svgaSurface.setImageDrawable(null);
|
||
}
|
||
}
|
||
|
||
// 隐藏所有视图
|
||
// if (playerView != null) playerView.setVisibility(View.GONE);
|
||
if (svgaSurface != null) svgaSurface.setVisibility(View.GONE);
|
||
|
||
|
||
// 停止播放器
|
||
if (mBinding != null && mBinding.playView != null) {
|
||
mBinding.playView.stopPlay();
|
||
mBinding.playView.setVisibility(View.GONE);
|
||
}
|
||
} catch (Exception e) {
|
||
LogUtils.e(TAG, "Error in clearPrevious: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// 简单的 LRU Cache 实现
|
||
private static class LruCache<K, V> extends LinkedHashMap<K, V> {
|
||
private final int maxSize;
|
||
|
||
public LruCache(int maxSize) {
|
||
super(16, 0.75f, true);
|
||
this.maxSize = maxSize;
|
||
}
|
||
|
||
@Override
|
||
protected boolean removeEldestEntry(Entry<K, V> eldest) {
|
||
return size() > maxSize;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 释放所有资源
|
||
*/
|
||
private void releaseResources() {
|
||
LogUtils.d(TAG, "Releasing all resources");
|
||
|
||
// if (isDestroyed) return;
|
||
// 使用异步线程处理耗时操作
|
||
new Thread(() -> {
|
||
try {
|
||
// 在后台线程处理文件操作和大对象清理
|
||
// performHeavyCleanup();
|
||
|
||
// 回到主线程处理 UI 相关的清理
|
||
mainHandler.post(() -> {
|
||
performUICleanup();
|
||
});
|
||
} catch (Exception e) {
|
||
Logger.e(TAG, "Error in async releaseResources: " + e.getMessage());
|
||
// 出错时仍在主线程清理 UI 资源
|
||
mainHandler.post(() -> {
|
||
performUICleanup();
|
||
});
|
||
}
|
||
}).start();
|
||
}
|
||
|
||
/**
|
||
* 在后台线程执行耗时的清理操作
|
||
*/
|
||
private void performHeavyCleanup() {
|
||
try {
|
||
// 清理缓存文件(如果需要)
|
||
// 清理大对象引用等
|
||
// clearCacheFiles();
|
||
} catch (Exception e) {
|
||
Logger.e(TAG, "Error in performHeavyCleanup: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 在主线程执行 UI 相关的清理操作
|
||
*/
|
||
private void performUICleanup() {
|
||
try {
|
||
// 停止并清理播放器
|
||
if (mBinding != null && mBinding.playView != null) {
|
||
try {
|
||
mBinding.playView.stopPlay();
|
||
} catch (Exception e) {
|
||
Logger.e(TAG, "Error stopping playView: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
// 清理 ExoPlayer 资源
|
||
// if (exoPlayer != null) {
|
||
// try {
|
||
// // 使用异步停止避免阻塞
|
||
// exoPlayer.stop();
|
||
// exoPlayer.clearVideoSurface();
|
||
// } catch (Exception e) {
|
||
// Logger.e(TAG, "Error releasing ExoPlayer resources: " + e.getMessage());
|
||
// }
|
||
// }
|
||
|
||
// 清理 SVGA 资源
|
||
if (svgaSurface != null) {
|
||
try {
|
||
// svgaSurface.pauseAnimation();
|
||
// svgaSurface.clearAnimation();
|
||
// svgaSurface.setImageDrawable(null);
|
||
svgaSurface.stopAnimation(true);
|
||
svgaSurface.clear();
|
||
svgaSurface.clearAnimation();
|
||
svgaSurface.setImageDrawable(null);
|
||
} catch (Exception e) {
|
||
Logger.e(TAG, "Error releasing SVGA resources: " + e.getMessage());
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
Logger.e(TAG, "Error in performUICleanup: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 公共释放方法,用于外部主动释放资源
|
||
*/
|
||
public void release() {
|
||
Logger.d("AvatarFrameView", "Public release called");
|
||
// if (isDestroyed) return;
|
||
// 确保在主线程中执行
|
||
// if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
// mainHandler.post(this::release);
|
||
// return;
|
||
// }
|
||
isDestroyed = true;
|
||
|
||
try {
|
||
// 清空播放队列
|
||
clearQueue();
|
||
// 清理播放管理器
|
||
if (playbackManager != null) {
|
||
playbackManager.reset();
|
||
}
|
||
// 释放所有资源
|
||
releaseResources();
|
||
|
||
|
||
// 延迟清理其他资源
|
||
mainHandler.postDelayed(() -> {
|
||
// 清理 binding
|
||
if (mBinding != null) {
|
||
mBinding = null;
|
||
}
|
||
}, 100);
|
||
|
||
|
||
// 清理 binding
|
||
if (mBinding != null) {
|
||
mBinding = null;
|
||
}
|
||
|
||
|
||
} catch (Exception e) {
|
||
LogUtils.e(TAG, "Error in AvatarFrameView release: " + e.getMessage());
|
||
} finally {
|
||
// 建议进行垃圾回收
|
||
// MemoryOptimizationUtils.forceGC();
|
||
}
|
||
}
|
||
|
||
public void clearQueue() {
|
||
// if (isDestroyed) return;
|
||
playQueue.clear();
|
||
isPlaying = false;
|
||
// 清理播放管理器中的任务
|
||
if (playbackManager != null) {
|
||
playbackManager.reset();
|
||
}
|
||
// 清理当前正在播放的内容
|
||
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 {
|
||
String url;
|
||
int type;
|
||
|
||
PlayItem(String url, int type) {
|
||
this.url = url;
|
||
this.type = type;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 关闭特效
|
||
*/
|
||
public void closeEffect() {
|
||
|
||
// 清空队列
|
||
clearQueue();
|
||
// 释放资源
|
||
// releaseResources();
|
||
// 清空队列
|
||
// playQueue.clear();
|
||
// 关闭动画
|
||
// if (mBinding.playView != null && isPlaying && mBinding.playView.isRunning()) {
|
||
// mBinding.playView.setAnimation(null);
|
||
// mBinding.playView.clearAnimation();
|
||
// mBinding.playView.stopPlay();
|
||
// }
|
||
}
|
||
|
||
/**
|
||
* 开始循环播放SVGA动画
|
||
*/
|
||
public void startLoopingSvga(String assetName) {
|
||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
mainHandler.post(() -> startLoopingSvga(assetName));
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// clearPrevious(); // 清除之前的动画
|
||
svgaSurface.setVisibility(View.VISIBLE);
|
||
new SVGAParser(getContext()).decodeFromAssets(assetName, new SVGAParser.ParseCompletion() {
|
||
@Override
|
||
public void onComplete(SVGAVideoEntity svgaVideoEntity) {
|
||
SVGADrawable drawable = new SVGADrawable(svgaVideoEntity);
|
||
svgaSurface.setImageDrawable(drawable);
|
||
svgaSurface.setLoops(0); // 0表示无限循环
|
||
svgaSurface.startAnimation();
|
||
}
|
||
|
||
@Override
|
||
public void onError() {
|
||
Log.e(TAG, "解析SVGA文件失败: " + assetName);
|
||
}
|
||
});
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "播放SVGA动画出错", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 停止并销毁当前SVGA动画
|
||
*/
|
||
public void stopSvga() {
|
||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||
mainHandler.post(() -> stopSvga());
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (svgaSurface2 != null) {
|
||
svgaSurface2.stopAnimation(true);
|
||
svgaSurface2.setImageDrawable(null);
|
||
svgaSurface2.setVisibility(View.GONE);
|
||
}
|
||
} catch (Exception 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 类中添加以下代码
|
||
|
||
// private IAnimListener MP4PlaybackCallback;
|
||
// 在 AvatarFrameView 类的成员变量区域添加单例实例
|
||
|
||
public void setAnimListener(IAnimListener mInstance) {
|
||
mBinding.playView.setAnimListener(mInstance);
|
||
}
|
||
|
||
// 添加统一的播放完成处理方法
|
||
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();
|
||
}
|
||
}
|
||
|
||
}
|