Files
orico-officialWebsite-ts-admin/src/components/Editor/quill-video.js
2025-07-29 11:41:09 +08:00

140 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Quill } from "@vueup/vue-quill";
const BlockEmbed = Quill.import("blots/block/embed");
const Link = Quill.import("formats/link");
const ATTRIBUTES = ["height", "width", "poster"];
class Video extends BlockEmbed {
static create(value) {
let node = super.create();
// 基础视频属性
node.setAttribute("controls", "controls");
node.setAttribute("playsinline", "true");
node.setAttribute("webkit-playsinline", "true");
node.setAttribute("type", "video/mp4");
// 处理视频URL添加时间片段定位到0.001秒(避开黑屏)
const baseUrl = this.sanitize(value.url);
const videoUrl = baseUrl.includes("#") ? `${baseUrl}&t=0.001` : `${baseUrl}#t=0.001`;
node.setAttribute("src", videoUrl);
// 临时封面(加载中显示)
node.setAttribute(
"poster",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='300' viewBox='0 0 600 300'%3E%3Crect width='100%25' height='100%25' fill='%23f0f0f0'/%3E%3Ccircle cx='300' cy='150' r='40' fill='%23ccc'/%3E%3Cpolygon points='300,130 330,160 270,160' fill='white'/%3E%3C/svg%3E"
);
// 自动截取封面未提供poster时
if (!value.poster) {
this.captureFrameAsBlob(videoUrl, node);
} else {
node.setAttribute("poster", this.sanitize(value.poster));
}
// width: 600px;
// height: 300px;
// object-fit: contain;
// 视频样式
node.setAttribute(
"style",
`
`
);
return node;
}
/**
* 生成视频帧Blob核心方法返回Promise
* @param {string} videoUrl - 视频的本地Blob URL
* @returns {Promise<Blob|null>} - 视频帧Blob或null
*/
/**
* 将视频Blob URL转换为JPG图片Blob
* @param {string} videoBlobUrl - 本地视频Blob URLblob:xxx
* @returns {Promise<Blob|null>} - JPG格式图片Blob失败返回null
*/
static async captureVideoFrame(videoBlobUrl) {
return new Promise(resolve => {
// 1. 创建视频元素加载Blob
const video = document.createElement("video");
video.src = videoBlobUrl;
video.muted = true;
video.playsInline = true;
video.preload = "auto";
// 2. 视频可播放时开始转换
video.oncanplay = () => {
// 3. 创建Canvas绘制视频帧
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth || 640; // 使用视频实际宽度
canvas.height = video.videoHeight || 360; // 使用视频实际高度
const ctx = canvas.getContext("2d");
// 绘制视频当前帧到Canvas
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// 4. 将Canvas转换为JPG Blob
canvas.toBlob(
blob => {
console.log(blob, "================>");
video.remove(); // 清理视频元素
resolve(blob || null); // 返回JPG Blob
},
"image/jpeg", // 强制JPG格式
0.8 // 图片质量0-1
);
};
// 错误处理
video.onerror = () => {
video.remove();
resolve(null);
};
});
}
// 以下方法保持不变
static formats(domNode) {
return ATTRIBUTES.reduce((formats, attribute) => {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute);
}
return formats;
}, {});
}
static sanitize(url) {
return Link.sanitize(url);
}
static value(domNode) {
return {
url: domNode.getAttribute("src").split("#")[0],
poster: domNode.getAttribute("poster") || ""
};
}
format(name, value) {
if (ATTRIBUTES.includes(name)) {
if (value) {
this.domNode.setAttribute(name, value);
} else {
this.domNode.removeAttribute(name);
}
} else {
super.format(name, value);
}
}
html() {
const { url, poster } = this.value();
return `<video src="${url}" ${poster ? `poster="${poster}"` : ""} controls playsinline webkit-playsinline ></video>`;
}
}
//style="width:600px;height:300px;"
Video.blotName = "customVideo";
// Video.className = "ql-video";
Video.tagName = "video";
export default Video;