2025-08-26 19:34:44 +08:00
|
|
|
|
package com.xscm.moduleutil.widget;
|
|
|
|
|
|
|
|
|
|
|
|
import android.content.Context;
|
2025-08-27 23:12:46 +08:00
|
|
|
|
import android.graphics.SurfaceTexture;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
import android.net.Uri;
|
2025-08-27 23:12:46 +08:00
|
|
|
|
import android.opengl.GLES11Ext;
|
|
|
|
|
|
import android.opengl.GLES20;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
import android.opengl.GLSurfaceView;
|
|
|
|
|
|
import android.os.Handler;
|
|
|
|
|
|
import android.os.Looper;
|
|
|
|
|
|
import android.util.AttributeSet;
|
|
|
|
|
|
import android.util.Log;
|
2025-08-27 23:12:46 +08:00
|
|
|
|
import android.util.LruCache;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
import android.view.LayoutInflater;
|
|
|
|
|
|
import android.view.Surface;
|
|
|
|
|
|
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.blankj.utilcode.util.PathUtils;
|
|
|
|
|
|
import com.google.android.exoplayer2.ExoPlayer;
|
|
|
|
|
|
import com.google.android.exoplayer2.MediaItem;
|
|
|
|
|
|
import com.google.android.exoplayer2.ui.PlayerView;
|
|
|
|
|
|
import com.liulishuo.okdownload.DownloadTask;
|
2025-08-27 23:12:46 +08:00
|
|
|
|
import com.liulishuo.okdownload.StatusUtil;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
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;
|
2025-08-27 23:12:46 +08:00
|
|
|
|
import com.tencent.qgame.animplayer.AnimConfig;
|
|
|
|
|
|
import com.tencent.qgame.animplayer.inter.IAnimListener;
|
|
|
|
|
|
import com.tencent.qgame.animplayer.inter.IFetchResource;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
import com.xscm.moduleutil.R;
|
|
|
|
|
|
import com.xscm.moduleutil.databinding.RoomViewSvgaAnimBinding;
|
|
|
|
|
|
import com.xscm.moduleutil.utils.Md5Utils;
|
|
|
|
|
|
import com.xscm.moduleutil.utils.MemoryOptimizationUtils;
|
|
|
|
|
|
import com.xscm.moduleutil.utils.SpUtil;
|
|
|
|
|
|
import com.xscm.moduleutil.utils.logger.Logger;
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
2025-08-29 01:14:35 +08:00
|
|
|
|
import java.io.FileOutputStream;
|
2025-08-27 23:12:46 +08:00
|
|
|
|
import java.io.IOException;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
import java.lang.ref.WeakReference;
|
|
|
|
|
|
import java.net.URL;
|
|
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
|
|
import java.util.LinkedList;
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
import java.util.Queue;
|
|
|
|
|
|
|
2025-08-29 01:14:35 +08:00
|
|
|
|
import okhttp3.Call;
|
|
|
|
|
|
import okhttp3.Callback;
|
|
|
|
|
|
import okhttp3.OkHttpClient;
|
|
|
|
|
|
import okhttp3.Request;
|
|
|
|
|
|
import okhttp3.Response;
|
|
|
|
|
|
import okhttp3.ResponseBody;
|
|
|
|
|
|
|
2025-08-26 19:34:44 +08:00
|
|
|
|
|
|
|
|
|
|
public class AvatarFrameView extends FrameLayout implements IAnimListener {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onFailed(int i, @Nullable String s) {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@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() {
|
|
|
|
|
|
// if (mType == 1) {
|
|
|
|
|
|
// mBinding.playView.startPlay(mFile); // 循环播放
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// isPlaying = false;
|
|
|
|
|
|
// playNextFromQueue(); // 播放下一项
|
|
|
|
|
|
// }
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 确保在主线程中执行
|
|
|
|
|
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
|
|
|
|
handleVideoComplete();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
mainHandler.post(() -> {
|
|
|
|
|
|
// if (!isDestroyed) {
|
|
|
|
|
|
handleVideoComplete();
|
|
|
|
|
|
// }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
private void handleVideoComplete() {
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (mType == 1) {
|
|
|
|
|
|
if (mBinding != null && mFile != null) {
|
|
|
|
|
|
mBinding.playView.startPlay(mFile); // 循环播放
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
// playNextFromQueue(); // 播放下一项
|
|
|
|
|
|
mainHandler.postDelayed(this::playNextFromQueue, 50);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onVideoDestroy() {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public enum RenderType {SVGA, MP4}
|
|
|
|
|
|
|
|
|
|
|
|
private RenderType renderType;
|
|
|
|
|
|
private ExoPlayer exoPlayer;
|
|
|
|
|
|
private PlayerView playerView;
|
|
|
|
|
|
private SVGAImageView svgaSurface;
|
|
|
|
|
|
private GLSurfaceView glSurfaceView;
|
|
|
|
|
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
|
|
|
|
private ChannelSplitRenderer1 renderer;
|
|
|
|
|
|
private int mType;//1:循环播放 2:播放一次停止播放
|
|
|
|
|
|
private File mFile;
|
|
|
|
|
|
private final Queue<PlayItem> playQueue = new LinkedList<>();
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
// // 初始化 GLSurfaceView
|
|
|
|
|
|
// glSurfaceView = new GLSurfaceView(getContext());
|
|
|
|
|
|
// 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) {
|
|
|
|
|
|
mBinding.playView.setAnimListener(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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 "";
|
|
|
|
|
|
}
|
|
|
|
|
|
private void playNextFromQueue() {
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
// 确保在主线程中执行
|
|
|
|
|
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
|
|
|
|
mainHandler.post(this::playNextFromQueue);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 再次检查内存状态
|
|
|
|
|
|
// if (isMemoryLow()) {
|
|
|
|
|
|
// LogUtils.w(TAG, "Low memory, clearing queue");
|
|
|
|
|
|
// clearQueue();
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// 检查特效是否开启
|
|
|
|
|
|
if (SpUtil.getOpenEffect() != 1) {
|
|
|
|
|
|
clearQueue();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 关键:即使 isPlaying 为 true,也要检查实际播放状态
|
|
|
|
|
|
if (isPlaying && isActuallyPlaying()) {
|
|
|
|
|
|
Logger.d("AvatarFrameView", "Already playing, skip");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
PlayItem item = playQueue.poll();
|
|
|
|
|
|
if (item != null) {
|
|
|
|
|
|
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;
|
|
|
|
|
|
playNextFromQueue(); // 跳过无效项
|
|
|
|
|
|
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 {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
Logger.d("AvatarFrameView", "Queue is empty, stop playing");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 添加实际播放状态检查方法
|
|
|
|
|
|
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 void setSource(String url, int type2) {
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
// 确保在主线程中执行
|
|
|
|
|
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
|
|
|
|
mainHandler.post(() -> setSource(url, type2));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// // 检查内存状态
|
|
|
|
|
|
// if (isMemoryLow()) {
|
|
|
|
|
|
// LogUtils.w(TAG, "Low memory, skipping animation");
|
|
|
|
|
|
// clearQueue();
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// 检查特效是否开启
|
|
|
|
|
|
if (SpUtil.getOpenEffect() != 1) {
|
|
|
|
|
|
// 特效关闭时清空队列并停止播放
|
|
|
|
|
|
clearQueue();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到播放队列
|
|
|
|
|
|
// playQueue.offer(new PlayItem(url, type2));
|
|
|
|
|
|
playQueue.add(new PlayItem(url, type2));
|
|
|
|
|
|
Logger.d("AvatarFrameView", "Added to queue, queue size: " + playQueue.size() + ", url: " + url);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前没有在播放,则开始播放
|
|
|
|
|
|
// if (!isPlaying) {
|
|
|
|
|
|
// playNextFromQueue();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// 改进播放检查逻辑
|
|
|
|
|
|
checkAndStartPlayback();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-29 01:14:35 +08:00
|
|
|
|
boolean isTxk = false;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
private void downloadAndPlayMp4(String url) {
|
2025-08-29 01:14:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 提取文件名
|
|
|
|
|
|
String fileName = url.substring(url.lastIndexOf("/"));
|
|
|
|
|
|
String filePath = getContext().getCacheDir().getAbsolutePath() + fileName;
|
2025-08-26 19:34:44 +08:00
|
|
|
|
File file = new File(filePath);
|
|
|
|
|
|
|
2025-08-29 01:14:35 +08:00
|
|
|
|
if (!file.exists()) {
|
|
|
|
|
|
LogUtils.e("无缓存");
|
|
|
|
|
|
// 使用OkHttp进行下载
|
|
|
|
|
|
OkHttpClient client = new OkHttpClient();
|
|
|
|
|
|
Request request = new Request.Builder()
|
|
|
|
|
|
.url(url)
|
2025-08-26 19:34:44 +08:00
|
|
|
|
.build();
|
|
|
|
|
|
|
2025-08-29 01:14:35 +08:00
|
|
|
|
client.newCall(request).enqueue(new Callback() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onFailure(Call call, IOException e) {
|
|
|
|
|
|
Log.d("sssssssssss", e.toString());
|
|
|
|
|
|
}
|
2025-08-27 23:12:46 +08:00
|
|
|
|
|
2025-08-29 01:14:35 +08:00
|
|
|
|
@Override
|
|
|
|
|
|
public void onResponse(Call call, Response response) throws IOException {
|
|
|
|
|
|
if (response.isSuccessful()) {
|
|
|
|
|
|
// 保存文件到缓存目录
|
|
|
|
|
|
try (ResponseBody responseBody = response.body()) {
|
|
|
|
|
|
if (responseBody != null) {
|
|
|
|
|
|
File downloadedFile = new File(filePath);
|
|
|
|
|
|
FileOutputStream fos = new FileOutputStream(downloadedFile);
|
|
|
|
|
|
fos.write(responseBody.bytes());
|
|
|
|
|
|
fos.close();
|
|
|
|
|
|
isTxk=true;
|
|
|
|
|
|
// 在主线程中播放动画
|
|
|
|
|
|
mainHandler.post(() -> {
|
|
|
|
|
|
if (isTxk) {
|
|
|
|
|
|
mBinding.playView.setLoop(20);
|
|
|
|
|
|
}
|
|
|
|
|
|
mBinding.playView.startPlay(downloadedFile);
|
|
|
|
|
|
});
|
2025-08-27 23:12:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-29 01:14:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
isTxk=true;
|
|
|
|
|
|
LogUtils.e("有缓存");
|
|
|
|
|
|
// 直接播放缓存文件
|
|
|
|
|
|
if (isTxk) {
|
|
|
|
|
|
mBinding.playView.setLoop(20);
|
2025-08-27 23:12:46 +08:00
|
|
|
|
}
|
2025-08-29 01:14:35 +08:00
|
|
|
|
mBinding.playView.startPlay(file);
|
2025-08-27 23:12:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 19:34:44 +08:00
|
|
|
|
|
2025-08-29 01:14:35 +08:00
|
|
|
|
// private void downloadAndPlayMp4(String url) {
|
|
|
|
|
|
// String filePath = PathUtils.getInternalAppCachePath() + Md5Utils.getStringMD5(url) + ".mp4";
|
|
|
|
|
|
// File file = new File(filePath);
|
|
|
|
|
|
//
|
|
|
|
|
|
// if (file.exists() && file.length() > 0) {
|
|
|
|
|
|
// playMp4(file);
|
|
|
|
|
|
// mFile = file;
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// // 删除可能存在的损坏文件
|
|
|
|
|
|
//// if (file.exists()) {
|
|
|
|
|
|
//// file.delete();
|
|
|
|
|
|
//// }
|
|
|
|
|
|
//
|
|
|
|
|
|
// DownloadTask task = new DownloadTask.Builder(url, PathUtils.getInternalAppCachePath()
|
|
|
|
|
|
// , Md5Utils.getStringMD5(url) + ".mp4")
|
|
|
|
|
|
// .setMinIntervalMillisCallbackProcess(300)
|
|
|
|
|
|
// .setPassIfAlreadyCompleted(true)
|
|
|
|
|
|
// .setAutoCallbackToUIThread(true)
|
|
|
|
|
|
// .setConnectionCount(3) // 增加连接数提高稳定性
|
|
|
|
|
|
// .setReadBufferSize(1024 * 8) // 增大缓冲区
|
|
|
|
|
|
// .build();
|
|
|
|
|
|
// if (StatusUtil.isCompleted(task)) {
|
|
|
|
|
|
// playMp4(task.getFile());
|
|
|
|
|
|
// mFile = task.getFile();
|
|
|
|
|
|
// } else if (StatusUtil.isSameTaskPendingOrRunning(task)) {
|
|
|
|
|
|
// Logger.d(TAG, "Task is pending or running, checking if it's stuck");
|
|
|
|
|
|
// // 检查任务是否可能卡住了
|
|
|
|
|
|
// // 添加超时机制,如果任务长时间没有进展,则重新开始
|
|
|
|
|
|
// checkAndHandleStuckTask(task, url);
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// Logger.d(TAG, "Starting new download task");
|
|
|
|
|
|
// startNewDownload(task, url);
|
|
|
|
|
|
// }
|
|
|
|
|
|
//
|
|
|
|
|
|
//// else if (StatusUtil.isSameTaskPendingOrRunning(task)) {
|
|
|
|
|
|
//// // 如果任务正在进行中,等待完成
|
|
|
|
|
|
//// // 可以通过监听器处理
|
|
|
|
|
|
//// attachToExistingTask(task);
|
|
|
|
|
|
//// } else {
|
|
|
|
|
|
//// task.enqueue(new DownloadListener1() {
|
|
|
|
|
|
//// @Override
|
|
|
|
|
|
//// public void taskStart(@NonNull DownloadTask task, @NonNull Listener1Assist.Listener1Model model) {
|
|
|
|
|
|
//// Logger.e("AvatarFrameView1", model);
|
|
|
|
|
|
//// }
|
|
|
|
|
|
////
|
|
|
|
|
|
//// @Override
|
|
|
|
|
|
//// public void retry(@NonNull DownloadTask task, @NonNull ResumeFailedCause cause) {
|
|
|
|
|
|
//// Logger.e("AvatarFrameView2", cause);
|
|
|
|
|
|
////
|
|
|
|
|
|
//// }
|
|
|
|
|
|
////
|
|
|
|
|
|
//// @Override
|
|
|
|
|
|
//// public void connected(@NonNull DownloadTask task, int blockCount, long currentOffset, long totalLength) {
|
|
|
|
|
|
//// Logger.e("AvatarFrameView3", blockCount);
|
|
|
|
|
|
//// }
|
|
|
|
|
|
////
|
|
|
|
|
|
//// @Override
|
|
|
|
|
|
//// public void progress(@NonNull DownloadTask task, long currentOffset, long totalLength) {
|
|
|
|
|
|
//// Logger.e("AvatarFrameView4", currentOffset);
|
|
|
|
|
|
//// }
|
|
|
|
|
|
////
|
|
|
|
|
|
//// @Override
|
|
|
|
|
|
//// public void taskEnd(@NonNull DownloadTask task, @NonNull EndCause cause, @Nullable Exception realCause, @NonNull Listener1Assist.Listener1Model model) {
|
|
|
|
|
|
//// Logger.e("AvatarFrameView5", model);
|
|
|
|
|
|
////// playMp4(task.getFile());
|
|
|
|
|
|
////// mFile = task.getFile();
|
|
|
|
|
|
////// if (cause != null && cause != EndCause.COMPLETED) {
|
|
|
|
|
|
//////// CrashReport.postCatchedException(new RuntimeException("下载任务结束:" + cause == null ? "" : cause.name() + "_realCause:" + realCause == null ? "" : realCause.getMessage()));
|
|
|
|
|
|
////// }
|
|
|
|
|
|
////
|
|
|
|
|
|
//// if (cause == EndCause.COMPLETED) {
|
|
|
|
|
|
//// File downloadedFile = task.getFile();
|
|
|
|
|
|
//// if (downloadedFile != null && downloadedFile.exists() && downloadedFile.length() > 0) {
|
|
|
|
|
|
//// playMp4(downloadedFile);
|
|
|
|
|
|
//// mFile = downloadedFile;
|
|
|
|
|
|
//// } else {
|
|
|
|
|
|
//// Logger.e(TAG, "Downloaded file is invalid");
|
|
|
|
|
|
//// handleDownloadFailure(url, cause, new IOException("Downloaded file is invalid"));
|
|
|
|
|
|
//// }
|
|
|
|
|
|
//// } else {
|
|
|
|
|
|
//// handleDownloadFailure(url, cause, realCause);
|
|
|
|
|
|
//// }
|
|
|
|
|
|
//// }
|
|
|
|
|
|
//// });
|
|
|
|
|
|
//// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2025-08-27 23:12:46 +08:00
|
|
|
|
// 添加检查进行中任务的方法
|
|
|
|
|
|
private void attachToExistingTask(DownloadTask task) {
|
|
|
|
|
|
// 为已经在队列中的任务附加监听器
|
|
|
|
|
|
task.enqueue(new DownloadListener1() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void taskEnd(@NonNull DownloadTask task, @NonNull EndCause cause, @Nullable Exception realCause, @NonNull Listener1Assist.Listener1Model model) {
|
|
|
|
|
|
if (cause == EndCause.COMPLETED) {
|
2025-08-26 19:34:44 +08:00
|
|
|
|
playMp4(task.getFile());
|
|
|
|
|
|
mFile = task.getFile();
|
2025-08-27 23:12:46 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
playNextFromQueue();
|
2025-08-26 19:34:44 +08:00
|
|
|
|
}
|
2025-08-27 23:12:46 +08:00
|
|
|
|
}
|
2025-08-26 19:34:44 +08:00
|
|
|
|
|
2025-08-27 23:12:46 +08:00
|
|
|
|
// 其他回调方法保持空实现或按需处理
|
|
|
|
|
|
@Override public void taskStart(@NonNull DownloadTask task, @NonNull Listener1Assist.Listener1Model model) {}
|
|
|
|
|
|
@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) {}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-08-26 19:34:44 +08:00
|
|
|
|
private void playMp4(File file) {
|
|
|
|
|
|
if (file != null) {
|
|
|
|
|
|
mBinding.playView.startPlay(file);
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// showAnim();
|
|
|
|
|
|
// playMp4(file);
|
|
|
|
|
|
// CrashReport.postCatchedException(new RuntimeException("播放MP4失败:File is null"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void handleSVGAComplete(SVGAVideoEntity videoItem, String url) {
|
|
|
|
|
|
// if (isDestroyed || svgaSurface == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 缓存实体(使用弱引用)
|
|
|
|
|
|
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() {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onPause() {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onFinished() {
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
|
|
|
|
mainHandler.post(() -> {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
playNextFromQueue();
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
playNextFromQueue();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
svgaSurface.startAnimation();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error handling SVGA completion: " + e.getMessage());
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
playNextFromQueue();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void playCachedSVGA(SVGAVideoEntity videoItem) {
|
|
|
|
|
|
// if (isDestroyed || svgaSurface == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
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 (isDestroyed) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
|
|
|
|
mainHandler.post(() -> {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
// 添加延迟确保状态更新
|
|
|
|
|
|
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
|
|
|
|
|
|
// playNextFromQueue();
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
// playNextFromQueue();
|
|
|
|
|
|
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
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) {
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
|
|
|
|
|
|
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 (isDestroyed || svgaSurface == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
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.setCallback(new SVGAImageViewCallback() {
|
|
|
|
|
|
// @Override
|
|
|
|
|
|
// public void onAnimationFinished() {
|
|
|
|
|
|
// isPlaying = false;
|
|
|
|
|
|
// playNextFromQueue();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// });
|
|
|
|
|
|
svgaSurface.startAnimation();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onError() {
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
playNextFromQueue();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void loadMP4(String url) {
|
|
|
|
|
|
svgaSurface.setVisibility(View.GONE);
|
|
|
|
|
|
playerView.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
|
|
|
|
glSurfaceView.setVisibility(View.VISIBLE);
|
|
|
|
|
|
glSurfaceView.onResume();
|
|
|
|
|
|
glSurfaceView.requestRender();
|
|
|
|
|
|
// 使用 post 确保 GLSurfaceView 已完成 layout
|
|
|
|
|
|
glSurfaceView.post(() -> {
|
|
|
|
|
|
Log.d("@@@", "GLSurfaceView size after layout: " + glSurfaceView.getWidth() + "x" + glSurfaceView.getHeight());
|
|
|
|
|
|
|
|
|
|
|
|
glSurfaceView.onResume();
|
|
|
|
|
|
glSurfaceView.requestRender();
|
|
|
|
|
|
|
|
|
|
|
|
renderer.setOnSurfaceTextureReadyListener(surfaceTexture -> {
|
|
|
|
|
|
mainHandler.post(() -> {
|
|
|
|
|
|
Surface surface = new Surface(surfaceTexture);
|
|
|
|
|
|
|
|
|
|
|
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(url));
|
|
|
|
|
|
exoPlayer.setMediaItem(mediaItem);
|
|
|
|
|
|
exoPlayer.setVideoSurface(surface);
|
|
|
|
|
|
exoPlayer.prepare();
|
|
|
|
|
|
exoPlayer.play();
|
|
|
|
|
|
Log.d("@@@", "ExoPlayer state after play: " + exoPlayer.getPlaybackState());
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void clearPrevious() {
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
// 确保在主线程中执行
|
|
|
|
|
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
|
|
|
|
mainHandler.post(() -> {
|
|
|
|
|
|
// if (!isDestroyed) {
|
|
|
|
|
|
clearPrevious();
|
|
|
|
|
|
// }
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 停止并清理 ExoPlayer
|
|
|
|
|
|
if (exoPlayer != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
exoPlayer.stop(); // 这里可能会在错误线程中调用
|
|
|
|
|
|
exoPlayer.clearVideoSurface();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
Logger.e("Error stopping ExoPlayer: " + e.getMessage());
|
|
|
|
|
|
// 如果在错误线程中,切换到主线程重试
|
|
|
|
|
|
mainHandler.post(() -> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (exoPlayer != null) {
|
|
|
|
|
|
exoPlayer.stop();
|
|
|
|
|
|
exoPlayer.clearVideoSurface();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
|
Logger.e("Error stopping ExoPlayer on main thread: " + ex.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 停止并清理 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);
|
|
|
|
|
|
mBinding.playView.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
|
|
|
|
// 停止播放器
|
|
|
|
|
|
if (mBinding.playView != null) {
|
|
|
|
|
|
mBinding.playView.stopPlay();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error in clearPrevious: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// if (svgaSurface.getDrawable() instanceof SVGADrawable) {
|
|
|
|
|
|
// ((SVGADrawable) svgaSurface.getDrawable()).stop();
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// if (playerView != null) playerView.setVisibility(View.GONE);
|
|
|
|
|
|
// if (svgaSurface != null) svgaSurface.setVisibility(View.GONE);
|
|
|
|
|
|
// if (glSurfaceView != null) glSurfaceView.setVisibility(View.GONE);
|
|
|
|
|
|
// mBinding.playView.setVisibility(View.GONE);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 简单的 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
|
2025-08-29 01:14:35 +08:00
|
|
|
|
protected boolean removeEldestEntry(Entry<K, V> eldest) {
|
2025-08-26 19:34:44 +08:00
|
|
|
|
return size() > maxSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 释放所有资源
|
|
|
|
|
|
*/
|
|
|
|
|
|
private void releaseResources() {
|
|
|
|
|
|
LogUtils.d(TAG, "Releasing all resources");
|
|
|
|
|
|
|
|
|
|
|
|
// if (isDestroyed) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 清理 SVGA 资源
|
|
|
|
|
|
if (svgaSurface != null && svgaSurface.getDrawable() instanceof SVGADrawable) {
|
|
|
|
|
|
SVGADrawable drawable = (SVGADrawable) svgaSurface.getDrawable();
|
|
|
|
|
|
if (drawable != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
drawable.stop();
|
|
|
|
|
|
svgaSurface.clearAnimation();
|
|
|
|
|
|
svgaSurface.setImageDrawable(null);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error releasing SVGA resources: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止并清理播放器
|
|
|
|
|
|
if (mBinding != null && mBinding.playView != null) {
|
|
|
|
|
|
mBinding.playView.stopPlay();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理 ExoPlayer 资源
|
|
|
|
|
|
if (exoPlayer != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
exoPlayer.stop();
|
|
|
|
|
|
exoPlayer.clearVideoSurface();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error releasing ExoPlayer resources: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error in releaseResources: " + 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();
|
|
|
|
|
|
|
|
|
|
|
|
// 释放所有资源
|
|
|
|
|
|
releaseResources();
|
|
|
|
|
|
|
|
|
|
|
|
// 释放 ExoPlayer
|
|
|
|
|
|
if (exoPlayer != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
exoPlayer.stop();
|
|
|
|
|
|
exoPlayer.release();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error releasing ExoPlayer: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
exoPlayer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理 PlayerView
|
|
|
|
|
|
if (playerView != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
playerView.setPlayer(null);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error releasing PlayerView: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
playerView = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理 SVGAImageView
|
|
|
|
|
|
if (svgaSurface != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
svgaSurface.pauseAnimation();
|
|
|
|
|
|
svgaSurface.clearAnimation();
|
|
|
|
|
|
svgaSurface.setImageDrawable(null);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtils.e(TAG, "Error releasing SVGAImageView: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
svgaSurface = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理 binding
|
|
|
|
|
|
if (mBinding != null) {
|
|
|
|
|
|
mBinding = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理队列
|
|
|
|
|
|
playQueue.clear();
|
|
|
|
|
|
|
|
|
|
|
|
} 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;
|
|
|
|
|
|
// 清理当前正在播放的内容
|
|
|
|
|
|
clearPrevious();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.image != null && isPlaying && mBinding.image.isAnimating()) {
|
|
|
|
|
|
// mBinding.image.setAnimation(null);
|
|
|
|
|
|
// mBinding.image.clearAnimation();
|
|
|
|
|
|
// mBinding.image.stopAnimation();
|
|
|
|
|
|
// }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|