前提条件

开发环境

详见 面板小程序 > 搭建环境

在复杂视频场景中,利用 AI 技术识别并突出显示关键主体(如宠物、人像等),以增强视觉焦点。

功能拆分

开发者平台创建面板小程序

面板小程序的开发在 小程序开发者 平台上进行操作,首先请前往 小程序开发者平台 完成平台的注册登录。

详细操作步骤,请参考 创建面板小程序

IDE 基于示例模板创建项目工程

打开物料广场基于 AI 视频主体突出示例 物料,创建一个小程序项目,需要在 Tuya MiniApp IDE 上操作。

详细操作步骤,请参考 初始化项目工程

完成以上步骤后,一个面板小程序的开发模板初始化完成。以下为工程目录的介绍:

├── src
│  	├── api // 面板所有云端 API 请求聚合文件
│  	├── components
│  	│   ├── GlobalToast // 全局轻弹窗
│  	│   ├── IconFont // svg 图标容器组件
│  	│   ├── MusicSelectModal // 背景音乐选择弹窗
│  	│   ├── TopBar // 顶部信息栏
│  	│   ├── TouchableOpacity // 点击按钮组件
│  	│   ├── Video // 视频容器组件
│  	│   ├── VideoSeeker // 视频滑动条组件
│  	├── constant
│  	│   ├── dpCodes.ts // dpCode 常量
│  	│   ├── index.ts // 存放所有的常量配置
│  	├── devices // 设备模型
│  	├── hooks // hooks
│  	├── i18n // 多语言
│  	├── pages
│   │   ├── home // 首页
│   │       ├── AISkillBtn // AI 视频处理能力按钮组件
│   │       ├── MusicSelectBtn // 背景音乐选择按钮组件
│  	├── redux // redux
│   ├── res // 图片资源 & svg 相关
│   ├── styles // 全局样式
│   ├── types // 全局类型定义
│   ├── utils // 业务常用工具方法
│   ├── app.config.ts
│   ├── app.less
│   ├── app.tsx
│   ├── composeLayout.tsx // 处理监听子设备添加、解绑、DP 变化等
│   ├── global.config.ts
│   ├── mixins.less // less mixins
│   ├── routes.config.ts // 配置路由
│   ├── variables.less // less variables

视频初始化导入

功能介绍

视频资源在初始化导入阶段依赖以下两个关键能力:

相关代码段

import { chooseMedia, clipVideo } from "@ray-js/ray";

// 由于系统兼容性问题,该路径用于视频展示
const [videoSrc, setVideoSrc] = useState("");
// 由于系统兼容性问题,该路径用于视频生成
const [videoFileSrc, setVideoFileSrc] = useState("");
// 存储视频首帧渲染图片
const [posterSrc, setPosterSrc] = useState("");

// 导入视频资源
const getVideoSourceList = () => {
  paseVideo();
  chooseMedia({
    mediaType: "video",
    sourceType: ["album", "camera"],
    isFetchVideoFile: true,
    success: (res) => {
      initState();
      const { tempFiles } = res;
      console.log("==tempFiles", tempFiles);
      clipVideo({
        filePath: tempFiles[0].tempFilePath,
        startTime: 0,
        endTime: tempFiles[0].duration * 1000,
        level: 4,
        success: ({ videoClipPath }) => {
          console.log("====clipVideo==success", videoClipPath);
          setVideoSrc(videoClipPath);
          setPosterSrc(tempFiles[0].thumbTempFilePath);
          setHandleState("inputDone");
        },
        fail: (error) => {
          console.log("====clipVideo==fail", error);
          setHandleState("idle");
        },
      });
    },
  });
};

视频 AI 生成后导出

功能介绍

视频 AI 生成后导出功能,主要用于支持 C 端用户完成视频素材编辑后将视频转存至手机相册。

相关代码段

import { saveVideoToPhotosAlbum, showToast } from "@ray-js/ray";

// 由于系统兼容性问题,该路径用于视频生成
const [videoFileSrc, setVideoFileSrc] = useState("");

