feat: 🚀 tabs
This commit is contained in:
@@ -1,32 +1,99 @@
|
||||
import { Quill } from "@vueup/vue-quill";
|
||||
// 源码中是import直接倒入,这里要用Quill.import引入
|
||||
const BlockEmbed = Quill.import("blots/block/embed");
|
||||
const Link = Quill.import("formats/link");
|
||||
|
||||
const ATTRIBUTES = ["height", "width"];
|
||||
const ATTRIBUTES = ["height", "width", "poster"];
|
||||
|
||||
class Video extends BlockEmbed {
|
||||
static create(value) {
|
||||
let node = super.create();
|
||||
// 添加video标签所需的属性
|
||||
|
||||
// 基础视频属性
|
||||
node.setAttribute("controls", "controls");
|
||||
node.setAttribute("playsinline", "true");
|
||||
node.setAttribute("webkit-playsinline", "true");
|
||||
node.setAttribute("type", "video/mp4");
|
||||
// poster 属性指定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。
|
||||
// console.log(value.url, "= value.poster=");
|
||||
// node.setAttribute("poster", this.sanitize(value.url));
|
||||
node.setAttribute("src", this.sanitize(value.url));
|
||||
|
||||
// 处理视频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",
|
||||
`
|
||||
width: 600px;
|
||||
height: 300px;
|
||||
`
|
||||
);
|
||||
|
||||
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)) {
|
||||
@@ -41,15 +108,14 @@ class Video extends BlockEmbed {
|
||||
}
|
||||
|
||||
static value(domNode) {
|
||||
// 设置自定义的属性值
|
||||
return {
|
||||
url: domNode.getAttribute("src")
|
||||
// poster: domNode.getAttribute("src")
|
||||
url: domNode.getAttribute("src").split("#")[0],
|
||||
poster: domNode.getAttribute("poster") || ""
|
||||
};
|
||||
}
|
||||
|
||||
format(name, value) {
|
||||
if (ATTRIBUTES.indexOf(name) > -1) {
|
||||
if (ATTRIBUTES.includes(name)) {
|
||||
if (value) {
|
||||
this.domNode.setAttribute(name, value);
|
||||
} else {
|
||||
@@ -61,12 +127,13 @@ class Video extends BlockEmbed {
|
||||
}
|
||||
|
||||
html() {
|
||||
const { video } = this.value();
|
||||
return `<a href="${video}">${video}</a>`;
|
||||
const { url, poster } = this.value();
|
||||
return `<video src="${url}" ${poster ? `poster="${poster}"` : ""} controls playsinline webkit-playsinline ></video>`;
|
||||
}
|
||||
}
|
||||
Video.blotName = "customVideo"; // 这里不用改,不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot
|
||||
Video.className = "ql-video"; // 可添加样式,看实际使用需要
|
||||
Video.tagName = "video"; // 用video标签替换iframe
|
||||
//style="width:600px;height:300px;"
|
||||
Video.blotName = "customVideo";
|
||||
// Video.className = "ql-video";
|
||||
Video.tagName = "video";
|
||||
|
||||
export default Video;
|
||||
|
||||
Reference in New Issue
Block a user