@tldraw/utils
Version:
tldraw infinite canvas SDK (private utilities).
8 lines (7 loc) • 12.4 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../src/lib/media/media.ts"],
"sourcesContent": ["import { promiseWithResolve } from '../control'\nimport { Image } from '../network'\nimport { isApngAnimated } from './apng'\nimport { isAvifAnimated } from './avif'\nimport { isGifAnimated } from './gif'\nimport { PngHelpers } from './png'\nimport { isWebpAnimated } from './webp'\n\n/** @public */\nexport const DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES = Object.freeze(['image/svg+xml' as const])\n/** @public */\nexport const DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES = Object.freeze([\n\t'image/jpeg' as const,\n\t'image/png' as const,\n\t'image/webp' as const,\n])\n/** @public */\nexport const DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES = Object.freeze([\n\t'image/gif' as const,\n\t'image/apng' as const,\n\t'image/avif' as const,\n])\n/** @public */\nexport const DEFAULT_SUPPORTED_IMAGE_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES,\n])\n/** @public */\nexport const DEFAULT_SUPPORT_VIDEO_TYPES = Object.freeze([\n\t'video/mp4' as const,\n\t'video/webm' as const,\n\t'video/quicktime' as const,\n])\n/** @public */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_IMAGE_TYPES,\n\t...DEFAULT_SUPPORT_VIDEO_TYPES,\n])\n/** @public */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPE_LIST = DEFAULT_SUPPORTED_MEDIA_TYPES.join(',')\n\n/**\n * Helpers for media\n *\n * @public\n */\nexport class MediaHelpers {\n\t/**\n\t * Load a video from a url.\n\t * @public\n\t */\n\tstatic loadVideo(src: string): Promise<HTMLVideoElement> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst video = document.createElement('video')\n\t\t\tvideo.onloadeddata = () => resolve(video)\n\t\t\tvideo.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load video'))\n\t\t\t}\n\t\t\tvideo.crossOrigin = 'anonymous'\n\t\t\tvideo.src = src\n\t\t})\n\t}\n\n\tstatic async getVideoFrameAsDataUrl(video: HTMLVideoElement, time = 0): Promise<string> {\n\t\tconst promise = promiseWithResolve<string>()\n\t\tlet didSetTime = false\n\n\t\tconst onReadyStateChanged = () => {\n\t\t\tif (!didSetTime) {\n\t\t\t\tif (video.readyState >= video.HAVE_METADATA) {\n\t\t\t\t\tdidSetTime = true\n\t\t\t\t\tvideo.currentTime = time\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (video.readyState >= video.HAVE_CURRENT_DATA) {\n\t\t\t\tconst canvas = document.createElement('canvas')\n\t\t\t\tcanvas.width = video.videoWidth\n\t\t\t\tcanvas.height = video.videoHeight\n\t\t\t\tconst ctx = canvas.getContext('2d')\n\t\t\t\tif (!ctx) {\n\t\t\t\t\tthrow new Error('Could not get 2d context')\n\t\t\t\t}\n\t\t\t\tctx.drawImage(video, 0, 0)\n\t\t\t\tpromise.resolve(canvas.toDataURL())\n\t\t\t}\n\t\t}\n\t\tconst onError = (e: Event) => {\n\t\t\tconsole.error(e)\n\t\t\tpromise.reject(new Error('Could not get video frame'))\n\t\t}\n\n\t\tvideo.addEventListener('loadedmetadata', onReadyStateChanged)\n\t\tvideo.addEventListener('loadeddata', onReadyStateChanged)\n\t\tvideo.addEventListener('canplay', onReadyStateChanged)\n\t\tvideo.addEventListener('seeked', onReadyStateChanged)\n\n\t\tvideo.addEventListener('error', onError)\n\t\tvideo.addEventListener('stalled', onError)\n\n\t\tonReadyStateChanged()\n\n\t\ttry {\n\t\t\treturn await promise\n\t\t} finally {\n\t\t\tvideo.removeEventListener('loadedmetadata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('loadeddata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('canplay', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('seeked', onReadyStateChanged)\n\n\t\t\tvideo.removeEventListener('error', onError)\n\t\t\tvideo.removeEventListener('stalled', onError)\n\t\t}\n\t}\n\n\t/**\n\t * Load an image from a url.\n\t * @public\n\t */\n\tstatic getImageAndDimensions(\n\t\tsrc: string\n\t): Promise<{ w: number; h: number; image: HTMLImageElement }> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst img = Image()\n\t\t\timg.onload = () => {\n\t\t\t\tlet dimensions\n\t\t\t\tif (img.naturalWidth) {\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.naturalWidth,\n\t\t\t\t\t\th: img.naturalHeight,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Sigh, Firefox doesn't have naturalWidth or naturalHeight for SVGs. :-/\n\t\t\t\t\t// We have to attach to dom and use clientWidth/clientHeight.\n\t\t\t\t\tdocument.body.appendChild(img)\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.clientWidth,\n\t\t\t\t\t\th: img.clientHeight,\n\t\t\t\t\t}\n\t\t\t\t\tdocument.body.removeChild(img)\n\t\t\t\t}\n\t\t\t\tresolve({ ...dimensions, image: img })\n\t\t\t}\n\t\t\timg.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load image'))\n\t\t\t}\n\t\t\timg.crossOrigin = 'anonymous'\n\t\t\timg.referrerPolicy = 'strict-origin-when-cross-origin'\n\t\t\timg.style.visibility = 'hidden'\n\t\t\timg.style.position = 'absolute'\n\t\t\timg.style.opacity = '0'\n\t\t\timg.style.zIndex = '-9999'\n\t\t\timg.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of a video blob\n\t *\n\t * @param blob - A SharedBlob containing the video\n\t * @public\n\t */\n\tstatic async getVideoSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\treturn MediaHelpers.usingObjectURL(blob, async (url) => {\n\t\t\tconst video = await MediaHelpers.loadVideo(url)\n\t\t\treturn { w: video.videoWidth, h: video.videoHeight }\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of an image blob\n\t *\n\t * @param blob - A Blob containing the image.\n\t * @public\n\t */\n\tstatic async getImageSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\tconst { w, h } = await MediaHelpers.usingObjectURL(blob, MediaHelpers.getImageAndDimensions)\n\n\t\ttry {\n\t\t\tif (blob.type === 'image/png') {\n\t\t\t\tconst view = new DataView(await blob.arrayBuffer())\n\t\t\t\tif (PngHelpers.isPng(view, 0)) {\n\t\t\t\t\tconst physChunk = PngHelpers.findChunk(view, 'pHYs')\n\t\t\t\t\tif (physChunk) {\n\t\t\t\t\t\tconst physData = PngHelpers.parsePhys(view, physChunk.dataOffset)\n\t\t\t\t\t\tif (physData.unit === 0 && physData.ppux === physData.ppuy) {\n\t\t\t\t\t\t\t// Calculate pixels per meter:\n\t\t\t\t\t\t\t// - 1 inch = 0.0254 meters\n\t\t\t\t\t\t\t// - 72 DPI is 72 dots per inch\n\t\t\t\t\t\t\t// - pixels per meter = 72 / 0.0254\n\t\t\t\t\t\t\tconst pixelsPerMeter = 72 / 0.0254\n\t\t\t\t\t\t\tconst pixelRatio = Math.max(physData.ppux / pixelsPerMeter, 1)\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tw: Math.round(w / pixelRatio),\n\t\t\t\t\t\t\t\th: Math.round(h / pixelRatio),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error(err)\n\t\t\treturn { w, h }\n\t\t}\n\t\treturn { w, h }\n\t}\n\n\tstatic async isAnimated(file: Blob): Promise<boolean> {\n\t\tif (file.type === 'image/gif') {\n\t\t\treturn isGifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/avif') {\n\t\t\treturn isAvifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/webp') {\n\t\t\treturn isWebpAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/apng') {\n\t\t\treturn isApngAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\treturn false\n\t}\n\n\tstatic isAnimatedImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\tstatic isStaticImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\tstatic isVectorImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\tstatic isImageType(mimeType: string): boolean {\n\t\treturn DEFAULT_SUPPORTED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\tstatic async usingObjectURL<T>(blob: Blob, fn: (url: string) => Promise<T>): Promise<T> {\n\t\tconst url = URL.createObjectURL(blob)\n\t\ttry {\n\t\t\treturn await fn(url)\n\t\t} finally {\n\t\t\tURL.revokeObjectURL(url)\n\t\t}\n\t}\n}\n"],
"mappings": "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",
"names": []
}