140 lines
4.7 KiB
JavaScript
140 lines
4.7 KiB
JavaScript
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 URL(blob: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;
|