// 导出视频资源
const handleOutputVideo = () => {
  saveVideoToPhotosAlbum({
    filePath: videoFileSrc,
    success: (res) => {
      // 进行视频导出成功提示
      showToast({
        title: Strings.getLang("dsc_output_video_success"),
        icon: "success",
      });
    },
    fail: (error) => {
      console.log("==saveVideoToPhotosAlbum==fail", error);
    },
  });
};

视频展示组件

组件介绍

视频展示组件用于原始视频素材、AI 生成视频素材的展示,支持 C 端用户随时播放、暂停视频,同时组件内部镶嵌了视频进度条,支持 C 端用户滑动预览视频内容。

相关代码段

import { saveVideoToPhotosAlbum, showToast } from "@ray-js/ray";

// 由于系统兼容性问题,该路径用于视频展示
const [videoSrc, setVideoSrc] = useState("");
// 存储视频首帧渲染图片
const [posterSrc, setPosterSrc] = useState("");

<Video id="video-editor" type="previewer" src={videoSrc} poster={posterSrc} />;

获取默认背景音乐

功能介绍

目前模版提供部分默认背景音乐供开发者获取、使用,后续音乐内容将进行扩充,其中获取默认背景音乐主要分为以下两个步骤:

  1. 获取默认背景音乐在线地址。
  2. 下载在线背景音乐至手机本地。

相关代码段

import { backgroundMusicList, backgroundMusicDownload } from "@ray-js/ray";

const createLocalMusicPath = (idx: number) => {
  const fileRoot = ty.env.USER_DATA_PATH;
  const filePath = `${fileRoot}/music${idx + 1}.mp4`;
  return filePath;
};

// 下载所有音乐
const downloadMusic = (musicArray: Array<ItemMusic>) => {
  // 使用 map 遍历每个 URL,并用 Promise 包裹同步调用
  const downloadPromises = musicArray.map((music, index) => {
    const localPath = createLocalMusicPath(index);

    return new Promise((resolve, reject) => {
      try {
        backgroundMusicDownload({
          musicUrl: music.musicUrl,
          musicPath: localPath,
          success: (res) => {
            console.log("==backgroundMusicDownload==success", res);
            resolve({
              id: music.musicTitle,
              ...music,
              musicLocalPath: localPath,
            });
          },
          fail: ({ errorMsg }) => {
            console.log("==backgroundMusicDownload==fail", errorMsg);
          },
        });
      } catch (error) {
        reject(error);
      }
    });
  });

  return Promise.all(downloadPromises)
    .then((localMusicArray) => {
      console.log("下载完成:", localMusicArray);
      return localMusicArray;
    })
    .catch((error) => {
      console.error("下载过程中出现错误:", error);
    });
};

// 初始化本地音乐资源列表
const initLocalMusicSource = () => {
  backgroundMusicList({
    success: ({ musicList }) => {
      console.log("===backgroundMusicList==", musicList);

      downloadMusic(musicList)
        .then((resList) => {
          console.log("===downloadMusic==success", resList);
          // 将背景音乐数据缓存至面板 redux 中
          dispatch(updateMusicInfoList(resList));
        })
        .catch((error) => {
          console.log("===downloadMusic==fail", error);
        });
    },
    fail: ({ errorMsg }) => {
      console.log("====backgroundMusicList==fail", errorMsg);
    },
  });
};

// 在面板初始化时完成背景音乐初始化
initLocalMusicSource();

展示、试听默认背景音乐

功能介绍

目前模版提供 LocalMusicList 组件,用于展示、试听背景音乐;其中音乐的试听能力,主要通过 InnerAudioContext 实例实现。

相关代码段

import { createInnerAudioContext } from "@ray-js/ray";

// 初始化 audioManager 实例
const audioManager = createInnerAudioContext({
  success: (res) => {
    console.log("==createInnerAudioContext==success", res);
  },
  fail: (error) => {
    console.log("==createInnerAudioContext==fail", error);
  },
});

