diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/widget/GiftAnimView.java b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/GiftAnimView.java new file mode 100644 index 0000000..4749660 --- /dev/null +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/GiftAnimView.java @@ -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 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(); + }); + } +} diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/widget/GiftSvgaView.java b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/GiftSvgaView.java new file mode 100644 index 0000000..78d3637 --- /dev/null +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/GiftSvgaView.java @@ -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; + } +} diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/widget/QXGiftPlayerManager.java b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/QXGiftPlayerManager.java new file mode 100644 index 0000000..da4d6ba --- /dev/null +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/QXGiftPlayerManager.java @@ -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(), + 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(), + Executors.defaultThreadFactory()); + chatEffectView.setQueue(queue); + } +}