1:修改播放礼物特效

2:修改特效按照ios方式更改
This commit is contained in:
2025-09-13 15:38:16 +08:00
parent 5635d0c707
commit 1d348784ea
3 changed files with 809 additions and 0 deletions

View File

@@ -0,0 +1,389 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.LogUtils;
import com.opensource.svgaplayer.SVGACallback;
import com.opensource.svgaplayer.SVGADrawable;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import com.tencent.qgame.animplayer.AnimConfig;
import com.tencent.qgame.animplayer.AnimView;
import com.tencent.qgame.animplayer.inter.IAnimListener;
import com.xscm.moduleutil.bean.GiftBean;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class GiftAnimView extends FrameLayout implements GiftSvgaView.OnAnimationListener {
private AnimView playerMp4View;
private GiftSvgaView svgaView;
private GiftBean playModel;
private boolean isLoadEffect = false;
private boolean isShow = true;//是否开启特效
private ReentrantLock lock = new ReentrantLock();
private List<GiftBean> giftArray = new ArrayList<>();
public ExecutorService queue = Executors.newSingleThreadExecutor();
private Context mContext;
public void setQueue(ExecutorService queue) {
this.queue = queue;
}
public GiftAnimView(Context context) {
super(context);
this.mContext = context;
init();
}
private void init() {
isLoadEffect = false;
// 初始化SVGA视图
svgaView = new GiftSvgaView(getContext());
addView(svgaView);
playerMp4View = new AnimView(getContext());
addView(playerMp4View);
// 设置布局参数 - 在Android中通常使用LayoutParams
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
playerMp4View.setLoop(1);
playerMp4View.setLayoutParams(params);
playerMp4View.setAnimListener(new IAnimListener() {
@Override
public void onVideoDestroy() {
LogUtils.e("onVideoDestroy");
}
@Override
public void onVideoComplete() {
LogUtils.e("onVideoComplete");
post(() -> {
playerMp4View.setVisibility(View.GONE);
loadStartSVGAPlayer();
});
}
@Override
public void onVideoRender(int i, @Nullable AnimConfig animConfig) {
LogUtils.e("onVideoRender", i, animConfig);
}
@Override
public void onVideoStart() {
LogUtils.e("onVideoStart");
}
@Override
public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
LogUtils.e("onVideoConfigReady", animConfig);
return true;
}
@Override
public void onFailed(int i, @Nullable String s) {
LogUtils.e("onFailed", s);
}
});
svgaView.setDidFinishedDisplay(this);
svgaView.setDidStartAnimation(this);
}
/// 礼物特效进来 gift为礼物实体类
public void displayEffectView(final GiftBean gift) {
queue.execute(new Runnable() {
@Override
public void run() {
/// 如果play_image不存在return
if (gift == null || gift.getPlay_image().isEmpty()) {
return;
}
/// 将play_image链接转为小写
String playImage = gift.getPlay_image();
String pathExtension = getFileExtension(playImage).toLowerCase();
/// 判定礼物的后缀是否为svga或者mp4如果非这两种 则return
if (!("svga".equals(pathExtension) || "mp4".equals(pathExtension))) {
return;
}
/// 锁住list
lock.lock();
try {
/// 添加礼物进list
giftArray.add(gift);
/// 如果没有在加载则开始加载
if (!isLoadEffect) {
/// 更改加载状态标记
isLoadEffect = true;
loadStartSVGAPlayer();
}
} finally {
/// 解锁
lock.unlock();
}
}
});
}
public void openOrCloseEffectViewWith(boolean isShow) {
this.isShow = isShow;
removeSvgaQueueData();
playerMp4View.stopPlay();
playerMp4View.setVisibility(View.GONE);
setVisibility(isShow ? View.VISIBLE : View.GONE);
}
public void stopPlay() {
removeSvgaQueueData();
playerMp4View.stopPlay();
playerMp4View.setVisibility(View.GONE);
}
private void loadStartSVGAPlayer() {
if (!isShow) {
/// isshow 为是否开启特效 如果未开启则return
return;
}
GiftBean giftModel = null;
/// list加锁
lock.lock();
try {
/// 如果list长度大于0
if (!giftArray.isEmpty()) {
/// gift的实体类则赋值取list中的第一个数据
giftModel = giftArray.get(0);
/// 移除list的第一条数据
giftArray.remove(0);
isLoadEffect = true;
} else {
isLoadEffect = false;
}
} finally {
/// 解锁
lock.unlock();
}
if (isLoadEffect && giftModel != null && !TextUtils.isEmpty(giftModel.getPlay_image())) {
GiftBean finalGiftModel = giftModel;
post(new Runnable() {
@Override
public void run() {
String playImage = finalGiftModel.getPlay_image();
if (playImage.endsWith("mp4")) {
downloadAndPlay(getContext(), playImage, new DownloadCallback() {
@Override
public void onSuccess(File file) {
post(() -> {
playerMp4View.setVisibility(View.VISIBLE);
svgaView.setVisibility(View.GONE);
playerMp4View.startPlay(file);
});
}
@Override
public void onFailure(Exception e) {
LogUtils.e("MP4下载或播放失败: " + e.getMessage());
// 处理失败情况,继续播放下一个
post(() -> {
lock.lock();
try {
isLoadEffect = false;
} finally {
lock.unlock();
}
loadStartSVGAPlayer();
});
}
});
} else if (playImage.endsWith("svga")) {
// File file = downloadAndPlay(getContext(), playImage);
post(() -> {
playerMp4View.setVisibility(View.GONE);
svgaView.setVisibility(View.VISIBLE);
svgaView.loadSVGAPlayerWith(finalGiftModel.getPlay_image(), false);
});
} else {
lock.lock();
try {
isLoadEffect = false;
} finally {
lock.unlock();
}
// 直接播放缓存文件
}
}
});
}
}
public void downloadAndPlay(Context context, String playImage, DownloadCallback callback) {
String fileName = playImage.substring(playImage.lastIndexOf("/"));
String filePath = context.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(playImage)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
LogUtils.e("MP4下载失败: " + e.toString());
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(e);
}
});
}
@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();
// 在主线程中回调成功
post(() -> {
if (callback != null) {
callback.onSuccess(downloadedFile);
}
});
} else {
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(new IOException("Response body is null"));
}
});
}
} catch (Exception e) {
LogUtils.e("MP4文件保存失败: " + e.getMessage());
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(e);
}
});
}
} else {
LogUtils.e("MP4下载响应失败");
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(new IOException("Response not successful: " + response.code()));
}
});
}
}
});
} else {
// 文件已存在,直接回调成功
if (callback != null) {
callback.onSuccess(file);
}
}
}
// 添加回调接口
public interface DownloadCallback {
void onSuccess(File file);
void onFailure(Exception e);
}
public void destroyEffectView() {
removeSvgaQueueData();
svgaView.stopEffectSvgaPlay();
playerMp4View.stopPlay();
removeView(playerMp4View);
removeView(svgaView);
svgaView = null;
playerMp4View = null;
playModel = null;
if (queue != null) {
queue.shutdown();
queue = null;
}
}
private void removeSvgaQueueData() {
lock.lock();
try {
giftArray.clear();
isLoadEffect = false;
} finally {
lock.unlock();
}
}
private String getFileExtension(String url) {
if (url != null && url.contains(".")) {
return url.substring(url.lastIndexOf(".") + 1);
}
return "";
}
@Override
public void onStartAnimation(GiftSvgaView view) {
}
@Override
public void onFinishedDisplay(GiftSvgaView view) {
post(() -> {
svgaView.setVisibility(View.GONE);
loadStartSVGAPlayer();
});
}
}

