@cornerstonejs/core
Version:
Cornerstone3D Core
142 lines (141 loc) • 5.45 kB
JavaScript
import ViewportType from '../../../enums/ViewportType.js';
import { normalizeVideoPlaybackInfo } from '../../../utilities/VideoUtilities.js';
import { resolveVideoCanvasMapping } from './videoViewportCamera.js';
export class HtmlVideoRenderPath {
async addData(ctx, data, options) {
const videoData = data;
const payload = videoData;
const element = document.createElement('video');
element.src = payload.renderedUrl;
element.preload = 'auto';
element.crossOrigin = 'anonymous';
element.playsInline = true;
element.style.position = 'absolute';
element.style.left = '0';
element.style.top = '0';
element.style.transformOrigin = 'top left';
element.style.objectFit = 'fill';
await new Promise((resolve) => {
const onLoadedMetadata = () => {
element.removeEventListener('loadedmetadata', onLoadedMetadata);
resolve();
};
element.addEventListener('loadedmetadata', onLoadedMetadata);
ctx.element.appendChild(element);
});
const playbackInfo = normalizeVideoPlaybackInfo({
durationSeconds: element.duration,
cineRate: payload.fps,
numberOfFrames: payload.numberOfFrames,
});
const normalizedPayload = {
...payload,
durationSeconds: element.duration,
fps: playbackInfo.fps,
numberOfFrames: playbackInfo.numberOfFrames,
frameRange: playbackInfo.frameRange,
};
Object.assign(videoData, normalizedPayload);
const rendering = {
renderMode: 'video2d',
element,
};
return {
rendering,
updateDataPresentation: (props) => {
this.updateDataPresentation(rendering, props);
},
applyViewState: (camera) => {
this.applyViewState(rendering, camera, videoData);
},
getFrameOfReferenceUID: () => {
return this.getFrameOfReferenceUID(rendering);
},
removeData: () => {
this.removeData(rendering);
},
};
}
updateDataPresentation(rendering, props) {
const videoProps = props;
const { element } = rendering;
element.style.display = videoProps?.visible === false ? 'none' : '';
element.style.opacity = String(videoProps?.opacity ?? 1);
element.loop = videoProps?.loop ?? true;
element.muted = videoProps?.muted ?? true;
element.playbackRate = videoProps?.playbackRate ?? 1;
element.style.objectFit = videoProps?.objectFit ?? 'contain';
}
applyViewState(rendering, camera, data) {
const videoCamera = camera;
const { element } = rendering;
const rotation = videoCamera.rotation ?? 0;
const mapping = this.getCanvasMapping(element, videoCamera);
rendering.currentCamera = videoCamera;
if (mapping) {
element.style.width = `${mapping.width}px`;
element.style.height = `${mapping.height}px`;
element.style.left = `${mapping.left}px`;
element.style.top = `${mapping.top}px`;
}
element.style.transform = `rotate(${rotation}deg)`;
if (typeof videoCamera.currentTimeSeconds === 'number' &&
Math.abs(element.currentTime - videoCamera.currentTimeSeconds) >
0.5 / Math.max(1, data.fps)) {
element.currentTime = videoCamera.currentTimeSeconds;
}
}
canvasToWorld(rendering, canvasPos) {
const mapping = this.getCanvasMapping(rendering.element, rendering.currentCamera);
if (!mapping) {
return [0, 0, 0];
}
return [
(canvasPos[0] - mapping.left) / mapping.worldToCanvasRatio,
(canvasPos[1] - mapping.top) / mapping.worldToCanvasRatio,
0,
];
}
worldToCanvas(rendering, worldPos) {
const mapping = this.getCanvasMapping(rendering.element, rendering.currentCamera);
if (!mapping) {
return [0, 0];
}
return [
mapping.left + worldPos[0] * mapping.worldToCanvasRatio,
mapping.top + worldPos[1] * mapping.worldToCanvasRatio,
];
}
getCanvasMapping(element, camera) {
const container = element.parentElement;
return resolveVideoCanvasMapping({
containerWidth: container?.clientWidth ?? 0,
containerHeight: container?.clientHeight ?? 0,
intrinsicWidth: element.videoWidth || container?.clientWidth || 0,
intrinsicHeight: element.videoHeight || container?.clientHeight || 0,
objectFit: element.style.objectFit,
camera,
});
}
getFrameOfReferenceUID(rendering) {
const { element } = rendering;
return element.currentSrc || element.src;
}
removeData(rendering) {
const { element } = rendering;
element.pause();
element.remove();
}
}
export class HtmlVideoPath {
constructor() {
this.id = 'video:html-element';
this.type = ViewportType.VIDEO_NEXT;
}
matches(data, options) {
return data.type === 'video' && options.renderMode === 'video2d';
}
createRenderPath() {
return new HtmlVideoRenderPath();
}
}