素材添加
用户端可以通过 API 添加图片(jpeg, jpg, png, gif)、视频(mp4)、音频素材(mp3)。视频非 mp4 格式和音频非 mp3 格式均会自动转换为 mp4 和 mp3 格式。
视频素材添加
参考数据格式如下:
ts
const videoParams = {
app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
name: "test.mp4", // 视频名称
urls: {
url: "/uploads/test.mp4", // 视频链接
thumb: "/uploads/test_thumb.png", // 封面图
},
attrs: {
size: 1024, // 非必填,文件大小,单位kb
type: "video/mp4", // 视频类型
duration: 10, // 视频时长,单位秒
videoWidth: 1280, // 视频宽度
videoHeight: 720, // 视频高度
noAudioTracks: false, // 非必填,判断是否存在音轨
frames: "/uploads/frames.png", // 非必填,记录当前的帧图数据,如果不传,服务端会自行进行补充处理
frameScale: 2, // 非必填,帧图的缩放比例,默认参数是2,
wave: "/uploads/wave.json", // 非必填,当前视频中的音波数据
rotation: false, // 非必填,记录当前视频是否是存在旋转90度拍摄
},
};
音频素材添加
参考数据格式如下:
ts
const videoParams = {
app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
name: "test.mp3", // 音频名称
urls: {
url: "/uploads/test.mp3", // 音频链接
},
attrs: {
size: 1024, // 非必填,文件大小,单位kb
type: "audio/mp3", // 音频类型
duration: 10, // 音频时长,单位秒
wave: "/uploads/wave.json", // 非必填,当前音频中的音波数据
},
};
图片素材添加
参考数据格式如下:
ts
const videoParams = {
app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
name: "test.png", // 图片名称
urls: {
url: "/uploads/test.png", // 图片链接
thumb: "/uploads/test_thumb.png", // 封面图
},
attrs: {
size: 1024, // 非必填,文件大小,单位kb
type: "image/png", // 图片类型
naturalWidth: 1000, // 图片宽度
naturalHeight: 800, // 图片高度
},
};
需要注意的是 gif 图片和普通图片不一样,gif 图片存在帧图,参考参数如下:
ts
const videoParams = {
app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
name: "test.gif", // 图片名称
urls: {
url: "/uploads/test.gif", // 图片链接
thumb: "/uploads/test_thumb.png", // 封面图
},
attrs: {
size: 1024, // 非必填,文件大小,单位kb
type: "image/gif", // 图片类型
naturalWidth: 1000, // 图片宽度
naturalHeight: 800, // 图片高度
frames: "/uploads/frames.png", // 非必填,记录当前的帧图数据,如果不传,服务端会自行进行补充处理
},
};
参数获取说明
我们封装了一个 JS 包,可以帮助用户获取到这些常规文件的参数,需要在项目中引入相关的 JS 包(需要配合/assets/worker
和/assets/MediaInfoModule.wasm
使用):
html
<script src="/assets/medianinfo/index.min.js"></script>
<script>
// 获取上传文件的参数
const info = await window.uploadInfo.getUploadBeforeData(
v.file.url, // 文件URL,字符串
'video', // 非必填、 文件类型:'audio' | 'image' | 'video' | 'image/svg' | 'image/gif'
uploadBase64, // 非必填、base64文件上传的接口,用于上传封面图、帧图、音波数据等文件,如果不填写该参数,将返回base64文件,自行进行上传
);
</script>
JS 示例代码
ts
async function main() {
// 获取文件后缀名称
const ext = util.getFileExtension(value);
// 创建一个素材数据
const obj: any = {
app_id: editor.appid,
name: util.getFileNameFromUrl(value),
urls: {
url: value,
thumb: "",
},
attrs: {
type: "video/mp4",
},
};
switch (ext.toLocaleLowerCase()) {
case "mp4":
{
// 资源获取
try {
const m = (await util.mediaLazy(value)) as any;
obj.attrs.type = "video/mp4";
obj.attrs.duration = m.duration;
obj.attrs.videoWidth = m.videoWidth;
obj.attrs.videoHeight = m.videoHeight;
const base64 = await util.drawVideoFrame(m, 130, 1);
const [res] = await editor.apiServer.uploadBase64({
content: base64,
name: util.randomID() + ".png",
});
obj.urls.thumb = res.storage_path;
} catch (err) {
Toast.error(err);
}
}
break;
case "jpeg":
case "png":
case "jpg":
case "gif":
{
try {
const m = await util.imgLazy(value);
obj.attrs.type = "image/" + ext.toLocaleLowerCase();
obj.attrs.naturalWidth = m.naturalWidth;
obj.attrs.naturalHeight = m.naturalHeight;
} catch (err) {
Toast.error(err);
}
}
break;
case "mp3": {
try {
const m = (await util.mediaLazy(value)) as any;
obj.attrs.type = "audio/mp3";
obj.attrs.duration = m.duration;
} catch (err) {
Toast.error(err);
}
}
}
// 新增素材
const [res2, err2] = await editor.apiServer.createUserMaterial(obj);
if (!err2) {
Toast.success("添加成功");
// 更新编辑器的素材列表,非编辑器页面可不写
pubsub.publish("addItemToCloudList", res2);
}
}
/**
* 获取文件后缀
*/
export function getFileExtension(url: string) {
url = url.split("?")[0];
const pathArray = url.split(".");
if (pathArray.length > 1) {
return pathArray.pop().toLowerCase();
} else {
return null; // 没有后缀名
}
}
/**
* 获取文件名称
*/
export function getFileNameFromUrl(url) {
try {
// 解析URL
const parsedUrl = new URL(url);
let path = parsedUrl.pathname;
// 移除路径末尾的斜杠
if (path.endsWith("/")) {
path = path.slice(0, -1);
}
// 获取路径最后一段(可能包含文件名)
let fileName = path.split("/").pop() || "";
// 如果没有获取到文件名,检查查询参数
if (!fileName) {
// 尝试从查询参数中寻找类似文件名的参数
const params = parsedUrl.searchParams;
const fileParams = ["file", "filename", "name", "download"];
for (const param of fileParams) {
if (params.has(param)) {
fileName = params.get(param);
break;
}
}
}
// 处理可能包含的URL编码(如中文)
fileName = decodeURIComponent(fileName);
// 移除可能的查询参数或锚点残留
const queryIndex = fileName.indexOf("?");
if (queryIndex !== -1) {
fileName = fileName.substring(0, queryIndex);
}
const hashIndex = fileName.indexOf("#");
if (hashIndex !== -1) {
fileName = fileName.substring(0, hashIndex);
}
return fileName || null; // 如果最终没有找到文件名,返回null
} catch (e) {
console.error("解析URL失败:", e);
return null;
}
}
/**
* 异步加载媒体资源
* @param src
* @param type
* @returns
*/
export function mediaLazy(
src: string,
time?: number,
type?: "video" | "audio"
): Promise<HTMLVideoElement | HTMLAudioElement> {
return new Promise((resolve, reject) => {
const media = document.createElement(type || "video");
media.preload = "auto";
media.crossOrigin = "Anonymous";
media.autoplay = false;
media.src = src;
media.setAttribute("playsinline", "");
media.setAttribute("webkit-playsinline", "");
media.setAttribute("x5-video-player-type", "h5"); // 针对微信的额外属性
media.setAttribute("x5-video-player-fullscreen", "false");
try {
media.load();
} catch (e) {
console.warn("iOS微信中load()可能受限:", e);
}
if (time !== undefined) {
media.currentTime = time;
}
// 如果在微信IOS浏览器中,直接延迟3秒返回对象,因为无法自动触发调用 loadedmetadata 和 canplay
if (isIOSWechatBrowser()) {
setTimeout(() => {
resolve(media);
}, 3000);
} else {
// 监听多个事件以确保兼容性
const handleLoad = () => {
console.log("媒体加载成功");
cleanup();
resolve(media);
};
const handleError = (err: Event | string) => {
console.error("媒体加载失败", src, err);
cleanup();
reject(err);
};
const cleanup = () => {
media.removeEventListener("loadedmetadata", handleLoad);
media.removeEventListener("canplay", handleLoad);
media.removeEventListener("error", handleError);
};
media.addEventListener("loadedmetadata", handleLoad);
media.addEventListener("canplay", handleLoad);
media.addEventListener("error", handleError);
}
});
}
/**
* 获取视频1s的封面图,返回base64
* @param video
* @param limitWidth
*/
export async function drawVideoFrame(
video: HTMLVideoElement,
limitWidth: number,
time?: number
) {
video = video.cloneNode() as any;
video.muted = true;
video.currentTime = time || 1;
await video.play();
video.pause();
const canvas = document.createElement("canvas"),
width = video.videoWidth, //canvas的尺寸和图片一样
height = video.videoHeight;
const scale = limitWidth / width;
canvas.width = limitWidth;
canvas.height = Math.floor(height * scale);
canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height); //绘制canvas
const thumb = canvas.toDataURL("image/jpeg");
return thumb;
}
export const imgLazy = (src: string): Promise<HTMLImageElement> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "anonymous";
img.src = src;
img.onload = function () {
resolve(img);
};
img.onerror = function () {
console.error("图片加载失败", src, img);
reject(img);
};
});
};