-
Notifications
You must be signed in to change notification settings - Fork 2.1k
@uppy/screen-capture: add screenshot button #5737
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Diff output filesdiff --git a/packages/@uppy/screen-capture/lib/RecordButton.js b/packages/@uppy/screen-capture/lib/RecordButton.js
index 1ed69fc..655a089 100644
--- a/packages/@uppy/screen-capture/lib/RecordButton.js
+++ b/packages/@uppy/screen-capture/lib/RecordButton.js
@@ -53,14 +53,13 @@ export default function RecordButton(_ref) {
"aria-hidden": "true",
focusable: "false",
className: "uppy-c-icon",
- width: "100",
- height: "100",
- viewBox: "0 0 100 100",
+ width: "24",
+ height: "24",
+ viewBox: "0 0 24 24",
+ fill: "currentColor",
},
- h("circle", {
- cx: "50",
- cy: "50",
- r: "40",
+ h("path", {
+ d: "M4.5 4.5a3 3 0 0 0-3 3v9a3 3 0 0 0 3 3h8.25a3 3 0 0 0 3-3v-9a3 3 0 0 0-3-3H4.5ZM19.94 18.75l-2.69-2.69V7.94l2.69-2.69c.944-.945 2.56-.276 2.56 1.06v11.38c0 1.336-1.616 2.005-2.56 1.06Z",
}),
),
);
diff --git a/packages/@uppy/screen-capture/lib/RecorderScreen.js b/packages/@uppy/screen-capture/lib/RecorderScreen.js
index ea999a6..d975c9d 100644
--- a/packages/@uppy/screen-capture/lib/RecorderScreen.js
+++ b/packages/@uppy/screen-capture/lib/RecorderScreen.js
@@ -8,8 +8,10 @@ function _extends() {
},
_extends.apply(null, arguments);
}
-import { Component, h } from "preact";
+import { Component, Fragment, h } from "preact";
+import DiscardButton from "./DiscardButton.js";
import RecordButton from "./RecordButton.js";
+import ScreenshotButton from "./ScreenshotButton.js";
import StopWatch from "./StopWatch.js";
import StreamStatus from "./StreamStatus.js";
import SubmitButton from "./SubmitButton.js";
@@ -29,6 +31,8 @@ class RecorderScreen extends Component {
recording,
stream: videoStream,
recordedVideo,
+ enableScreenshots,
+ capturedScreenshotUrl,
} = this.props;
const videoProps = {
playsinline: true,
@@ -54,27 +58,45 @@ class RecorderScreen extends Component {
h(
"div",
{
- className: "uppy-ScreenCapture-videoContainer",
+ className: "uppy-ScreenCapture-mediaContainer",
},
h(StreamStatus, this.props),
- h(
- "video",
- _extends({
- ref: videoElement => {
- this.videoElement = videoElement;
+ capturedScreenshotUrl && !recording && !recordedVideo
+ ? h(
+ "div",
+ {
+ className: "uppy-ScreenCapture-imageContainer",
},
- className: "uppy-ScreenCapture-video",
- }, videoProps),
- ),
- h(StopWatch, this.props),
+ h("img", {
+ src: capturedScreenshotUrl,
+ className: "uppy-ScreenCapture-media",
+ alt: "screenshotPreview",
+ }),
+ )
+ : h(
+ "video",
+ _extends({
+ ref: videoElement => {
+ this.videoElement = videoElement;
+ },
+ className: "uppy-ScreenCapture-media",
+ }, videoProps),
+ ),
+ h("div", null, h(StopWatch, this.props)),
),
h(
"div",
{
className: "uppy-ScreenCapture-buttonContainer",
},
- h(RecordButton, this.props),
- h(SubmitButton, this.props),
+ recordedVideo || capturedScreenshotUrl
+ ? h(Fragment, null, h(SubmitButton, this.props), h(DiscardButton, this.props))
+ : h(
+ Fragment,
+ null,
+ enableScreenshots && !recording && h(ScreenshotButton, this.props),
+ h(RecordButton, this.props),
+ ),
),
);
}
diff --git a/packages/@uppy/screen-capture/lib/ScreenCapture.js b/packages/@uppy/screen-capture/lib/ScreenCapture.js
index 1306ed8..6d7ac06 100644
--- a/packages/@uppy/screen-capture/lib/ScreenCapture.js
+++ b/packages/@uppy/screen-capture/lib/ScreenCapture.js
@@ -25,6 +25,7 @@ function isScreenRecordingSupported() {
function getMediaDevices() {
return window.MediaRecorder && navigator.mediaDevices;
}
+const SUPPORTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/webp"];
const defaultOptions = {
displayMediaConstraints: {
video: {
@@ -42,6 +43,8 @@ const defaultOptions = {
audio: true,
},
preferredVideoMimeType: "video/webm",
+ preferredImageMimeType: "image/png",
+ enableScreenshots: true,
};
export default class ScreenCapture extends UIPlugin {
constructor(uppy, opts) {
@@ -72,6 +75,8 @@ export default class ScreenCapture extends UIPlugin {
this.stopRecording = this.stopRecording.bind(this);
this.submit = this.submit.bind(this);
this.streamInterrupted = this.streamInactivated.bind(this);
+ this.captureScreenshot = this.captureScreenshot.bind(this);
+ this.discardRecordedMedia = this.discardRecordedMedia.bind(this);
this.captureActive = false;
this.capturedMediaFile = null;
}
@@ -237,6 +242,23 @@ export default class ScreenCapture extends UIPlugin {
throw error;
});
}
+ discardRecordedMedia() {
+ const {
+ capturedScreenshotUrl,
+ recordedVideo,
+ } = this.getPluginState();
+ if (capturedScreenshotUrl) {
+ URL.revokeObjectURL(capturedScreenshotUrl);
+ }
+ if (recordedVideo) {
+ URL.revokeObjectURL(recordedVideo);
+ }
+ this.capturedMediaFile = null;
+ this.setPluginState({
+ recordedVideo: null,
+ capturedScreenshotUrl: null,
+ });
+ }
submit() {
try {
if (this.capturedMediaFile) {
@@ -276,8 +298,22 @@ export default class ScreenCapture extends UIPlugin {
});
this.outputStream = null;
}
+ const {
+ capturedScreenshotUrl,
+ recordedVideo,
+ } = this.getPluginState();
+ if (capturedScreenshotUrl) {
+ URL.revokeObjectURL(capturedScreenshotUrl);
+ }
+ if (recordedVideo) {
+ URL.revokeObjectURL(recordedVideo);
+ }
this.setPluginState({
+ recording: false,
+ streamActive: false,
+ audioStreamActive: false,
recordedVideo: null,
+ capturedScreenshotUrl: null,
});
this.captureActive = false;
}
@@ -301,6 +337,88 @@ export default class ScreenCapture extends UIPlugin {
};
return Promise.resolve(file);
}
+ async captureScreenshot() {
+ var _this$mediaDevices;
+ if (!((_this$mediaDevices = this.mediaDevices) != null && _this$mediaDevices.getDisplayMedia)) {
+ throw new Error("Screen capture is not supported");
+ }
+ try {
+ let stream = this.videoStream;
+ if (!stream) {
+ const newStream = await this.selectVideoStreamSource();
+ if (!newStream) {
+ throw new Error("Failed to get screen capture stream");
+ }
+ stream = newStream;
+ }
+ const video = document.createElement("video");
+ video.srcObject = stream;
+ await new Promise(resolve => {
+ video. => {
+ video.play();
+ resolve(null);
+ };
+ });
+ const canvas = document.createElement("canvas");
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+ const ctx = canvas.getContext("2d");
+ if (!ctx) {
+ throw new Error("Failed to get canvas context");
+ }
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+ let mimeType = this.opts.preferredImageMimeType;
+ if (!mimeType || !SUPPORTED_IMAGE_TYPES.includes(mimeType)) {
+ this.uppy.log(`Unsupported image type "${mimeType}", falling back to image/png`, "warning");
+ mimeType = "image/png";
+ }
+ const quality = 1;
+ return new Promise((resolve, reject) => {
+ canvas.toBlob(
+ blob => {
+ if (!blob) {
+ reject(new Error("Failed to create screenshot blob"));
+ return;
+ }
+ const fileExtension = getFileTypeExtension(mimeType) || "png";
+ const file = {
+ source: this.id,
+ name: `Screenshot ${new Date().toISOString()}.${fileExtension}`,
+ type: mimeType,
+ data: blob,
+ };
+ try {
+ this.capturedMediaFile = file;
+ const screenshotUrl = URL.createObjectURL(blob);
+ this.setPluginState({
+ capturedScreenshotUrl: screenshotUrl,
+ });
+ resolve();
+ } catch (err) {
+ if (this.getPluginState().capturedScreenshotUrl) {
+ this.setPluginState({
+ capturedScreenshotUrl: null,
+ });
+ }
+ if (!err.isRestriction) {
+ this.uppy.log(err, "error");
+ }
+ reject(err);
+ } finally {
+ video.srcObject = null;
+ canvas.remove();
+ video.remove();
+ }
+ },
+ mimeType,
+ quality,
+ );
+ });
+ } catch (err) {
+ this.uppy.log(err, "error");
+ throw err;
+ }
+ }
render() {
const recorderState = this.getPluginState();
if (!recorderState.streamActive && !this.captureActive && !this.userDenied) {
@@ -311,10 +429,13 @@ export default class ScreenCapture extends UIPlugin {
_extends({}, recorderState, {
onStartRecording: this.startRecording,
onStopRecording: this.stopRecording,
+ enableScreenshots: this.opts.enableScreenshots,
+ onScreenshot: this.captureScreenshot,
onStop: this.stop,
onSubmit: this.submit,
i18n: this.i18n,
stream: this.videoStream,
+ onDiscard: this.discardRecordedMedia,
}),
);
}
diff --git a/packages/@uppy/screen-capture/lib/SubmitButton.js b/packages/@uppy/screen-capture/lib/SubmitButton.js
index 514969e..8df861b 100644
--- a/packages/@uppy/screen-capture/lib/SubmitButton.js
+++ b/packages/@uppy/screen-capture/lib/SubmitButton.js
@@ -4,9 +4,10 @@ export default function SubmitButton(_ref) {
recording,
recordedVideo,
onSubmit,
+ capturedScreenshotUrl,
i18n,
} = _ref;
- if (recordedVideo && !recording) {
+ if ((recordedVideo || capturedScreenshotUrl) && !recording) {
return h(
"button",
{
diff --git a/packages/@uppy/screen-capture/lib/locale.js b/packages/@uppy/screen-capture/lib/locale.js
index 3d42e1e..10beca5 100644
--- a/packages/@uppy/screen-capture/lib/locale.js
+++ b/packages/@uppy/screen-capture/lib/locale.js
@@ -8,5 +8,7 @@ export default {
streamPassive: "Stream passive",
micDisabled: "Microphone access denied by user",
recording: "Recording",
+ takeScreenshot: "Take Screenshot",
+ discardMediaFile: "Discard Media",
},
};
diff --git a/packages/@uppy/utils/lib/getFileTypeExtension.js b/packages/@uppy/utils/lib/getFileTypeExtension.js
index 6e5b7d4..61faa22 100644
--- a/packages/@uppy/utils/lib/getFileTypeExtension.js
+++ b/packages/@uppy/utils/lib/getFileTypeExtension.js
@@ -8,6 +8,7 @@ const mimeToExtensions = {
"image/heic": "heic",
"image/heif": "heif",
"image/jpeg": "jpg",
+ "image/webp": "webp",
"image/png": "png",
"image/svg+xml": "svg",
"video/mp4": "mp4", |
This reverts commit 764bb97.
@Murderlon , here are few of my questions relating to this PR
Both approaches have their own sets of pros and cons:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good 👌
Some comments:
- I think we should replace both icons in the button with
camera
andvideo-camera
from https://heroicons.com/solid - I'm not sure if the
screenshotQuality
option makes sense to add, can't we always do max quality? If people want compression you can add@uppy/compressor
. Bit of a conflicting feature with that plugin. - Should we make
enableScreenshots
default totrue
? I'm open to both. - I think fallback makes sense when an incorrect mime type is given to align with how video does it.
I couldn't find any existing unit tests for this plugin ?
Quite a few plugins aren't tested so that's fine. I don't think we need it for this feature.
Do you think it makes sense for adding a preview functionality similar to the screencast video ?
That would be nice, yes!
…to add-screenshot
Hi @Murderlon , Thanks for your comments !
|
A couple more things I thought about:
So, I’ve disabled the screenshot button in the above cases. TL;DR
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code LGTM
- Once the user has taken a screenshot and it’s in preview, there’s no point in showing the screenshot button again — there's really no reason to take an identical screenshot. So, I removed it.
I think it's currently a bit confusing, after a screenshot the button that is auto focussed is to start a recording. This seems counter intuitive after just taking a screenshot. I'm wondering if we should shared submit/discard button combination after taking a screenshot or taking a video. Submit closes the plugin, discard returns to the button combo record/screenshot.
What do you think?
Thanks for the review !
Yup, agreed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good now!
One last suggestion: swap the order of discard and submit. My gut feeling is that having the autofocus and visual order on submit is more intuitive.
Then ready to merge
alright sure @Murderlon , there was a small name conflict in the discard button which was failing |
Screenshot Support for Screen Capture Plugin
Closes #3310
Overview
Added screenshot capture functionality to
@uppy/screen-capture
plugin, allowing users to take screenshots alongside screen recordings.API Changes
Added new configuration options:
enableScreenshots
: Toggle screenshot functionality (default: false)screenshotQuality
: Control image compression (0.0-1.0, default: 0.9)preferredImageMimeType
: Set output format ('image/jpeg'|'image/png'|'image/webp', default: 'image/png')Implementation Details
Hi @Murderlon let me know what do you think about this, also how do I update the docs for this plugin ? and the stackblitz live example.
Screenshots