@antv/s2
Version:
effective spreadsheet render core lib
142 lines • 6.19 kB
JavaScript
import { __awaiter } from "tslib";
import { Image as GImage, Rect } from '@antv/g';
import { merge } from 'lodash';
import { VIDEO_RECT_NAME } from '../common/constant/renderer';
import { GuiIcon } from '../common/icons';
import { CellClipBox } from '../common/interface';
import { asyncDrawImage, calculateImageSize, } from '../utils/cell/customRenderer';
import { BaseRenderer } from './BaseRenderer';
// 部分浏览器 autoplay=false 时不解码首帧,seek 到此时间点强制解码以展示预览画面
const VIDEO_PREVIEW_FRAME_TIME = 0.001;
const defaultVideoConfig = {
loop: true,
autoplay: false,
preload: 'auto',
crossOrigin: true,
controls: false,
muted: true,
};
export class VideoRenderer extends BaseRenderer {
constructor() {
super(...arguments);
this.fallback = '';
}
prepare(renderer, cell) {
return __awaiter(this, void 0, void 0, function* () {
const text = yield this.prepareText(renderer, cell);
return new Promise((resolve) => {
const { height, width } = this.getCellInfo(cell);
const { timeout = 10000, fallback = '' } = renderer;
this.fallback = fallback;
const cacheKey = BaseRenderer.getCacheKey('video', text);
if (BaseRenderer.mediaCache.has(cacheKey)) {
const cached = BaseRenderer.mediaCache.get(cacheKey);
if (cached instanceof HTMLVideoElement ||
cached instanceof HTMLImageElement) {
resolve(cached);
return;
}
if (cached === null) {
resolve(fallback);
return;
}
}
const video = document.createElement('video');
const handleFallback = () => __awaiter(this, void 0, void 0, function* () {
if (fallback) {
const img = yield asyncDrawImage({
src: fallback,
fallback: '',
timeout: 5000,
mediaCache: BaseRenderer.mediaCache,
}).catch(() => null);
if (img) {
BaseRenderer.mediaCache.set(cacheKey, img);
resolve(img);
return;
}
}
BaseRenderer.mediaCache.set(cacheKey, null);
resolve(fallback);
});
const fallbackTimer = setTimeout(handleFallback, timeout);
const config = Object.assign(Object.assign({ height,
width, src: text }, defaultVideoConfig), renderer.videoConfig);
Object.assign(video, config);
video.onloadeddata = () => {
clearTimeout(fallbackTimer);
// 只在未显式配置 autoplay: true 时才 pause/seek,避免覆盖用户配置
if (!config.autoplay) {
video.pause();
video.currentTime = VIDEO_PREVIEW_FRAME_TIME;
}
BaseRenderer.mediaCache.set(cacheKey, video);
resolve(video);
};
const onError = () => {
clearTimeout(fallbackTimer);
handleFallback();
};
// 错误处理
['error', 'abort', 'stalled'].forEach((eventName) => {
video.addEventListener(eventName, onError);
});
});
});
}
generateConfig(renderer, cell, element) {
const { y, height } = cell.getBBoxByType(CellClipBox.CONTENT_BOX);
const availableWidth = Math.max(cell.getMaxTextWidth(), 0);
let videoWidth = availableWidth;
let videoHeight = height;
let fill = 'transparent';
const getMediaSize = (el) => el instanceof HTMLVideoElement
? { width: el.videoWidth, height: el.videoHeight }
: { width: el.naturalWidth, height: el.naturalHeight };
if (element instanceof HTMLVideoElement ||
element instanceof HTMLImageElement) {
const { width: srcWidth, height: srcHeight } = getMediaSize(element);
const { width: finalWidth, height: finalHeight } = calculateImageSize(availableWidth, height, srcWidth, srcHeight);
videoWidth = finalWidth;
videoHeight = finalHeight;
fill = {
image: element,
repetition: 'no-repeat',
transform: `scale(${finalWidth / srcWidth}, ${finalHeight / srcHeight})`,
};
}
else {
// 加载失败:展示空白
fill = 'transparent';
}
const { x: videoX } = cell.getContentPosition({
contentWidth: videoWidth,
});
const videoY = y + (height - videoHeight) / 2;
// https://g.antv.antgroup.com/api/css/pattern
return {
style: Object.assign({ x: videoX, y: videoY, width: videoWidth, height: videoHeight, fill }, renderer.config),
isFallback: element instanceof HTMLImageElement,
};
}
render(cell, config) {
if (config.isFallback) {
cell.appendChild(new GImage(merge({}, config, { style: { src: this.fallback } })));
return;
}
const rect = new Rect(Object.assign(Object.assign({}, config), { name: VIDEO_RECT_NAME }));
const { x, y, width, height } = config.style;
const calcSize = Math.min(width, height) * 0.25;
rect.appendChild(new GuiIcon({
name: 'Play',
width: calcSize,
height: calcSize,
x: x + width / 2 - calcSize / 2,
y: y + height / 2 - calcSize / 2,
pointerEvents: 'none',
cursor: 'pointer',
}));
cell.appendChild(rect);
}
}
//# sourceMappingURL=VideoRenderer.js.map