UNPKG

@xata.io/screenshot

Version:

A zero-dependency browser-native way to take screenshots powered by the native web MediaDevices API.

137 lines (121 loc) 6.72 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.XataScreenshot = {})); })(this, (function (exports) { 'use strict'; /** * Checks if the current browser supports the MediaDevices API. */ const checkIfBrowserSupported = () => { var _a; return Boolean((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getDisplayMedia); }; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } const createVideoElementToCaptureFrames = (mediaStream) => { const video = document.createElement("video"); video.autoplay = true; video.muted = true; video.playsInline = true; video.srcObject = mediaStream; video.setAttribute("style", "position:fixed;top:0;left:0;pointer-events:none;visibility:hidden;"); return video; }; const paintVideoFrameOnCanvas = (video) => { var _a, _b, _c; // Get the video settings // @ts-ignore because getTracks is very much valid in modern browsers const videoTrackSettings = (_a = video.srcObject) === null || _a === void 0 ? void 0 : _a.getTracks()[0].getSettings(); // Create a canvas with the video's size and draw the video frame on it const canvas = document.createElement("canvas"); canvas.width = (_b = videoTrackSettings === null || videoTrackSettings === void 0 ? void 0 : videoTrackSettings.width) !== null && _b !== void 0 ? _b : 0; canvas.height = (_c = videoTrackSettings === null || videoTrackSettings === void 0 ? void 0 : videoTrackSettings.height) !== null && _c !== void 0 ? _c : 0; const ctx = canvas.getContext("2d"); ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0); return canvas; }; const playCameraClickSound = (url) => { const audio = document.createElement("audio"); audio.loop = false; audio.src = url; audio.play(); audio.remove(); }; const sleep = (timeoutInMs = 300) => new Promise((r) => { setTimeout(r, timeoutInMs); }); const stopCapture = (video) => { var _a; // @ts-ignore because getTracks is very much valid in modern browsers const tracks = (_a = video.srcObject) === null || _a === void 0 ? void 0 : _a.getTracks(); tracks === null || tracks === void 0 ? void 0 : tracks.forEach((track) => track.stop()); // This is the only way to clean up a video stream in the browser so... // eslint-disable-next-line no-param-reassign video.srcObject = null; video.remove(); }; const waitForFocus = (result) => __awaiter(void 0, void 0, void 0, function* () { yield sleep(); if (document.hasFocus()) { return result; } return waitForFocus(result); }); /** * Takes a screenshot of the current page using a the native browser [`MediaDevices`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API. */ const takeScreenshot = ({ onCaptureEnd, onCaptureStart, quality = 0.7, type = "image/jpeg", soundEffectUrl, } = {}) => __awaiter(void 0, void 0, void 0, function* () { yield (onCaptureStart === null || onCaptureStart === void 0 ? void 0 : onCaptureStart()); return navigator.mediaDevices .getDisplayMedia({ // This is actually supported, but only in Chrome so not yet part of the TS typedefs, so // @ts-ignore preferCurrentTab: true, video: { frameRate: 30 }, }) .then(waitForFocus) // We can only proceed if our tab is in focus. .then((result) => __awaiter(void 0, void 0, void 0, function* () { // So we mount the screen capture to a video element... const video = createVideoElementToCaptureFrames(result); // ...which needs to be in the DOM but invisible so we can capture it. document.body.appendChild(video); // Now, we need to wait a bit to capture the right moment... // Hide this modal... // Play camera click sound, because why not if (soundEffectUrl) { playCameraClickSound(soundEffectUrl); } // Wait for the video feed... yield sleep(); // Paint the video frame on a canvas... const canvas = paintVideoFrameOnCanvas(video); // Set the data URL in state const screenshot = canvas.toDataURL(type, quality); // Stop sharing screen. stopCapture(video); // Clean up canvas.remove(); yield (onCaptureEnd === null || onCaptureEnd === void 0 ? void 0 : onCaptureEnd()); return screenshot; })); }); exports.checkIfBrowserSupported = checkIfBrowserSupported; exports.takeScreenshot = takeScreenshot; Object.defineProperty(exports, '__esModule', { value: true }); }));