Files
yusheng-android/BaseModule/src/main/java/com/xscm/moduleutil/widget/AvatarFrameView.java
2026-01-22 15:28:44 +08:00

572 lines
19 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.xscm.moduleutil.widget;
import android.content.Context;
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.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.xscm.moduleutil.R;
import com.xscm.moduleutil.databinding.RoomViewSvgaAnimBinding;
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.LinkedHashMap;
import java.util.Map;
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 boolean isMute = false;
public void setMute(boolean b) {
this.isMute = b;
}
public enum RenderType {SVGA, MP4}
private RenderType renderType;
private SVGAImageView svgaSurface;
private int mType;//1:循环播放 2:播放一次停止播放
private boolean isPlaying = false;
// 添加销毁标记
private boolean isDestroyed = false;
private static final String TAG = "AvatarFrameView";
private RoomViewSvgaAnimBinding mBinding;
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() {
// 初始化 SVGA View
svgaSurface = new SVGAImageView(getContext());
svgaSurface.setVisibility(View.GONE);
addView(svgaSurface);
}
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 setSource(String url, int type2) {
// 添加到播放队列
PlayItem item = new PlayItem(url, type2);
processPlayItem(item);
Logger.d("AvatarFrameView", "Added to queue, queue size: url: " + url);
}
private void processPlayItem(PlayItem item) {
try {
String ext = getFileExtension(item.url);
if ("svga".equalsIgnoreCase(ext)) {
renderType = RenderType.SVGA;
mType = item.type;
if (mBinding != null) {
mBinding.playView.setVisibility(View.GONE);
}
loadSVGA(item.url);
} else if ("mp4".equalsIgnoreCase(ext)) {
renderType = RenderType.MP4;
mType = item.type;
if (mBinding != 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);
}
}
} catch (Exception e) {
LogUtils.e(TAG, "Error processing play item: " + e.getMessage());
}
}
public boolean isPlaying() {
if (mBinding != null) {
return mBinding.playView.isRunning();
}
return true;
}
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());
}
// 更简单的优化版本
@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;
// 关键在执行UI操作前再次检查是否已销毁
if (downloadedFile.exists()) {
LogUtils.d("@@@@Thread", Thread.currentThread().getName());
playMp4File(downloadedFile); // 使用正确的文件引用
} else {
LogUtils.w(TAG, "View destroyed or file not exist after download");
}
} catch (IOException e) {
LogUtils.e("MP4文件保存失败: " + e.getMessage());
}
}
} catch (Exception e) {
LogUtils.e("MP4文件保存失败: " + e.getMessage());
}
} else {
LogUtils.e("MP4下载响应失败");
}
}
});
} else {
isTxk = true;
// 检查是否已销毁
if (file.exists()) {
LogUtils.e("有缓存:" + file.exists() + "====" + file.getAbsolutePath());
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");
return;
}
if (mBinding == null) {
LogUtils.w(TAG, "PlayView is null");
return;
}
if (file != null && file.exists()) {
// 设置循环次数根据mType决定
if (mType == 1 || mType == 3) {
mBinding.playView.setLoop(Integer.max(1, 999999999)); // 无限循环
} else {
mBinding.playView.setLoop(1); // 播放一次
}
mBinding.playView.setMute(isMute);
// 开始播放前检查视图状态
if (!isDestroyed && mBinding != null && mBinding.playView != null) {
mBinding.playView.startPlay(file);
} else {
LogUtils.w(TAG, "View was destroyed before MP4 playback started");
}
} else {
LogUtils.e("播放MP4文件出错: 文件不存在或已损坏");
}
} catch (Exception e) {
LogUtils.e("播放MP4文件出错: " + e.getMessage());
}
}
private void handleSVGAComplete(SVGAVideoEntity videoItem, String url) {
if (svgaSurface == null) {
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);
}
}
@Override
public void onPause() {
}
@Override
public void onFinished() {
if (mType == 1) { // 循环播放
// 继续循环播放
}
}
});
// 设置循环次数
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());
}
}
public void stopAll() {
if (svgaSurface != null) {
svgaSurface.stopAnimation();
svgaSurface.clearAnimation();
svgaSurface.setImageDrawable(null);
}
// 增加空值检查
if (mBinding != null) {
mBinding.playView.stopPlay();
}
}
public void stopPlay(){
// 增加空值检查
if (mBinding != null && mBinding.playView.isRunning()) {
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() {
isPlaying = false;
}
});
// 设置循环次数
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;
}
}
private void loadNewSVGA(String url) {
try {
new SVGAParser(getContext()).parse(new URL(url), new SVGAParser.ParseCompletion() {
@Override
public void onComplete(SVGAVideoEntity videoItem) {
if (Looper.myLooper() != Looper.getMainLooper()) {
handleSVGAComplete(videoItem, url);
} else {
handleSVGAComplete(videoItem, url);
}
}
@Override
public void onError() {
isPlaying = false;
}
});
} catch (Exception e) {
LogUtils.e(TAG, "Error parsing SVGA: " + e.getMessage());
isPlaying = false;
}
}
private void loadSVGA(String url) {
try {
svgaSurface.setVisibility(View.VISIBLE);
// 检查缓存
WeakReference<SVGAVideoEntity> cachedRef = svgaCache.get(url);
SVGAVideoEntity cachedEntity = cachedRef != null ? cachedRef.get() : null;
if (cachedEntity != null) {
// 使用缓存的实体
playCachedSVGA(cachedEntity);
return;
} else {
// 加载新的SVGA
loadNewSVGA(url);
}
} catch (Exception e) {
LogUtils.e(TAG, "Error loading SVGA: " + e.getMessage());
isPlaying = false;
}
// 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;
// }
// });
//
// svgaSurface.startAnimation();
// }
//
// @Override
// public void onError() {
// isPlaying = false;
// }
// });
// } 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 (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");
performUICleanup();
}
/**
* 在主线程执行 UI 相关的清理操作
*/
private void performUICleanup() {
try {
// 停止并清理播放器
if (mBinding != null) {
try {
mBinding.playView.stopPlay();
} catch (Exception e) {
Logger.e(TAG, "Error stopping playView: " + e.getMessage());
}
}
// 清理 SVGA 资源
if (svgaSurface != null) {
try {
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");
isDestroyed = true;
try {
// 清空播放队列
clearQueue();
// 释放所有资源
releaseResources();
} catch (Exception e) {
LogUtils.e(TAG, "Error in AvatarFrameView release: " + e.getMessage());
}
}
public void clearQueue() {
isPlaying = false;
// 清理当前正在播放的内容
clearPrevious();
}
private static class PlayItem {
String url;
int type;
PlayItem(String url, int type) {
this.url = url;
this.type = type;
}
}
}