View File

@@ -0,0 +1,283 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.opensource.svgaplayer.SVGACallback;
import com.opensource.svgaplayer.SVGADrawable;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class GiftSvgaView extends FrameLayout implements SVGACallback {
private SVGAImageView player;
private SVGAParser parser;
private boolean isAutoPlay = true;
// 回调接口
public interface OnAnimationListener {
void onStartAnimation(GiftSvgaView view);
void onFinishedDisplay(GiftSvgaView view);
}
private OnAnimationListener didStartAnimation;
private OnAnimationListener didFinishedDisplay;
public GiftSvgaView(Context context) {
this(context, null);
}
public GiftSvgaView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GiftSvgaView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initializeData(true);
}
public GiftSvgaView(Context context, boolean isAutoPlay) {
super(context);
initializeData(isAutoPlay);
}
private void initializeData(boolean isAutoPlay) {
this.isAutoPlay = isAutoPlay;
setBackgroundColor(Color.TRANSPARENT); // clearColor
setClickable(false); // userInteractionEnabled = NO
initPlayer();
}
private void initPlayer() {
player = new SVGAImageView(getContext());
player.setBackgroundColor(Color.TRANSPARENT);
player.setScaleType(ImageView.ScaleType.CENTER_CROP); // UIViewContentModeScaleAspectFill
// 如果需要 ScaleAspectFit使用 ImageView.ScaleType.FIT_CENTER
// 设置布局参数 - 填满父视图
LayoutParams params = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
addView(player, params);
// 初始化解析器
parser = new SVGAParser(getContext());
player.setCallback(this);
}
public void loadSVGAPlayerWith(String loadPath) {
loadSVGAPlayerWith(loadPath, false);
}
public void loadSVGAPlayerWith(String loadPath, boolean inBundle) {
loadSVGAPlayerWith(loadPath, inBundle, 1);
}
public void loadSVGAPlayerWith(String loadPath, boolean inBundle, int loop) {
if (loadPath == null || loadPath.isEmpty()) {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
return;
}
if (player == null) {
initPlayer();
}
stopEffectSvgaPlay();
player.setLoops(loop);
if (loadPath.startsWith("https:") || loadPath.startsWith("http:")) {
// URL加载
try {
parser.parse(new URL(loadPath), new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
});
} catch (Exception e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
} else if (inBundle) {
// 从Assets加载
try {
parser.parse(loadPath, new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
});
} catch (Exception e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
} else {
// 从文件路径加载
try {
File file = new File(loadPath);
if (!file.exists() || file.length() < 4) {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
return;
}
InputStream inputStream = new FileInputStream(file);
parser.parse(inputStream, loadPath, new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
}, true);
} catch (IOException e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
}
}
// Public方法
public void startEffectSvgaPlay() {
if (player != null && player.getDrawable() != null) {
player.startAnimation();
}
}
public void pauseEffectSvgaPlay() {
if (player != null) {
player.pauseAnimation();
}
}
public void stopEffectSvgaPlay() {
if (player != null) {
player.stopAnimation();
}
}
public void destroySvga() {
stopEffectSvgaPlay();
if (player != null) {
removeView(player);
player = null;
}
parser = null;
}
// SVGACallback接口实现
@Override
public void onPause() {
// 暂停回调
}
@Override
public void onFinished() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
@Override
public void onRepeat() {
// 重复回调
}
@Override
public void onStep(int frame, double percentage) {
// 步骤回调
}
// Getter和Setter方法
public void setDidStartAnimation(OnAnimationListener listener) {
this.didStartAnimation = listener;
}
public void setDidFinishedDisplay(OnAnimationListener listener) {
this.didFinishedDisplay = listener;
if (player != null) {
player.setCallback(this);
}
}
public boolean isAutoPlay() {
return isAutoPlay;
}
public void setAutoPlay(boolean autoPlay) {
isAutoPlay = autoPlay;
}
public SVGAImageView getPlayer() {
return player;
}
public SVGAParser getParser() {
return parser;
}
}

