卢春龙
2026-08-17 f525b6c3cc105bd1a997d893937689bebcc9570d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
 * 视频预下载缓存:用 wx.downloadFile 提前把转场 mp4 落地为本地临时文件,
 * 转场时改用本地路径作 <video src>,消除首帧网络缓冲导致的黑帧。
 *
 * ## 背景
 * wx.preloadAssets 仅支持 image / font,**不支持视频**。派单转场 mp4 直接用 CDN URL 作
 * <video src> 时,首帧需先经网络请求 + 缓冲,「加载完成 → 首帧绘制」间会露出约一帧黑屏。
 * 方案:进入下单流程(发布中/呼叫中)即 downloadFile 把 mp4 下到本地临时路径,转场时用本地
 * 路径播放——本地文件无网络缓冲,首帧秒出。
 *
 * ## 使用
 * - preloadVideo(url):幂等发起下载,成功后缓存 `CDN URL → 本地 tempFilePath`。
 * - getPreloadedVideoSrc(url):取本地路径;未就绪(下载中/失败/非 weapp)则回退原 CDN URL,不阻塞。
 *
 * 参考:https://developers.weixin.qq.com/miniprogram/dev/api/network/download/wx.downloadFile.html
 */
 
/** 判断当前是否为微信小程序环境。 */
const isWeapp = (): boolean => process.env.TARO_ENV === 'weapp';
 
/** CDN URL → 本地临时文件路径(下载成功后写入)。 */
const localPathCache: Record<string, string> = {};
 
/** 已发起下载的 URL(下载中/已完成均登记,避免重复请求;失败时移除以允许重试)。 */
const pendingSet = new Set<string>();
 
/**
 * 幂等预下载视频。已缓存或正在下载则直接返回;非 weapp / 低版本无 downloadFile 时静默跳过。
 * 下载失败不报错、不缓存,转场时自动回退 CDN URL。
 *
 * @param url 视频 CDN 地址
 */
export const preloadVideo = (url: string): void => {
  try {
    if (!isWeapp() || !url) return;
    if (localPathCache[url] || pendingSet.has(url)) return;
    // @ts-ignore 微信小程序全局 API,低版本走 typeof 兜底跳过
    if (typeof wx === 'undefined' || typeof wx.downloadFile !== 'function') return;
 
    pendingSet.add(url);
    // @ts-ignore
    wx.downloadFile({
      url,
      success: (res: any) => {
        if (res && res.statusCode === 200 && res.tempFilePath) {
          localPathCache[url] = res.tempFilePath;
        } else {
          // 非 200:不缓存,允许后续重试
          pendingSet.delete(url);
        }
      },
      fail: () => {
        // 下载失败:不缓存、允许后续重试,转场时回退 CDN URL(不影响功能)
        pendingSet.delete(url);
      },
    });
  } catch (e) {
    console.error('[preloadVideo] 视频预下载异常:', e);
    // 兜底:绝不抛出
  }
};
 
/**
 * 取预下载好的本地视频路径;未就绪则回退原 CDN URL。
 *
 * @param url 视频 CDN 地址
 * @returns 本地临时路径(已就绪)或原 CDN URL
 */
export const getPreloadedVideoSrc = (url: string): string => localPathCache[url] || url;