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 URL转换为JPG图片Blob * @param {string} videoBlobUrl - 本地视频Blob URL(blob:xxx) * @returns {Promise} - 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 ``; } } //style="width:600px;height:300px;" Video.blotName = "customVideo"; // Video.className = "ql-video"; Video.tagName = "video"; export default Video;