View File

@@ -0,0 +1,137 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.xscm.moduleutil.bean.GiftBean;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class QXGiftPlayerManager {
private static QXGiftPlayerManager instance;
private View bgEffectView;
private GiftAnimView fullEffectView;
private GiftAnimView chatEffectView;
private Context context;
private QXGiftPlayerManager(Context context) {
this.context = context.getApplicationContext();
}
public static synchronized QXGiftPlayerManager getInstance(Context context) {
if (instance == null) {
instance = new QXGiftPlayerManager(context);
}
return instance;
}
public View getDefaultBgEffectView() {
if (bgEffectView == null) {
initBgEffectView();
}
return bgEffectView;
}
public GiftAnimView getDefaultFullEffectView() {
if (fullEffectView == null) {
initFullEffectView();
}
return fullEffectView;
}
public GiftAnimView getDefaultChatEffectView() {
if (chatEffectView == null) {
initChatEffectView();
}
return chatEffectView;
}
private void initBgEffectView() {
bgEffectView = new FrameLayout(context);
bgEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
bgEffectView.setBackgroundColor(0x00000000);
bgEffectView.setClickable(false); // userInteractionEnabled = NO
// 添加全屏特效视图和聊天特效视图
((ViewGroup) bgEffectView).addView(getDefaultFullEffectView());
((ViewGroup) bgEffectView).addView(getDefaultChatEffectView());
}
public void displayFullEffectView(GiftBean gift) {
getDefaultFullEffectView().displayEffectView(gift);
}
public void displayChatEffectView(GiftBean gift) {
getDefaultChatEffectView().displayEffectView(gift);
}
public void openOrCloseEffectViewWith(boolean isShow) {
getDefaultFullEffectView().openOrCloseEffectViewWith(isShow);
getDefaultChatEffectView().openOrCloseEffectViewWith(isShow);
}
public void destroyEffectSvga() {
if (fullEffectView != null) {
fullEffectView.destroyEffectView();
if (bgEffectView != null) {
((ViewGroup) bgEffectView).removeView(fullEffectView);
}
fullEffectView = null;
}
if (chatEffectView != null) {
chatEffectView.destroyEffectView();
if (bgEffectView != null) {
((ViewGroup) bgEffectView).removeView(chatEffectView);
}
chatEffectView = null;
}
if (bgEffectView != null) {
bgEffectView = null;
}
}
public void stopPlay() {
if (fullEffectView != null) {
fullEffectView.stopPlay();
}
if (chatEffectView != null) {
chatEffectView.stopPlay();
}
}
private void initFullEffectView() {
fullEffectView = new GiftAnimView(context);
fullEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
// 创建专用线程池替代GCD队列
ExecutorService queue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory());
fullEffectView.setQueue(queue);
}
private void initChatEffectView() {
chatEffectView = new GiftAnimView(context);
chatEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
// 创建专用线程池替代GCD队列
ExecutorService queue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory());
chatEffectView.setQueue(queue);
}
}