@scaleflex/widget-screen-capture 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6755 -0
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/style.css +126 -0
- package/dist/style.min.css +1 -0
- package/lib/CaptureScreen.js +77 -0
- package/lib/RecordButton.js +55 -0
- package/lib/ScreenRecIcon.js +41 -0
- package/lib/StopWatch.js +105 -0
- package/lib/StreamStatus.js +56 -0
- package/lib/SubmitButton.js +36 -0
- package/lib/common.slice.js +29 -0
- package/lib/defaultLocale.js +9 -0
- package/lib/index.js +438 -0
- package/lib/style.scss +149 -0
- package/package.json +31 -0
- package/types/index.d.ts +25 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Submit recorded video to filerobot. Enabled when file is available
|
|
4
|
+
*/
|
|
5
|
+
export default function SubmitButton(_ref) {
|
|
6
|
+
var recordedVideo = _ref.recordedVideo,
|
|
7
|
+
recording = _ref.recording,
|
|
8
|
+
onSubmit = _ref.onSubmit,
|
|
9
|
+
i18n = _ref.i18n;
|
|
10
|
+
if (recordedVideo && !recording) {
|
|
11
|
+
return /*#__PURE__*/_jsx("button", {
|
|
12
|
+
className: "filerobot-u-reset filerobot-c-btn filerobot-ScreenCapture-button filerobot-ScreenCapture-button--submit",
|
|
13
|
+
type: "button",
|
|
14
|
+
title: i18n('screenCaptureSubmitRecordedFileTitle'),
|
|
15
|
+
"aria-label": i18n('screenCaptureSubmitRecordedFileTitle'),
|
|
16
|
+
onClick: onSubmit,
|
|
17
|
+
"data-filerobot-super-focusable": true,
|
|
18
|
+
children: /*#__PURE__*/_jsxs("svg", {
|
|
19
|
+
"aria-hidden": "true",
|
|
20
|
+
focusable: "false",
|
|
21
|
+
className: "filerobot-c-icon",
|
|
22
|
+
width: "48",
|
|
23
|
+
height: "48",
|
|
24
|
+
viewBox: "0 0 48 48",
|
|
25
|
+
children: [/*#__PURE__*/_jsx("path", {
|
|
26
|
+
d: "M0 0h48v48h-48z",
|
|
27
|
+
fill: "none"
|
|
28
|
+
}), /*#__PURE__*/_jsx("path", {
|
|
29
|
+
d: "M38.71 20.07c-1.36-6.88-7.43-12.07-14.71-12.07-5.78 0-10.79 3.28-13.3 8.07-6.01.65-10.7 5.74-10.7 11.93 0 6.63 5.37 12 12 12h26c5.52 0 10-4.48 10-10 0-5.28-4.11-9.56-9.29-9.93zm-10.71 5.93v8h-8v-8h-6l10-10 10 10h-6z"
|
|
30
|
+
})]
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
5
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
6
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
7
|
+
import { createSlice } from '@reduxjs/toolkit';
|
|
8
|
+
import { PLUGINS_IDS } from '@scaleflex/widget-utils/lib/constants';
|
|
9
|
+
var initialState = {
|
|
10
|
+
streamActive: false,
|
|
11
|
+
audioStreamActive: false,
|
|
12
|
+
recording: false,
|
|
13
|
+
recordedVideo: null
|
|
14
|
+
};
|
|
15
|
+
var commonSlice = createSlice({
|
|
16
|
+
name: PLUGINS_IDS.SCREEN_CAPTURE,
|
|
17
|
+
initialState: initialState,
|
|
18
|
+
reducers: {
|
|
19
|
+
screenCaptureCommonStateUpdated: function screenCaptureCommonStateUpdated(state, action) {
|
|
20
|
+
return _objectSpread(_objectSpread({}, state), action.payload);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
var screenCaptureCommonStateUpdated = commonSlice.actions.screenCaptureCommonStateUpdated;
|
|
25
|
+
export { screenCaptureCommonStateUpdated };
|
|
26
|
+
export var selectScreenCaptureCommonState = function selectScreenCaptureCommonState(state) {
|
|
27
|
+
return state[PLUGINS_IDS.SCREEN_CAPTURE];
|
|
28
|
+
};
|
|
29
|
+
export default commonSlice.reducer;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
screenCaptureStartCapturingTitle: 'Begin screen capturing',
|
|
3
|
+
screenCaptureStopCapturingLabel: 'Stop screen capturing',
|
|
4
|
+
screenCaptureSubmitRecordedFileTitle: 'Submit captured video',
|
|
5
|
+
screenCaptureStreamActive: 'Stream active',
|
|
6
|
+
screenCaptureStreamPassiveTitle: 'Stream passive',
|
|
7
|
+
screenCaptureMicDisabledInfo: 'Microphone access denied by user',
|
|
8
|
+
screenCaptureRecordingText: 'Recording'
|
|
9
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
5
|
+
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
|
|
6
|
+
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
|
|
7
|
+
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
|
|
8
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
9
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
10
|
+
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
|
|
11
|
+
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
|
|
12
|
+
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
|
|
13
|
+
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
14
|
+
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
|
|
15
|
+
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
|
|
16
|
+
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
|
|
17
|
+
import { createElement } from 'react';
|
|
18
|
+
import { Plugin } from '@scaleflex/widget-core';
|
|
19
|
+
import Explorer from '@scaleflex/widget-explorer';
|
|
20
|
+
import Translator from '@scaleflex/widget-utils/lib/Translator';
|
|
21
|
+
import getFileTypeExtension from '@scaleflex/widget-utils/lib/getFileTypeExtension';
|
|
22
|
+
import { PLUGINS_IDS } from '@scaleflex/widget-utils/lib/constants';
|
|
23
|
+
import defaultLocale from './defaultLocale';
|
|
24
|
+
import ScreenRecIcon from './ScreenRecIcon';
|
|
25
|
+
import CaptureScreen from './CaptureScreen';
|
|
26
|
+
import screenCaptureReducer, { screenCaptureCommonStateUpdated } from './common.slice';
|
|
27
|
+
// TODO: find a way to show version of the current plugin
|
|
28
|
+
// why solution below isn't good?
|
|
29
|
+
// first import doesn't work with webpack 5 as it was deprecated
|
|
30
|
+
// second import fixes webpack 5 issue as it was mentioned in their docs
|
|
31
|
+
// but it exposes our package.json to the client and it is mentioned as security rist in mutiple places
|
|
32
|
+
// https://github.com/axelpale/genversion
|
|
33
|
+
// https://stackoverflow.com/questions/64993118/error-should-not-import-the-named-export-version-imported-as-version
|
|
34
|
+
// https://stackoverflow.com/questions/9153571/is-there-a-way-to-get-version-from-package-json-in-nodejs-code/10855054#10855054
|
|
35
|
+
// import { version } from '../package.json'
|
|
36
|
+
// import packageInfo from '../package.json'
|
|
37
|
+
|
|
38
|
+
// Adapted from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
|
|
39
|
+
function getMediaDevices() {
|
|
40
|
+
// check if screen capturing is supported
|
|
41
|
+
/* eslint-disable */
|
|
42
|
+
if (navigator && navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia && window && window.MediaRecorder) {
|
|
43
|
+
return navigator.mediaDevices;
|
|
44
|
+
}
|
|
45
|
+
/* eslint-enable */
|
|
46
|
+
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Screen capture
|
|
52
|
+
*/
|
|
53
|
+
var ScreenCapture = /*#__PURE__*/function (_Plugin) {
|
|
54
|
+
// static VERSION = packageInfo.version
|
|
55
|
+
|
|
56
|
+
function ScreenCapture(filerobot) {
|
|
57
|
+
var _this;
|
|
58
|
+
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
59
|
+
_classCallCheck(this, ScreenCapture);
|
|
60
|
+
_this = _callSuper(this, ScreenCapture, [filerobot, opts]);
|
|
61
|
+
_this.mediaDevices = getMediaDevices();
|
|
62
|
+
_this.protocol = location.protocol.match(/https/i) ? 'https' : 'http';
|
|
63
|
+
_this.id = PLUGINS_IDS.SCREEN_CAPTURE;
|
|
64
|
+
_this.title = _this.opts.title || 'Screen cast';
|
|
65
|
+
_this.type = 'acquirer';
|
|
66
|
+
_this.icon = ScreenRecIcon;
|
|
67
|
+
_this.defaultLocale = {
|
|
68
|
+
strings: defaultLocale
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// set default options
|
|
72
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints
|
|
73
|
+
var defaultOptions = {
|
|
74
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#Properties_of_shared_screen_tracks
|
|
75
|
+
displayMediaConstraints: {
|
|
76
|
+
video: {
|
|
77
|
+
width: 1280,
|
|
78
|
+
height: 720,
|
|
79
|
+
frameRate: {
|
|
80
|
+
ideal: 3,
|
|
81
|
+
max: 5
|
|
82
|
+
},
|
|
83
|
+
cursor: 'motion',
|
|
84
|
+
displaySurface: 'monitor'
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints/audio
|
|
88
|
+
userMediaConstraints: {
|
|
89
|
+
audio: true
|
|
90
|
+
},
|
|
91
|
+
preferredVideoMimeType: 'video/webm'
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// merge default options with the ones set by user
|
|
95
|
+
_this.opts = _objectSpread(_objectSpread({}, defaultOptions), opts);
|
|
96
|
+
|
|
97
|
+
// i18n
|
|
98
|
+
_this.translator = new Translator([_this.defaultLocale, _this.filerobot.locale, _this.opts.locale]);
|
|
99
|
+
_this.i18n = _this.translator.translate.bind(_this.translator);
|
|
100
|
+
_this.i18nArray = _this.translator.translateArray.bind(_this.translator);
|
|
101
|
+
|
|
102
|
+
// filerobot plugin class related
|
|
103
|
+
_this.install = _this.install.bind(_this);
|
|
104
|
+
_this.render = _this.render.bind(_this);
|
|
105
|
+
|
|
106
|
+
// screen capturer related
|
|
107
|
+
_this.start = _this.start.bind(_this);
|
|
108
|
+
_this.stop = _this.stop.bind(_this);
|
|
109
|
+
_this.startRecording = _this.startRecording.bind(_this);
|
|
110
|
+
_this.stopRecording = _this.stopRecording.bind(_this);
|
|
111
|
+
_this.submit = _this.submit.bind(_this);
|
|
112
|
+
_this.streamInterrupted = _this.streamInactivated.bind(_this);
|
|
113
|
+
|
|
114
|
+
// initialize
|
|
115
|
+
_this.captureActive = false;
|
|
116
|
+
_this.capturedMediaFile = null;
|
|
117
|
+
return _this;
|
|
118
|
+
}
|
|
119
|
+
_inherits(ScreenCapture, _Plugin);
|
|
120
|
+
return _createClass(ScreenCapture, [{
|
|
121
|
+
key: "install",
|
|
122
|
+
value: function install() {
|
|
123
|
+
// Return if browser doesn’t support getDisplayMedia and
|
|
124
|
+
if (!this.mediaDevices) {
|
|
125
|
+
this.filerobot.log('Screen recorder access is not supported', 'error');
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (Explorer) {
|
|
129
|
+
this.mount(Explorer, this);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, {
|
|
133
|
+
key: "uninstall",
|
|
134
|
+
value: function uninstall() {
|
|
135
|
+
if (this.videoStream) {
|
|
136
|
+
this.stop();
|
|
137
|
+
}
|
|
138
|
+
this.unmount();
|
|
139
|
+
}
|
|
140
|
+
}, {
|
|
141
|
+
key: "setPluginCommonState",
|
|
142
|
+
value: function setPluginCommonState(update) {
|
|
143
|
+
this.dispatch(screenCaptureCommonStateUpdated(update));
|
|
144
|
+
}
|
|
145
|
+
}, {
|
|
146
|
+
key: "getPluginReducer",
|
|
147
|
+
value: function getPluginReducer() {
|
|
148
|
+
return screenCaptureReducer;
|
|
149
|
+
}
|
|
150
|
+
}, {
|
|
151
|
+
key: "start",
|
|
152
|
+
value: function start() {
|
|
153
|
+
var _this2 = this;
|
|
154
|
+
if (!this.mediaDevices) {
|
|
155
|
+
return Promise.reject(new Error('Screen recorder access not supported'));
|
|
156
|
+
}
|
|
157
|
+
this.captureActive = true;
|
|
158
|
+
this.selectAudioStreamSource();
|
|
159
|
+
this.selectVideoStreamSource().then(function (res) {
|
|
160
|
+
// something happened in start -> return
|
|
161
|
+
if (res === false) {
|
|
162
|
+
// Close the Explorer panel if plugin is installed
|
|
163
|
+
// into Explorer (could be other parent UI plugin)
|
|
164
|
+
if (_this2.parent && _this2.parent.hideAllPanelsAndShowAddFilesPanel) {
|
|
165
|
+
_this2.parent.hideAllPanelsAndShowAddFilesPanel();
|
|
166
|
+
_this2.captureActive = false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}, {
|
|
172
|
+
key: "selectVideoStreamSource",
|
|
173
|
+
value: function selectVideoStreamSource() {
|
|
174
|
+
var _this3 = this;
|
|
175
|
+
// if active stream available, return it
|
|
176
|
+
if (this.videoStream) {
|
|
177
|
+
return new Promise(function (resolve) {
|
|
178
|
+
return resolve(_this3.videoStream);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ask user to select source to record and get mediastream from that
|
|
183
|
+
return this.mediaDevices.getDisplayMedia(this.opts.displayMediaConstraints).then(function (videoStream) {
|
|
184
|
+
_this3.videoStream = videoStream;
|
|
185
|
+
|
|
186
|
+
// add event listener to stop recording if stream is interrupted
|
|
187
|
+
_this3.videoStream.addEventListener('inactive', function (event) {
|
|
188
|
+
_this3.streamInactivated();
|
|
189
|
+
});
|
|
190
|
+
_this3.setPluginCommonState({
|
|
191
|
+
streamActive: true
|
|
192
|
+
});
|
|
193
|
+
return videoStream;
|
|
194
|
+
})["catch"](function () {
|
|
195
|
+
var err = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
196
|
+
_this3.filerobot.info(err, 'error', 5000);
|
|
197
|
+
_this3.userDenied = true;
|
|
198
|
+
setTimeout(function () {
|
|
199
|
+
_this3.userDenied = false;
|
|
200
|
+
}, 1000);
|
|
201
|
+
return false;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}, {
|
|
205
|
+
key: "selectAudioStreamSource",
|
|
206
|
+
value: function selectAudioStreamSource() {
|
|
207
|
+
var _this4 = this;
|
|
208
|
+
// if active stream available, return it
|
|
209
|
+
if (this.audioStream) {
|
|
210
|
+
return new Promise(function (resolve) {
|
|
211
|
+
return resolve(_this4.audioStream);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ask user to select source to record and get mediastream from that
|
|
216
|
+
return this.mediaDevices.getUserMedia(this.opts.userMediaConstraints).then(function (audioStream) {
|
|
217
|
+
_this4.audioStream = audioStream;
|
|
218
|
+
_this4.setPluginCommonState({
|
|
219
|
+
audioStreamActive: true
|
|
220
|
+
});
|
|
221
|
+
return audioStream;
|
|
222
|
+
})["catch"](function (err) {
|
|
223
|
+
if (err.name === 'NotAllowedError') {
|
|
224
|
+
_this4.filerobot.info(_this4.i18n('screenCaptureMicDisabledInfo'), 'error', 5000);
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}, {
|
|
230
|
+
key: "startRecording",
|
|
231
|
+
value: function startRecording() {
|
|
232
|
+
var _this5 = this;
|
|
233
|
+
var options = {};
|
|
234
|
+
this.capturedMediaFile = null;
|
|
235
|
+
this.recordingChunks = [];
|
|
236
|
+
var preferredVideoMimeType = this.opts.preferredVideoMimeType;
|
|
237
|
+
this.selectVideoStreamSource().then(function (videoStream) {
|
|
238
|
+
// Attempt to use the passed preferredVideoMimeType (if any) during recording.
|
|
239
|
+
// If the browser doesn't support it, we'll fall back to the browser default instead
|
|
240
|
+
if (preferredVideoMimeType && MediaRecorder.isTypeSupported(preferredVideoMimeType) && getFileTypeExtension(preferredVideoMimeType)) {
|
|
241
|
+
options.mimeType = preferredVideoMimeType;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// prepare tracks
|
|
245
|
+
var tracks = [videoStream.getVideoTracks()[0]];
|
|
246
|
+
|
|
247
|
+
// merge audio if exits
|
|
248
|
+
if (_this5.audioStream) {
|
|
249
|
+
tracks.push(_this5.audioStream.getAudioTracks()[0]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// create new stream from video and audio
|
|
253
|
+
_this5.outputStream = new MediaStream(tracks);
|
|
254
|
+
|
|
255
|
+
// initialize mediarecorder
|
|
256
|
+
_this5.recorder = new MediaRecorder(_this5.outputStream, options);
|
|
257
|
+
|
|
258
|
+
// push data to buffer when data available
|
|
259
|
+
_this5.recorder.addEventListener('dataavailable', function (event) {
|
|
260
|
+
_this5.recordingChunks.push(event.data);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// start recording
|
|
264
|
+
_this5.recorder.start();
|
|
265
|
+
|
|
266
|
+
// set plugin state to recording
|
|
267
|
+
_this5.setPluginCommonState({
|
|
268
|
+
recording: true
|
|
269
|
+
});
|
|
270
|
+
})["catch"](function (err) {
|
|
271
|
+
_this5.filerobot.log(err, 'error');
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}, {
|
|
275
|
+
key: "streamInactivated",
|
|
276
|
+
value: function streamInactivated() {
|
|
277
|
+
// get screen recorder state
|
|
278
|
+
var recording = this.getPluginCommonState().recording; // recordedVideo
|
|
279
|
+
|
|
280
|
+
// commented because It is generating this bug https://scaleflexhq.atlassian.net/browse/FRA-1038
|
|
281
|
+
// TODO: delete this comment if a long time passed without issues related to this fix
|
|
282
|
+
// if (!recordedVideo && !recording) {
|
|
283
|
+
// // Close the Explorer panel if plugin is installed
|
|
284
|
+
// // into Explorer (could be other parent UI plugin)
|
|
285
|
+
// if (this.parent && this.parent.hideAllPanelsAndShowAddFilesPanel) {
|
|
286
|
+
// this.parent.hideAllPanelsAndShowAddFilesPanel()
|
|
287
|
+
// }
|
|
288
|
+
// }
|
|
289
|
+
if (recording) {
|
|
290
|
+
// stop recorder if it is active
|
|
291
|
+
this.filerobot.log('Capture stream inactive — stop recording');
|
|
292
|
+
this.stopRecording();
|
|
293
|
+
}
|
|
294
|
+
this.videoStream = null;
|
|
295
|
+
this.audioStream = null;
|
|
296
|
+
this.setPluginCommonState({
|
|
297
|
+
streamActive: false,
|
|
298
|
+
audioStreamActive: false
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}, {
|
|
302
|
+
key: "stopRecording",
|
|
303
|
+
value: function stopRecording() {
|
|
304
|
+
var _this6 = this;
|
|
305
|
+
var stopped = new Promise(function (resolve, reject) {
|
|
306
|
+
_this6.recorder.addEventListener('stop', function () {
|
|
307
|
+
resolve();
|
|
308
|
+
});
|
|
309
|
+
_this6.recorder.stop();
|
|
310
|
+
});
|
|
311
|
+
return stopped.then(function () {
|
|
312
|
+
// recording stopped
|
|
313
|
+
_this6.setPluginCommonState({
|
|
314
|
+
recording: false
|
|
315
|
+
});
|
|
316
|
+
// get video file after recorder stopped
|
|
317
|
+
return _this6.getVideo();
|
|
318
|
+
}).then(function (file) {
|
|
319
|
+
// store media file
|
|
320
|
+
_this6.capturedMediaFile = file;
|
|
321
|
+
|
|
322
|
+
// create object url for capture result preview
|
|
323
|
+
_this6.setPluginCommonState({
|
|
324
|
+
recordedVideo: URL.createObjectURL(file.data)
|
|
325
|
+
});
|
|
326
|
+
}).then(function () {
|
|
327
|
+
_this6.recordingChunks = null;
|
|
328
|
+
_this6.recorder = null;
|
|
329
|
+
}, function (error) {
|
|
330
|
+
_this6.recordingChunks = null;
|
|
331
|
+
_this6.recorder = null;
|
|
332
|
+
throw error;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}, {
|
|
336
|
+
key: "submit",
|
|
337
|
+
value: function submit() {
|
|
338
|
+
try {
|
|
339
|
+
// add recorded file to filerobot
|
|
340
|
+
if (this.capturedMediaFile) {
|
|
341
|
+
this.filerobot.addFile(this.capturedMediaFile);
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
// Logging the error, exept restrictions, which is handled in Core
|
|
345
|
+
if (!err.isRestriction) {
|
|
346
|
+
this.filerobot.log(err, 'error');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}, {
|
|
351
|
+
key: "stop",
|
|
352
|
+
value: function stop() {
|
|
353
|
+
// flush video stream
|
|
354
|
+
if (this.videoStream) {
|
|
355
|
+
this.videoStream.getVideoTracks().forEach(function (track) {
|
|
356
|
+
track.stop();
|
|
357
|
+
});
|
|
358
|
+
this.videoStream.getAudioTracks().forEach(function (track) {
|
|
359
|
+
track.stop();
|
|
360
|
+
});
|
|
361
|
+
this.videoStream = null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// flush audio stream
|
|
365
|
+
if (this.audioStream) {
|
|
366
|
+
this.audioStream.getAudioTracks().forEach(function (track) {
|
|
367
|
+
track.stop();
|
|
368
|
+
});
|
|
369
|
+
this.audioStream.getVideoTracks().forEach(function (track) {
|
|
370
|
+
track.stop();
|
|
371
|
+
});
|
|
372
|
+
this.audioStream = null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// flush output stream
|
|
376
|
+
if (this.outputStream) {
|
|
377
|
+
this.outputStream.getAudioTracks().forEach(function (track) {
|
|
378
|
+
track.stop();
|
|
379
|
+
});
|
|
380
|
+
this.outputStream.getVideoTracks().forEach(function (track) {
|
|
381
|
+
track.stop();
|
|
382
|
+
});
|
|
383
|
+
this.outputStream = null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// remove preview video
|
|
387
|
+
this.setPluginCommonState({
|
|
388
|
+
recordedVideo: null
|
|
389
|
+
});
|
|
390
|
+
if (this.getPluginCommonState().recording) {
|
|
391
|
+
this.setPluginCommonState({
|
|
392
|
+
recording: false
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
this.captureActive = false;
|
|
396
|
+
}
|
|
397
|
+
}, {
|
|
398
|
+
key: "getVideo",
|
|
399
|
+
value: function getVideo() {
|
|
400
|
+
var mimeType = this.recordingChunks[0].type;
|
|
401
|
+
var fileExtension = getFileTypeExtension(mimeType);
|
|
402
|
+
if (!fileExtension) {
|
|
403
|
+
return Promise.reject(new Error("Could not retrieve recording: Unsupported media type \"".concat(mimeType, "\"")));
|
|
404
|
+
}
|
|
405
|
+
var name = "screencap-".concat(Date.now(), ".").concat(fileExtension);
|
|
406
|
+
var blob = new Blob(this.recordingChunks, {
|
|
407
|
+
type: mimeType
|
|
408
|
+
});
|
|
409
|
+
var file = {
|
|
410
|
+
source: this.id,
|
|
411
|
+
name: name,
|
|
412
|
+
data: new Blob([blob], {
|
|
413
|
+
type: mimeType
|
|
414
|
+
}),
|
|
415
|
+
type: mimeType
|
|
416
|
+
};
|
|
417
|
+
return Promise.resolve(file);
|
|
418
|
+
}
|
|
419
|
+
}, {
|
|
420
|
+
key: "render",
|
|
421
|
+
value: function render() {
|
|
422
|
+
// get screen recorder state
|
|
423
|
+
var streamActive = this.getPluginCommonState().streamActive;
|
|
424
|
+
if (!streamActive && !this.captureActive && !this.userDenied) {
|
|
425
|
+
this.start();
|
|
426
|
+
}
|
|
427
|
+
return /*#__PURE__*/createElement(CaptureScreen, {
|
|
428
|
+
onStartRecording: this.startRecording,
|
|
429
|
+
onStopRecording: this.stopRecording,
|
|
430
|
+
onStop: this.stop,
|
|
431
|
+
onSubmit: this.submit,
|
|
432
|
+
i18n: this.i18n,
|
|
433
|
+
stream: this.videoStream
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}]);
|
|
437
|
+
}(Plugin);
|
|
438
|
+
export { ScreenCapture as default };
|
package/lib/style.scss
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// packages/@scaleflex/widget-screen-capture/src/CaptureScreen.jsx
|
|
2
|
+
@import '@scaleflex/widget-core/lib/_utils.scss';
|
|
3
|
+
@import '@scaleflex/widget-core/lib/_variables.scss';
|
|
4
|
+
|
|
5
|
+
.filerobot-ScreenCapture-container {
|
|
6
|
+
width: 100%;
|
|
7
|
+
height: 100%;
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
align-items: center;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.filerobot-ScreenCapture-videoContainer {
|
|
15
|
+
width: 100%;
|
|
16
|
+
flex: 1;
|
|
17
|
+
flex-grow: 1;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
background-color: $darkgrey;
|
|
20
|
+
text-align: center;
|
|
21
|
+
position: relative;
|
|
22
|
+
|
|
23
|
+
.filerobot-size--md & {
|
|
24
|
+
max-width: 100%;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.filerobot-ScreenCapture-video {
|
|
29
|
+
max-width: 100%;
|
|
30
|
+
max-height: 100%;
|
|
31
|
+
position: absolute;
|
|
32
|
+
top: 0;
|
|
33
|
+
right: 0;
|
|
34
|
+
bottom: 0;
|
|
35
|
+
left: 0;
|
|
36
|
+
margin: auto;
|
|
37
|
+
outline: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.filerobot-ScreenCapture-buttonContainer {
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 75px;
|
|
43
|
+
border-top: 1px solid $gray-200;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
padding: 0 20px;
|
|
48
|
+
background-color: $white;
|
|
49
|
+
|
|
50
|
+
[data-filerobot-theme="dark"] & {
|
|
51
|
+
background-color: $gray-900;
|
|
52
|
+
border-top: 1px solid $gray-800;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.filerobot-ScreenCapture-button {
|
|
57
|
+
@include blue-border-focus;
|
|
58
|
+
width: 45px;
|
|
59
|
+
height: 45px;
|
|
60
|
+
border-radius: 50%;
|
|
61
|
+
color: $white;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
transition: all 0.3s;
|
|
64
|
+
|
|
65
|
+
[data-filerobot-theme="dark"] & {
|
|
66
|
+
@include blue-border-focus--dark;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.filerobot-size--md & {
|
|
70
|
+
width: 60px;
|
|
71
|
+
height: 60px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&:hover {
|
|
75
|
+
background-color: darken($red, 5%);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.filerobot-ScreenCapture-button svg {
|
|
80
|
+
width: 30px;
|
|
81
|
+
height: 30px;
|
|
82
|
+
max-width: 100%;
|
|
83
|
+
max-height: 100%;
|
|
84
|
+
display: inline-block;
|
|
85
|
+
vertical-align: text-top;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
fill: currentColor;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.filerobot-ScreenCapture-button--submit {
|
|
91
|
+
background-color: $blue;
|
|
92
|
+
margin-left: 12px;
|
|
93
|
+
|
|
94
|
+
&:hover {
|
|
95
|
+
background-color: darken($blue, 5%);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:disabled {
|
|
99
|
+
background-color: $gray-500;
|
|
100
|
+
cursor: default;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&:disabled:hover {
|
|
104
|
+
background-color: $gray-200;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.filerobot-ScreenCapture-title {
|
|
109
|
+
font-size: 22px;
|
|
110
|
+
line-height: 1.35;
|
|
111
|
+
font-weight: 400;
|
|
112
|
+
margin: 0;
|
|
113
|
+
margin-bottom: 5px;
|
|
114
|
+
padding: 0 15px;
|
|
115
|
+
max-width: 500px;
|
|
116
|
+
text-align: center;
|
|
117
|
+
color: $gray-800;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.filerobot-ScreenCapture-icon--stream {
|
|
121
|
+
position: absolute;
|
|
122
|
+
right: 0;
|
|
123
|
+
top: 0;
|
|
124
|
+
margin: 1rem;
|
|
125
|
+
z-index:1;
|
|
126
|
+
|
|
127
|
+
svg {
|
|
128
|
+
fill: $gray-500;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.filerobot-ScreenCapture-icon--streamActive svg {
|
|
133
|
+
animation: filerobot-ScreenCapture-icon--blink 1s cubic-bezier(0.47, 0, 0.75, 0.72) infinite;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@keyframes filerobot-ScreenCapture-icon--blink {
|
|
137
|
+
0% { fill: $blue;}
|
|
138
|
+
50% { fill: $gray-500; }
|
|
139
|
+
100% { fill: $blue; }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.filerobot-ScreenCapture-button--video {
|
|
143
|
+
color: $white;
|
|
144
|
+
background: $red;
|
|
145
|
+
|
|
146
|
+
&:hover {
|
|
147
|
+
background-color: darken($red, 10%);
|
|
148
|
+
}
|
|
149
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scaleflex/widget-screen-capture",
|
|
3
|
+
"description": "Scaleflex plugin that captures video from display or application.",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"style": "dist/style.min.css",
|
|
8
|
+
"types": "types/index.d.ts",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@scaleflex/widget-utils": "^0.0.1"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"react": "^19.0.0",
|
|
14
|
+
"react-dom": "^19.0.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@scaleflex/widget-core": "^0.0.0",
|
|
18
|
+
"@scaleflex/widget-explorer": "^0.0.0",
|
|
19
|
+
"react": ">=19.0.0",
|
|
20
|
+
"react-dom": ">=19.0.0"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"/dist",
|
|
27
|
+
"/lib",
|
|
28
|
+
"/types"
|
|
29
|
+
],
|
|
30
|
+
"gitHead": "64ea82e745b7deda36d6794863350e6671e9010d"
|
|
31
|
+
}
|