package com.qxcm.moduleutil.widget; import android.content.Context; import android.graphics.SurfaceTexture; import android.net.Uri; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.util.LruCache; 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; import com.liulishuo.okdownload.StatusUtil; 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.qxcm.moduleutil.R; import com.qxcm.moduleutil.databinding.RoomViewSvgaAnimBinding; import com.qxcm.moduleutil.utils.DownloadUtil; import com.qxcm.moduleutil.utils.Md5Utils; import com.qxcm.moduleutil.utils.MemoryOptimizationUtils; import com.qxcm.moduleutil.utils.SpUtil; import com.qxcm.moduleutil.utils.logger.Logger; import com.tencent.qgame.animplayer.AnimConfig; import com.tencent.qgame.animplayer.inter.IAnimListener; import com.tencent.qgame.animplayer.inter.IFetchResource; import java.io.File; 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; 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(); // 播放下一项 } } @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 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> 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; } 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"); } } 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(); } } 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; } // public void setSource(String url, int type2) { // if (SpUtil.getOpenEffect()==1) { // playQueue.offer(new PlayItem(url, type2)); // if (!isPlaying) { // playNextFromQueue(); // } // }else { // playQueue.clear(); // isPlaying = false; // } // //// RenderType type = null; //// if ("svga".equalsIgnoreCase(getFileExtension(url))){ //// type = RenderType.SVGA; //// }else if ("mp4".equalsIgnoreCase(getFileExtension(url))){ //// type = RenderType.MP4; //// } //// //// clearPrevious(); //// renderType = type; //// mType = type2; //// switch (type) { //// case SVGA: //// mBinding.playView.stopPlay(); //// mBinding.playView.setVisibility(View.GONE); //// loadSVGA(url); //// break; //// case MP4: ////// loadMP4(url); //// mBinding.playView.setVisibility(View.VISIBLE); //// downloadAndPlayMp4(url); //// break; //// } // } private void downloadAndPlayMp4(String url) { String filePath = PathUtils.getInternalAppCachePath() + Md5Utils.getStringMD5(url) + ".mp4"; File file = new File(filePath); if (file.exists()) { playMp4(file); mFile = file; } else { DownloadTask task = new DownloadTask.Builder(url, PathUtils.getInternalAppCachePath() , Md5Utils.getStringMD5(url) + ".mp4") .setMinIntervalMillisCallbackProcess(100) .setPassIfAlreadyCompleted(false) .setAutoCallbackToUIThread(true) .build(); // if (StatusUtil.isCompleted(task)) { // playMp4(task.getFile()); // mFile = task.getFile(); // } else { task.enqueue(new DownloadListener1() { @Override public void taskStart(@NonNull DownloadTask task, @NonNull Listener1Assist.Listener1Model model) { Logger.e("taskStart", model); } @Override public void retry(@NonNull DownloadTask task, @NonNull ResumeFailedCause cause) { Logger.e("retry", cause); task.cancel(); // CrashReport.postCatchedException(new RuntimeException("下载文件重试:" + cause == null ? "" : cause.name())); } @Override public void connected(@NonNull DownloadTask task, int blockCount, long currentOffset, long totalLength) { Logger.e("connected", blockCount); } @Override public void progress(@NonNull DownloadTask task, long currentOffset, long totalLength) { Logger.e("progress", currentOffset); } @Override public void taskEnd(@NonNull DownloadTask task, @NonNull EndCause cause, @Nullable Exception realCause, @NonNull Listener1Assist.Listener1Model model) { Logger.e("taskEnd", 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())); } } }); // } } } 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; playNextFromQueue(); }); } else { isPlaying = false; playNextFromQueue(); } } }); svgaSurface.startAnimation(); } catch (Exception e) { LogUtils.e(TAG, "Error playing cached SVGA: " + e.getMessage()); isPlaying = false; 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 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 extends LinkedHashMap { private final int maxSize; public LruCache(int maxSize) { super(16, 0.75f, true); this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { 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(); // } } }