// 通过本地路径播放本地音乐
audioManager.play({
  src: currentMusic.musicLocalPath,
  success: (res) => {
    console.log("==play==success", res);
  },
  fail: (error) => {
    console.log("==play==fail", error);
  },
});

// 暂停音乐播放
audioManager.pause({
  success: (res) => {
    console.log("==stop==pause", res);
  },
  fail: (error) => {
    console.log("==stop==pause", error);
  },
});

// 销毁音频管理实例
audioManager.destroy({
  success: (res) => {
    console.log("==destroy==success", res);
  },
  fail: (error) => {
    console.log("==destroy==fail", error);
  },
});

宠物、人像主体突出

功能介绍

C 端用户可在模版中选择希望突出的主体分类,AI 将根据用户选项自动处理。

视频流背景音乐编辑

功能介绍

C 端用户可在模版中选择希望与 AI 视频合并导出的背景音乐,同时模版支持 C 端用户自定义原视频音频与背景音乐的混音比例。

关键 API 代码段

import { ai } from "@ray-js/ray";
import { createTempVideoRoot } from "@/utils";

const {
  objectDetectCreate,
  objectDetectDestroy,
  objectDetectForVideo,
  objectDetectForVideoCancel,
  offVideoObjectDetectProgress,
  onVideoObjectDetectProgress,
} = ai;

// 当前 AI 视频的编辑状态
const [handleState, setHandleState] = useState("idle");
// 当前 AI 视频流处理进度
const [progressState, setProgressState] = useState(0);

// 注册 App AI 实例
useEffect(() => {
  objectDetectCreate();

  return () => {
    // 页面销毁时同时销毁 App AI 实例
    objectDetectDestroy();
    audioManager.destroy({
      success: (res) => {
        console.log("==destroy2==success", res);
      },
      fail: (error) => {
        console.log("==destroy2==fail", error);
      },
    });
  };
}, []);

// AI 视频流处理功能
const handleVideoByAI = (
  detectType: number,
  imageEditType: number,
  musicPath = ""
) => {
  // 生成 AI 主体突出视频时建议关闭音乐播放,避免系统出现未知错误
  paseVideo();

  // 生成 AI 主体突出视频导出路径
  const tempVideoPath = createTempVideoRoot();

  // 开启 AI 主体突出视频生成进度监听
  onVideoObjectDetectProgress(handleListenerProgress);
  objectDetectForVideo({
    inputVideoPath: videoSrc,
    outputVideoPath: tempVideoPath,
    detectType,
    musicPath,
    originAudioVolume: volumeObj.video / 100,
    overlayAudioVolume: volumeObj.music / 100,
    imageEditType,
    audioEditType: 2,
    success: ({ path }) => {
      // 注销 AI 主体突出视频生成进度监听
      offVideoObjectDetectProgress(handleListenerProgress);
      setProgressState(0);
      fetchVideoThumbnails({
        filePath: path,
        startTime: 0,
        endTime: 1,
        thumbnailCount: 1,
        thumbnailWidth: 375,
        thumbnailHeight: 212,
        success: (res) => {
          setHandleState("success");
          setVideoSrc(path);
          showToast({
            title: Strings.getLang("dsc_ai_generates_success"),
            icon: "success",
          });
        },
        fail: ({ errorMsg }) => {
          console.log("==fetchVideoThumbnails==fail==", errorMsg);
        },
      });
    },
    fail: ({ errorMsg }) => {
      console.log("==objectDetectForVideo==fail==", errorMsg);
      offVideoObjectDetectProgress(handleListenerProgress);
      setProgressState(0);
      setHandleState("fail");
      setHandleState("selectSkill");
      setIsShowAISkills(true);
      showToast({
        title: Strings.getLang("dsc_ai_generates_fail"),
        icon: "error",
      });
    },
  });
};

// 打断 AI 主体突出视频流生成
const handleCancelAIProcess = () => {
  objectDetectForVideoCancel({
    success: () => {
      setHandleState("select");
    },
  });
};

具体 AI 技术方案介绍,详见:视频解决方案—视频主体突出方案

具体 API 相关介绍,详见:开发者文档—AI 基础包