├── .babelrc.js
├── .gitignore
├── .prettierignore
├── README.MD
├── commitlint.config.js
├── dist
├── es
│ ├── components
│ │ ├── Controls.js
│ │ ├── Dragger.js
│ │ ├── FilePicker.js
│ │ ├── Icon.js
│ │ ├── Player.js
│ │ ├── Status.js
│ │ └── Trimmer.js
│ ├── index.js
│ ├── libs
│ │ ├── WebVideo.js
│ │ ├── formatSeconds.js
│ │ ├── preloadWebVideo.js
│ │ └── utils.js
│ ├── style.js
│ └── styles
│ │ ├── controls.scss
│ │ ├── dragger.scss
│ │ ├── file-picker.scss
│ │ ├── icon.scss
│ │ ├── main-container.scss
│ │ ├── player.scss
│ │ ├── status.scss
│ │ └── trimmer.scss
├── index.js
└── style.css
├── docs
├── build
│ └── bundle.65bace37.js
└── index.html
├── examples
├── BASE.MD
└── theme.css
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── components
│ ├── Controls.js
│ ├── Dragger.js
│ ├── FilePicker.js
│ ├── Icon.js
│ ├── Player.js
│ ├── Status.js
│ └── Trimmer.js
├── icons
│ ├── download.svg
│ ├── music.svg
│ ├── pause.svg
│ ├── play.svg
│ ├── replay.svg
│ └── spin.svg
├── index.js
├── libs
│ ├── WebVideo.js
│ ├── formatSeconds.js
│ ├── preloadWebVideo.js
│ └── utils.js
├── style.js
└── styles
│ ├── controls.scss
│ ├── dragger.scss
│ ├── file-picker.scss
│ ├── icon.scss
│ ├── main-container.scss
│ ├── player.scss
│ ├── status.scss
│ └── trimmer.scss
├── styleguide.config.js
├── styleguide
└── setup.js
├── testSetup.js
└── yarn.lock
/.babelrc.js:
--------------------------------------------------------------------------------
1 | // https://babeljs.io/docs/en/configuration
2 | const presets = ["@babel/preset-react"];
3 | if (process.env["BABEL_ENV"] === "es") {
4 | presets.unshift(["@babel/preset-env", { modules: false }]);
5 | } else {
6 | presets.unshift("@babel/preset-env");
7 | }
8 |
9 | const plugins = [
10 | "@babel/plugin-proposal-export-default-from",
11 | "@babel/plugin-proposal-class-properties",
12 | "@babel/plugin-proposal-logical-assignment-operators",
13 | ["@babel/plugin-proposal-optional-chaining", { loose: false }],
14 | ["@babel/plugin-proposal-pipeline-operator", { proposal: "minimal" }],
15 | ["@babel/plugin-proposal-nullish-coalescing-operator", { loose: false }],
16 | "@babel/plugin-proposal-do-expressions",
17 | "add-module-exports"
18 | ];
19 |
20 | module.exports = {
21 | presets,
22 | plugins,
23 | env: {
24 | test: {
25 | presets,
26 | plugins: [
27 | ...plugins,
28 | "@babel/plugin-transform-runtime",
29 | "dynamic-import-node"
30 | ]
31 | }
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 | .env.test
68 |
69 | # parcel-bundler cache (https://parceljs.org/)
70 | .cache
71 |
72 | # next.js build output
73 | .next
74 |
75 | # nuxt.js build output
76 | .nuxt
77 |
78 | # vuepress build output
79 | .vuepress/dist
80 |
81 | # Serverless directories
82 | .serverless/
83 |
84 | # FuseBox cache
85 | .fusebox/
86 |
87 | # DynamoDB Local files
88 | .dynamodb/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | docs/
3 | node_modules/
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # react-video-trimmer
2 |
3 | Amazing React component for manipulating video length.
4 |
5 | With the aid of FFMPEG in the browser, it get easier to do amazing things with
6 | media content.
7 |
8 | ## Demo
9 |
10 | [See Demo](https://limistah.github.io/react-video-trimmer/)
11 |
12 | ## Installation
13 |
14 | ```bash
15 | npm install --save react-video-trimmer
16 | ```
17 |
18 | or:
19 |
20 | ```bash
21 | yarn add react-video-trimmer
22 | ```
23 |
24 | ## Usage
25 |
26 | **NOTE:** Do import the styles from `react-video-trimmer/dist/style.css`
27 |
28 | ```js
29 | import ReactVideoTrimmer from "react-video-trimmer";
30 |
31 |
32 |
;
33 | ```
34 |
35 | ## Props
36 |
37 | ### timeRange: Number
38 |
39 | Start and end value in seconds the trimmer should restrict to.ti
40 |
41 | ```js static
42 | import React from "react";
43 | import ReactVideoTrimmer from "react-video-trimmer";
44 | import "react-video-trimmer/dist/style.css";
45 |
46 | const Trimmer = () => {
47 | return (
48 |
49 |
50 |
51 | );
52 | };
53 | ```
54 |
55 | ### onVideoEncode: function
56 |
57 | Handler that receives the video encoding once it has been encoded
58 |
59 | ```js static
60 | import React from "react";
61 | import ReactVideoTrimmer from "react-video-trimmer";
62 | import "react-video-trimmer/dist/style.css";
63 |
64 | const Trimmer = () => {
65 | const handleVideoEncode = React.useCallback(result => {
66 | console.log("Encoding Result:", result);
67 | });
68 | return (
69 |
70 |
74 |
75 | );
76 | };
77 | ```
78 |
79 | ### loadingFFMPEGText: string
80 |
81 | A text to tell users that FFMPEG is being loaded in the background.
82 |
83 | Default: _Please wait..._
84 |
85 | ```js static
86 | import React from "react";
87 | import ReactVideoTrimmer from "react-video-trimmer";
88 | import "react-video-trimmer/dist/style.css";
89 |
90 | const Trimmer = () => {
91 | const handleVideoEncode = React.useCallback(result => {
92 | console.log("Encoding Result:", result);
93 | });
94 | return (
95 |
96 |
101 |
102 | );
103 | };
104 | ```
105 |
106 | ## React Ref
107 |
108 | Pass a ref to access all the static methods of ReactVideoTrimmer methods
109 |
110 | ```js static
111 | import React from "react";
112 | import ReactVideoTrimmer from "react-video-trimmer";
113 | import "react-video-trimmer/dist/style.css";
114 |
115 | const Trimmer = () => {
116 | const trimmerRef = React.useRef();
117 | return (
118 |
119 |
120 |
121 | );
122 | };
123 | ```
124 |
125 | ## Methods
126 |
127 | ### handleFFMPEGStdout: void
128 |
129 | A listener to [ffmpeg-webworker](https://www.npmjs.com/package/ffmpeg-webworker)
130 | `FFMPEGStdout` event
131 |
132 | ### handleFFMPEGReady: void
133 |
134 | A listener to [ffmpeg-webworker](https://www.npmjs.com/package/ffmpeg-webworker)
135 | `FFMPEGReady` event
136 |
137 | ### handleFFMPEGFileReceived: void
138 |
139 | A listener to [ffmpeg-webworker](https://www.npmjs.com/package/ffmpeg-webworker)
140 | `FFMPEGFileReceived` event
141 |
142 | ### handleFFMPEGDone: void
143 |
144 | A listener to [ffmpeg-webworker](https://www.npmjs.com/package/ffmpeg-webworker)
145 | `FFMPEGDone` event
146 |
147 | > Converts the returned result into a `Blob`, before updating the video player
148 |
149 | ### decodeVideoFile: void
150 |
151 | ##### params
152 |
153 | - **file: Blob** A Blob/File with type matching `video/*`
154 | - **doneCB: function** Called after the decode action is completed
155 |
156 | ### handleFileSelected: void
157 |
158 | Called when a valid video file is selected, in turn calls `decodeVideoFile` for
159 | proper handling
160 |
161 | ##### params
162 |
163 | - **file: Blob** A Blob/File with type matching `video/*`
164 |
165 | ### handleEncodeVideo: void
166 |
167 | Called when a valid video file is selected, in turn calls `decodeVideoFile` for
168 | proper handling
169 |
170 | ##### params
171 |
172 | - **file: Blob** A Blob/File with type matching `video/*`
173 |
174 | ### handleDownloadVideo: void
175 |
176 | Handler for the Download button `onClick` event. Downloads the converted video
177 | file
178 |
179 | ##### params
180 |
181 | - **encodedVideo: Blob** Encoded video data converted to `Blob`
182 |
183 | ## Preloading ffmpeg
184 |
185 | ffmpeg.js will not load until the component is in scope. To override this, a
186 | `preloadWebVideo` field has been included to make ffmpeg load ahead of this
187 | component mount period.
188 |
189 | ```js static
190 | import React from "react";
191 | import { preloadWebVideo } from "react-video-trimmer";
192 |
193 | // It is a function, no other process is required
194 | preloadWebVideo();
195 | ```
196 |
197 | ## License
198 |
199 | MIT
200 |
201 | ## Credit
202 |
203 | This library uses the work of the guys at
204 | [ffmpeg-webworker](https://www.npmjs.com/package/ffmpeg-webworker)
205 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/label-has-associated-control */
2 | // eslint-disable-next-line quotes
3 | module.exports = { extends: ["@commitlint/config-angular"] };
4 |
--------------------------------------------------------------------------------
/dist/es/components/Controls.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "./Icon";
3 |
4 | var Controls = function Controls(_ref) {
5 | var onPlayPauseClick = _ref.onPlayPauseClick,
6 | playing = _ref.playing,
7 | onReselectFile = _ref.onReselectFile,
8 | processing = _ref.processing,
9 | onEncode = _ref.onEncode,
10 | showEncodeBtn = _ref.showEncodeBtn,
11 | canDownload = _ref.canDownload,
12 | onDownload = _ref.onDownload;
13 | return React.createElement("div", {
14 | className: "rvt-controls-cont"
15 | }, React.createElement("a", {
16 | className: "rvt-controller-item",
17 | title: "Pause",
18 | onClick: onPlayPauseClick
19 | }, React.createElement(Icon, {
20 | name: playing ? "pause" : "play"
21 | })), React.createElement("a", {
22 | className: "rvt-controller-item",
23 | title: "Select File",
24 | onClick: onReselectFile
25 | }, React.createElement(Icon, {
26 | name: "music"
27 | })), showEncodeBtn && React.createElement("div", {
28 | className: "rvt-controller-dropdown rvt-controller-list-wrap"
29 | }, canDownload ? React.createElement("a", {
30 | className: "rvt-controller-item",
31 | onClick: onDownload
32 | }, React.createElement(Icon, {
33 | name: "download"
34 | })) : React.createElement("a", {
35 | className: "rvt-controller-item",
36 | onClick: onEncode
37 | }, React.createElement(Icon, {
38 | name: processing ? "spin" : "replay"
39 | }))));
40 | };
41 |
42 | export default Controls;
--------------------------------------------------------------------------------
/dist/es/components/Dragger.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
6 |
7 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
8 |
9 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
10 |
11 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
12 |
13 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
16 |
17 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
18 |
19 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
20 |
21 | import React, { PureComponent } from "react";
22 | import PropTypes from "prop-types";
23 |
24 | var Dragger =
25 | /*#__PURE__*/
26 | function (_PureComponent) {
27 | _inherits(Dragger, _PureComponent);
28 |
29 | function Dragger() {
30 | var _getPrototypeOf2;
31 |
32 | var _this;
33 |
34 | _classCallCheck(this, Dragger);
35 |
36 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
37 | args[_key] = arguments[_key];
38 | }
39 |
40 | _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(Dragger)).call.apply(_getPrototypeOf2, [this].concat(args)));
41 |
42 | _defineProperty(_assertThisInitialized(_this), "_screenX", null);
43 |
44 | _defineProperty(_assertThisInitialized(_this), "_screenY", null);
45 |
46 | _defineProperty(_assertThisInitialized(_this), "_ox", null);
47 |
48 | _defineProperty(_assertThisInitialized(_this), "_oy", null);
49 |
50 | _defineProperty(_assertThisInitialized(_this), "handleMouseDown", function (e) {
51 | _this._screenX = e.screenX;
52 | _this._screenY = e.screenY;
53 | _this._ox = _this.props.x;
54 | _this._oy = _this.props.y;
55 | window.addEventListener("mousemove", _this.handleMouseMove, false);
56 | window.addEventListener("mouseup", _this.handleMouseUp, false);
57 | });
58 |
59 | _defineProperty(_assertThisInitialized(_this), "handleMouseMove", function (e) {
60 | _this.props.onDrag({
61 | x: e.screenX - _this._screenX + _this._ox,
62 | y: e.screenY - _this._screenY + _this._oy
63 | });
64 | });
65 |
66 | _defineProperty(_assertThisInitialized(_this), "handleMouseUp", function () {
67 | window.removeEventListener("mousemove", _this.handleMouseMove);
68 | window.removeEventListener("mouseup", _this.handleMouseUp);
69 |
70 | var handler = _this.props.onDragStop || function () {};
71 |
72 | handler();
73 | });
74 |
75 | return _this;
76 | }
77 |
78 | _createClass(Dragger, [{
79 | key: "render",
80 | value: function render() {
81 | return React.createElement("div", {
82 | className: "rvt-dragger " + this.props.className || "",
83 | onMouseDown: this.handleMouseDown,
84 | style: {
85 | left: this.props.x + "px",
86 | top: this.props.y + "px"
87 | }
88 | }, this.props.children);
89 | }
90 | }]);
91 |
92 | return Dragger;
93 | }(PureComponent);
94 |
95 | _defineProperty(Dragger, "defaultProps", {
96 | onDrag: function onDrag() {},
97 | x: 0,
98 | y: 0
99 | });
100 |
101 | _defineProperty(Dragger, "propTypes", {
102 | x: PropTypes.number,
103 | y: PropTypes.number,
104 | onDrag: PropTypes.func,
105 | className: PropTypes.string,
106 | children: PropTypes.element
107 | });
108 |
109 | export { Dragger as default };
--------------------------------------------------------------------------------
/dist/es/components/FilePicker.js:
--------------------------------------------------------------------------------
1 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2 |
3 | import React, { useCallback, useRef } from "react";
4 | import { isVideo, noop } from "../libs/utils";
5 | import Icon from "./Icon";
6 | import Dropzone from "react-dropzone";
7 |
8 | function FilePicker(props) {
9 | var onDrop = useCallback(function (acceptedFiles) {
10 | if (acceptedFiles.length) {
11 | var video = acceptedFiles[0];
12 | var handler = props.onFileSelected || noop;
13 | handler(video);
14 | }
15 | }, []);
16 | var MAX_SIZE = props.maxSize || 10000024;
17 | var MIN_SIZE = props.minSize || 0; // const handleFileChange = useCallback(e => {
18 | // if (e.target.files.length) {
19 | // const video = e.target.files[0];
20 | // if (isVideo(video)) {
21 | // const handler = props.onFileSelected || noop;
22 | // handler(video);
23 | // } else {
24 | // return alert("Unsupported File Type");
25 | // }
26 | // }
27 | // });
28 |
29 | var toMB = function toMB(_byte) {
30 | return Math.round(_byte / 1000000);
31 | };
32 |
33 | return React.createElement(Dropzone, {
34 | onDrop: onDrop,
35 | maxSize: MAX_SIZE,
36 | minSize: MIN_SIZE,
37 | accept: "video/*"
38 | }, function (_ref) {
39 | var getRootProps = _ref.getRootProps,
40 | getInputProps = _ref.getInputProps,
41 | isDragActive = _ref.isDragActive;
42 | return React.createElement("div", _extends({}, getRootProps(), {
43 | className: "rvt-file-picker"
44 | }), React.createElement(Icon, {
45 | name: "music"
46 | }), React.createElement("input", getInputProps()), isDragActive ? React.createElement("p", null, "Drop the video here ...") : React.createElement(React.Fragment, null, React.createElement("p", null, "Drag 'n' drop a video here, or click to select one"), React.createElement("p", null, React.createElement("small", null, "(", toMB(MIN_SIZE), " - ", toMB(MAX_SIZE), "MB)"))));
47 | });
48 | }
49 |
50 | export default FilePicker;
--------------------------------------------------------------------------------
/dist/es/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | var Download = function Download(_ref) {
5 | var className = _ref.className;
6 | return React.createElement("svg", {
7 | className: className,
8 | viewBox: "0 0 24 24",
9 | xmlns: "http://www.w3.org/2000/svg"
10 | }, React.createElement("path", {
11 | d: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"
12 | }), React.createElement("path", {
13 | d: "M0 0h24v24H0z",
14 | fill: "none"
15 | }));
16 | };
17 |
18 | var Music = function Music(_ref2) {
19 | var className = _ref2.className;
20 | return React.createElement("svg", {
21 | className: className,
22 | viewBox: "0 0 24 24",
23 | xmlns: "http://www.w3.org/2000/svg"
24 | }, React.createElement("path", {
25 | d: "M0 0h24v24H0z",
26 | fill: "none"
27 | }), React.createElement("path", {
28 | d: "M15 6H3v2h12V6zm0 4H3v2h12v-2zM3 16h8v-2H3v2zM17 6v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6h-5z"
29 | }));
30 | };
31 |
32 | var Play = function Play(_ref3) {
33 | var className = _ref3.className;
34 | return React.createElement("svg", {
35 | className: className,
36 | height: "24",
37 | viewBox: "0 0 24 24",
38 | width: "24",
39 | xmlns: "http://www.w3.org/2000/svg"
40 | }, React.createElement("path", {
41 | d: "M0 0h24v24H0z",
42 | fill: "none"
43 | }), React.createElement("path", {
44 | d: "M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"
45 | }));
46 | };
47 |
48 | var Pause = function Pause(_ref4) {
49 | var className = _ref4.className;
50 | return React.createElement("svg", {
51 | className: className,
52 | height: "24",
53 | viewBox: "0 0 24 24",
54 | width: "24",
55 | xmlns: "http://www.w3.org/2000/svg"
56 | }, React.createElement("path", {
57 | d: "M0 0h24v24H0z",
58 | fill: "none"
59 | }), React.createElement("path", {
60 | d: "M9 16h2V8H9v8zm3-14C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm1-4h2V8h-2v8z"
61 | }));
62 | };
63 |
64 | var Replay = function Replay(_ref5) {
65 | var className = _ref5.className;
66 | return React.createElement("svg", {
67 | className: className,
68 | viewBox: "0 0 24 24",
69 | xmlns: "http://www.w3.org/2000/svg"
70 | }, React.createElement("path", {
71 | d: "M0 0h24v24H0z",
72 | fill: "none"
73 | }), React.createElement("path", {
74 | d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"
75 | }));
76 | };
77 |
78 | var Spin = function Spin(_ref6) {
79 | var className = _ref6.className;
80 | return React.createElement("svg", {
81 | className: className,
82 | xmlns: "http://www.w3.org/2000/svg",
83 | viewBox: "0 0 42 42"
84 | }, React.createElement("path", {
85 | d: "M21 37c-4.3 0-8.3-1.7-11.3-4.7S5 25.3 5 21c0-3 .8-6 2.5-8.5C9 10 11.2 8 13.8 6.7l1.3 2.7c-2.1 1.1-3.9 2.7-5.2 4.7-1.3 2.1-2 4.5-2 6.9 0 7.2 5.8 13 13 13s13-5.8 13-13c0-2.5-.7-4.9-2-6.9s-3.1-3.6-5.2-4.7L28 6.7c2.8 1.3 5 3.3 6.5 5.8C36.2 15 37 18 37 21c0 4.3-1.7 8.3-4.7 11.3S25.3 37 21 37z"
86 | }));
87 | };
88 |
89 | var Icon = function Icon(props) {
90 | var El = Download;
91 |
92 | switch (props.name) {
93 | case "music":
94 | El = Music;
95 | break;
96 |
97 | case "play":
98 | El = Play;
99 | break;
100 |
101 | case "pause":
102 | El = Pause;
103 | break;
104 |
105 | case "replay":
106 | El = Replay;
107 | break;
108 |
109 | case "spin":
110 | El = Spin;
111 | break;
112 |
113 | default:
114 | El = Download;
115 | break;
116 | }
117 |
118 | return React.createElement(El, {
119 | className: "rvt-icon rat-icon-".concat(props.name, " ").concat(props.className).trim()
120 | });
121 | };
122 |
123 | Icon.propTypes = {
124 | name: PropTypes.string
125 | };
126 | export default Icon;
--------------------------------------------------------------------------------
/dist/es/components/Player.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
6 |
7 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
8 |
9 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
10 |
11 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
12 |
13 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
16 |
17 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
18 |
19 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
20 |
21 | import React from "react";
22 | import ReactPlayer from "react-player";
23 | import { formatSeconds, noop, leftZero } from "../libs/utils";
24 |
25 | var Player =
26 | /*#__PURE__*/
27 | function (_React$Component) {
28 | _inherits(Player, _React$Component);
29 |
30 | function Player() {
31 | var _getPrototypeOf2;
32 |
33 | var _this;
34 |
35 | _classCallCheck(this, Player);
36 |
37 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
38 | args[_key] = arguments[_key];
39 | }
40 |
41 | _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(Player)).call.apply(_getPrototypeOf2, [this].concat(args)));
42 |
43 | _defineProperty(_assertThisInitialized(_this), "state", {
44 | playing: _this.props.playVideo || false
45 | });
46 |
47 | _defineProperty(_assertThisInitialized(_this), "handlePlayerProgress", function (data) {
48 | if (data.loaded) {
49 | var playedSeconds = data.playedSeconds;
50 | var startTimeRange = _this.props.timeRange.start;
51 | var endTimeRange = _this.props.timeRange.end;
52 | var playedSecondsIsLowerThanStartTime = playedSeconds <= startTimeRange;
53 | var playedSecondsIsGreaterThanEndTime = playedSeconds >= endTimeRange;
54 |
55 | if (playedSecondsIsLowerThanStartTime) {
56 | _this.player.seekTo(startTimeRange, "seconds");
57 | }
58 |
59 | if (playedSecondsIsGreaterThanEndTime) {
60 | _this.player.seekTo(startTimeRange, "seconds"); // this.setState({ playing: false });
61 |
62 | }
63 |
64 | var handler = _this.props.onPlayerProgress || noop;
65 | handler(playedSeconds);
66 | }
67 | });
68 |
69 | _defineProperty(_assertThisInitialized(_this), "handleOnPause", function () {
70 | var handler = _this.props.onPlayerPause || noop;
71 | handler();
72 | });
73 |
74 | _defineProperty(_assertThisInitialized(_this), "handleOnPlay", function () {
75 | var handler = _this.props.onPlayerPlay || noop;
76 | handler();
77 | });
78 |
79 | return _this;
80 | }
81 |
82 | _createClass(Player, [{
83 | key: "componentWillReceiveProps",
84 | value: function componentWillReceiveProps(newProps) {
85 | var newTimeRange = newProps.timeRange;
86 | var oldTimeRange = this.props.timeRange;
87 | var canSeek = oldTimeRange && newTimeRange.start !== oldTimeRange.start || !oldTimeRange && newTimeRange.start > 0;
88 |
89 | if (canSeek) {
90 | this.setState({
91 | playing: false
92 | });
93 | this.player.seekTo(newTimeRange.start, "seconds");
94 | }
95 |
96 | if (newProps.playVideo !== this.props.playVideo) {
97 | this.setState({
98 | playing: newProps.playVideo
99 | });
100 | }
101 | }
102 | }, {
103 | key: "displaySeconds",
104 | value: function displaySeconds(seconds) {
105 | return seconds.toFixed(2) + "s";
106 | }
107 | }, {
108 | key: "render",
109 | value: function render() {
110 | var _this2 = this,
111 | _React$createElement;
112 |
113 | var _this$props$timeRange = this.props.timeRange,
114 | start = _this$props$timeRange.start,
115 | end = _this$props$timeRange.end;
116 | return React.createElement("div", {
117 | className: "rvt-player-cont",
118 | onContextMenu: function onContextMenu() {}
119 | }, React.createElement(ReactPlayer, (_React$createElement = {
120 | onPlay: this.handleOnPause
121 | }, _defineProperty(_React$createElement, "onPlay", this.handleOnPlay), _defineProperty(_React$createElement, "onProgress", this.handlePlayerProgress), _defineProperty(_React$createElement, "url", this.props.src), _defineProperty(_React$createElement, "ref", function ref(el) {
122 | return _this2.player = el;
123 | }), _defineProperty(_React$createElement, "playing", this.state.playing), _defineProperty(_React$createElement, "style", {
124 | margin: "0 auto"
125 | }), _React$createElement)), React.createElement("div", {
126 | className: "rvt-player-time-range-cont"
127 | }, React.createElement("span", {
128 | className: "rvt-player-time-range"
129 | }, "From: ", React.createElement("strong", null, this.displaySeconds(start))), React.createElement("span", {
130 | className: "rvt-player-time-range"
131 | }, "To: ", React.createElement("strong", null, this.displaySeconds(end))), React.createElement("span", {
132 | className: "rvt-player-time-range"
133 | }, "Selected ", React.createElement("strong", null, this.displaySeconds(end - start)), " of", " ", React.createElement("strong", null, this.displaySeconds(this.props.timeLimit)), " allowed")));
134 | }
135 | }]);
136 |
137 | return Player;
138 | }(React.Component);
139 |
140 | export default Player;
--------------------------------------------------------------------------------
/dist/es/components/Status.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | var Status = function Status(_ref) {
4 | var children = _ref.children;
5 | return React.createElement("div", {
6 | className: "rvt-status"
7 | }, children);
8 | };
9 |
10 | export default Status;
--------------------------------------------------------------------------------
/dist/es/components/Trimmer.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
6 |
7 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
8 |
9 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
10 |
11 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
12 |
13 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
16 |
17 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
18 |
19 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
20 |
21 | import React, { PureComponent } from "react";
22 | import Dragger from "./Dragger";
23 | import { noop, formatSeconds, leftZero } from "../libs/utils";
24 |
25 | var TrimmerOverLay = function TrimmerOverLay(props) {
26 | return React.createElement("div", {
27 | className: "rvt-trimmer",
28 | style: {
29 | width: props.width,
30 | left: props.left,
31 | right: props.right
32 | }
33 | });
34 | };
35 |
36 | var TimeStamp = function TimeStamp(props) {
37 | var formated = formatSeconds(props.time);
38 | return React.createElement("div", {
39 | className: "rvt-player-cursor-current"
40 | }, React.createElement("span", {
41 | className: "rvt-player-num"
42 | }, formated[0]), "'", React.createElement("span", {
43 | className: "rvt-player-num"
44 | }, formated[1]), !props.noMicroSeconds && React.createElement(React.Fragment, null, ".", React.createElement("span", {
45 | className: "rvt-player-num"
46 | }, leftZero(formated[2], 2))));
47 | };
48 |
49 | var Trimmer =
50 | /*#__PURE__*/
51 | function (_PureComponent) {
52 | _inherits(Trimmer, _PureComponent);
53 |
54 | function Trimmer() {
55 | var _getPrototypeOf2;
56 |
57 | var _this;
58 |
59 | _classCallCheck(this, Trimmer);
60 |
61 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
62 | args[_key] = arguments[_key];
63 | }
64 |
65 | _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(Trimmer)).call.apply(_getPrototypeOf2, [this].concat(args)));
66 |
67 | _defineProperty(_assertThisInitialized(_this), "pos2Time", function (pos) {
68 | return pos / _this.props.widthDurationRatio;
69 | });
70 |
71 | _defineProperty(_assertThisInitialized(_this), "time2pos", function (time) {
72 | return time * _this.props.widthDurationRatio;
73 | });
74 |
75 | _defineProperty(_assertThisInitialized(_this), "keepInRange", function (x) {
76 | var containerWidth = _this.props.containerWidth;
77 |
78 | if (x < 0) {
79 | return 0;
80 | }
81 |
82 | if (x > containerWidth) {
83 | return containerWidth;
84 | }
85 |
86 | return x;
87 | });
88 |
89 | _defineProperty(_assertThisInitialized(_this), "withinTimeLimit", function (time) {
90 | var isDragEnd = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
91 | var timeLimit = _this.props.timeLimit;
92 | var startTime = _this.props.startTime;
93 | var endTime = time;
94 |
95 | if (!isDragEnd) {
96 | startTime = time;
97 | endTime = _this.props.endTime;
98 | }
99 |
100 | var duration = _this.props.duration;
101 | var timeTillEnd = duration - endTime;
102 | var currentRange = duration - startTime - timeTillEnd;
103 | return timeLimit ? currentRange <= timeLimit : true;
104 | });
105 |
106 | _defineProperty(_assertThisInitialized(_this), "withinTimeRange", function (time) {
107 | var isDragEnd = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
108 | var timeRange = _this.props.timeRangeLimit;
109 | var interval = time - _this.props.startTime;
110 |
111 | if (!isDragEnd) {
112 | interval = _this.props.endTime - time;
113 | }
114 |
115 | return timeRange ? interval >= timeRange : true;
116 | });
117 |
118 | _defineProperty(_assertThisInitialized(_this), "handleDragStart", function (pos) {
119 | var pos2Time = _this.pos2Time(_this.keepInRange(pos.x));
120 |
121 | var time = pos2Time;
122 | var currentTime = _this.props.currentTime;
123 |
124 | var currentTimeIsWithinRange = _this.withinTimeRange(time, false);
125 |
126 | var currentTimeIsWithinLimit = _this.withinTimeLimit(time, false);
127 |
128 | if (time >= currentTime || !currentTimeIsWithinRange || !currentTimeIsWithinLimit) {
129 | time = _this.props.startTime;
130 |
131 | var handler = _this.props.onPausePlayer || function () {};
132 |
133 | handler();
134 | }
135 |
136 | _this.props.onStartTimeChange(time);
137 | });
138 |
139 | _defineProperty(_assertThisInitialized(_this), "handleDragEnd", function (pos) {
140 | var pos2Time = _this.pos2Time(_this.keepInRange(pos.x));
141 |
142 | var time = pos2Time;
143 | var endTime = _this.props.endTime;
144 | var currentTime = _this.props.currentTime;
145 |
146 | var currentTimeIsWithinRange = _this.withinTimeRange(time);
147 |
148 | var currentTimeIsWithinLimit = _this.withinTimeLimit(time);
149 |
150 | if (currentTime >= time || !currentTimeIsWithinRange || !currentTimeIsWithinLimit) {
151 | time = _this.props.endTime;
152 |
153 | var handler = _this.props.onPausePlayer || function () {};
154 |
155 | handler();
156 | }
157 |
158 | _this.props.onEndTimeChange(time);
159 | });
160 |
161 | _defineProperty(_assertThisInitialized(_this), "handleDragStop", function () {
162 | var handler = _this.props.onGetData || noop;
163 | handler({
164 | start: _this.props.startTime,
165 | end: _this.props.endTime
166 | });
167 | });
168 |
169 | _defineProperty(_assertThisInitialized(_this), "getTrimmerWidth", function (width) {
170 | return _this.props.containerWidth - width;
171 | });
172 |
173 | return _this;
174 | }
175 |
176 | _createClass(Trimmer, [{
177 | key: "render",
178 | value: function render() {
179 | var start = this.time2pos(this.props.startTime);
180 | var end = this.time2pos(this.props.endTime);
181 | var current = this.time2pos(this.props.currentTime);
182 | return React.createElement(React.Fragment, null, React.createElement(TrimmerOverLay, {
183 | left: 0,
184 | width: start
185 | }), React.createElement(Dragger, {
186 | x: start,
187 | onDrag: this.handleDragStart,
188 | onDragStop: this.handleDragStop
189 | }, React.createElement(TimeStamp, {
190 | time: this.props.startTime
191 | })), React.createElement(Dragger, {
192 | x: current,
193 | onDrag: function onDrag() {},
194 | onDragStop: function onDragStop() {}
195 | }, React.createElement(TimeStamp, {
196 | noMicroSeconds: true,
197 | time: this.props.currentTime
198 | })), React.createElement(Dragger, {
199 | x: end,
200 | onDrag: this.handleDragEnd,
201 | onDragStop: this.handleDragStop
202 | }, React.createElement(TimeStamp, {
203 | time: this.props.endTime
204 | })), React.createElement(TrimmerOverLay, {
205 | right: 0,
206 | width: this.getTrimmerWidth(end)
207 | }));
208 | }
209 | }]);
210 |
211 | return Trimmer;
212 | }(PureComponent);
213 |
214 | export var VideoTrimmer =
215 | /*#__PURE__*/
216 | function (_PureComponent2) {
217 | _inherits(VideoTrimmer, _PureComponent2);
218 |
219 | function VideoTrimmer() {
220 | var _getPrototypeOf3;
221 |
222 | var _this2;
223 |
224 | _classCallCheck(this, VideoTrimmer);
225 |
226 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
227 | args[_key2] = arguments[_key2];
228 | }
229 |
230 | _this2 = _possibleConstructorReturn(this, (_getPrototypeOf3 = _getPrototypeOf(VideoTrimmer)).call.apply(_getPrototypeOf3, [this].concat(args)));
231 |
232 | _defineProperty(_assertThisInitialized(_this2), "state", {
233 | start: 0,
234 | end: 0
235 | });
236 |
237 | _defineProperty(_assertThisInitialized(_this2), "handleStartTimeChange", function (time) {
238 | _this2.setState({
239 | start: time
240 | });
241 | });
242 |
243 | _defineProperty(_assertThisInitialized(_this2), "handleGetTrimData", function () {
244 | var trimmerHandler = _this2.props.onTrim || noop;
245 | setTimeout(function () {
246 | return trimmerHandler({
247 | start: _this2.state.start || _this2.props.timeRange.start,
248 | end: _this2.state.end || _this2.props.timeRange.end
249 | });
250 | }, 200);
251 | });
252 |
253 | _defineProperty(_assertThisInitialized(_this2), "handleEndTimeChange", function (time) {
254 | _this2.setState({
255 | end: time
256 | });
257 | });
258 |
259 | return _this2;
260 | }
261 |
262 | _createClass(VideoTrimmer, [{
263 | key: "render",
264 | value: function render() {
265 | var _this3 = this;
266 |
267 | return React.createElement("div", {
268 | className: "rvt-trimmer-cont",
269 | ref: function ref(e) {
270 | return _this3.containerRef = e;
271 | }
272 | }, this.props.showTrimmer && React.createElement(Trimmer, {
273 | timeLimit: this.props.timeLimit,
274 | onStartTimeChange: this.handleStartTimeChange,
275 | onEndTimeChange: this.handleEndTimeChange,
276 | widthDurationRatio: this.widthDurationRatio,
277 | containerWidth: this.containerWidth,
278 | startTime: this.state.start || this.props.timeRange.start,
279 | endTime: this.state.end || this.props.timeRange.end,
280 | currentTime: this.props.currentTime,
281 | duration: this.props.duration,
282 | onGetData: this.handleGetTrimData,
283 | onPausePlayer: this.onPausePlayer
284 | }));
285 | }
286 | }, {
287 | key: "widthDurationRatio",
288 | get: function get() {
289 | return this.containerWidth / this.props.duration;
290 | }
291 | }, {
292 | key: "containerWidth",
293 | get: function get() {
294 | return this.containerRef.getBoundingClientRect().width;
295 | }
296 | }]);
297 |
298 | return VideoTrimmer;
299 | }(PureComponent);
300 | export default VideoTrimmer;
--------------------------------------------------------------------------------
/dist/es/index.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
4 |
5 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
6 |
7 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
8 |
9 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
10 |
11 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
12 |
13 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
14 |
15 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
16 |
17 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
18 |
19 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
20 |
21 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
22 |
23 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
24 |
25 | import React from "react";
26 | import FilePicker from "./components/FilePicker";
27 | import Status from "./components/Status";
28 | import Player from "./components/Player";
29 | import Controls from "./components/Controls";
30 | import Trimmer from "./components/Trimmer";
31 | import WebVideo from "./libs/WebVideo";
32 | import webVideoLoader from "./libs/preloadWebVideo";
33 | import Icon from "./components/Icon";
34 | import { noop, arrayBufferToBlob, readBlobURL, download } from "./libs/utils";
35 | import "./style.js";
36 | import PropTypes from "prop-types";
37 |
38 | var ReactVideoTrimmer =
39 | /*#__PURE__*/
40 | function (_React$PureComponent) {
41 | _inherits(ReactVideoTrimmer, _React$PureComponent);
42 |
43 | /**
44 | * @type {WebVideo}
45 | */
46 | function ReactVideoTrimmer(props) {
47 | var _this2 = this;
48 |
49 | var _this;
50 |
51 | _classCallCheck(this, ReactVideoTrimmer);
52 |
53 | _this = _possibleConstructorReturn(this, _getPrototypeOf(ReactVideoTrimmer).call(this, props));
54 |
55 | _defineProperty(_assertThisInitialized(_this), "webVideo", webVideoLoader({}));
56 |
57 | _defineProperty(_assertThisInitialized(_this), "handleFFMPEGStdout", function (msg) {// console.log(msg);
58 | });
59 |
60 | _defineProperty(_assertThisInitialized(_this), "handleFFMPEGReady", function () {
61 | // console.log("FFMPEG is Ready");
62 | _this.setState({
63 | ffmpegReady: true
64 | });
65 | });
66 |
67 | _defineProperty(_assertThisInitialized(_this), "handleFFMPEGFileReceived", function () {// console.log("FFMPEG Received File");
68 | });
69 |
70 | _defineProperty(_assertThisInitialized(_this), "handleFFMPEGDone", function (result) {
71 | _this.setState({
72 | timeRange: {
73 | start: 0,
74 | end: _this.state.timeRange.end
75 | }
76 | });
77 |
78 | var videoBlob = arrayBufferToBlob(result[0].data);
79 | setTimeout(function () {
80 | _this.decodeVideoFile(videoBlob, function () {
81 | var handler = _this.props.onVideoEncode || noop;
82 | handler(result);
83 |
84 | _this.setState({
85 | encoding: false,
86 | encoded: true,
87 | encodedVideo: videoBlob
88 | });
89 | });
90 | }, 300);
91 | });
92 |
93 | _defineProperty(_assertThisInitialized(_this), "defaultState", {
94 | decoding: false,
95 | encoding: false,
96 | encoded: false,
97 | playVideo: false,
98 | videoDataURL: "",
99 | videoFrames: [],
100 | isDecoding: false,
101 | timeRange: {
102 | start: 5,
103 | end: _this.props.timeLimit || 15
104 | },
105 | encodedVideo: null,
106 | playedSeconds: 0,
107 | ffmpegReady: false
108 | });
109 |
110 | _defineProperty(_assertThisInitialized(_this), "state", _this.defaultState);
111 |
112 | _defineProperty(_assertThisInitialized(_this), "updateVideoDataURL", function (dataURL) {
113 | return _this.setState({
114 | videoDataURL: dataURL
115 | });
116 | });
117 |
118 | _defineProperty(_assertThisInitialized(_this), "updateVideoFrames", function (frames) {
119 | return _this.setState({
120 | videoFrames: frames
121 | });
122 | });
123 |
124 | _defineProperty(_assertThisInitialized(_this), "updateIsDecoding", function (state) {
125 | return _this.setState({
126 | updateIsDecoding: state
127 | });
128 | });
129 |
130 | _defineProperty(_assertThisInitialized(_this), "updateVideoDuration", function (duration) {
131 | return _this.setState({
132 | updateVideoDuration: duration
133 | });
134 | });
135 |
136 | _defineProperty(_assertThisInitialized(_this), "decodeVideoFile", function (file) {
137 | var doneCB = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
138 |
139 | _this.setState({
140 | decoding: true
141 | });
142 |
143 | var webVideo = _this.webVideo;
144 | webVideo.videoFile = file;
145 | webVideo.decode(file).then(function (_ref) {
146 | var blob = _ref.blob,
147 | arrayBuffer = _ref.arrayBuffer,
148 | dataURL = _ref.dataURL;
149 |
150 | _this.updateVideoDataURL(dataURL);
151 |
152 | var timeRangeStart = _this.state.timeRange.start;
153 | var duration = _this.webVideo.videoData.duration;
154 | var timeLimit = timeRangeStart + (_this.props.timeLimit || 10);
155 | var timeRangeEnd = duration > timeLimit ? timeLimit : duration;
156 |
157 | _this.setState({
158 | timeRange: {
159 | start: timeRangeStart,
160 | end: timeRangeEnd
161 | },
162 | playedSeconds: (timeRangeEnd - timeRangeStart) / 2 + timeRangeStart
163 | });
164 |
165 | _this.setState({
166 | decoding: false
167 | });
168 |
169 | doneCB();
170 | })["catch"](function (e) {
171 | return console.log(e);
172 | });
173 | });
174 |
175 | _defineProperty(_assertThisInitialized(_this), "handleFileSelected", function (file) {
176 | _this.decodeVideoFile(file);
177 | });
178 |
179 | _defineProperty(_assertThisInitialized(_this), "handleVideoTrim", function (time) {
180 | _this.setState({
181 | timeRange: time
182 | });
183 | });
184 |
185 | _defineProperty(_assertThisInitialized(_this), "handleEncodeVideo", function (timeRange) {
186 | _this.setState({
187 | encoding: true,
188 | videoDataURL: "",
189 | playVideo: false
190 | });
191 |
192 | var timeDifference = timeRange.end - timeRange.start; // console.log(timeRange);
193 |
194 | _this.webVideo.trimVideo(timeRange.start, timeDifference);
195 | });
196 |
197 | _defineProperty(_assertThisInitialized(_this), "handlePlayPauseVideo", function () {
198 | var playVideo = _this.state.playVideo;
199 |
200 | _this.setState({
201 | playVideo: !playVideo
202 | });
203 | });
204 |
205 | _defineProperty(_assertThisInitialized(_this), "handlePlayerPause", function () {
206 | // console.log("pause video");
207 | _this.setState({
208 | playVideo: false
209 | });
210 | });
211 |
212 | _defineProperty(_assertThisInitialized(_this), "handlePlayerPlay", function () {
213 | _this.setState({
214 | playVideo: true
215 | });
216 | });
217 |
218 | _defineProperty(_assertThisInitialized(_this), "handlePlayerProgress", function (seconds) {
219 | if (_this.state.playVideo) {
220 | _this.setState({
221 | playedSeconds: seconds
222 | });
223 | }
224 | });
225 |
226 | _defineProperty(_assertThisInitialized(_this), "handleReselectFile", function () {
227 | _this.setState(_objectSpread({}, _this.defaultState, {
228 | ffmpegReady: true
229 | }));
230 | });
231 |
232 | _defineProperty(_assertThisInitialized(_this), "VideoPlayerWithTrimmer", function (_ref2) {
233 | var showTrimmer = _ref2.showTrimmer;
234 | var _this$state = _this.state,
235 | decoding = _this$state.decoding,
236 | encoding = _this$state.encoding,
237 | encoded = _this$state.encoded,
238 | videoDataURL = _this$state.videoDataURL;
239 | return React.createElement(React.Fragment, null, !decoding && !encoding && videoDataURL && React.createElement(Player, {
240 | src: _this.state.videoDataURL,
241 | timeRange: _this.state.timeRange,
242 | timeLimit: _this.props.timeLimit,
243 | playVideo: _this.state.playVideo,
244 | onPlayerPlay: _this.handlePlayerPlay,
245 | onPlayerPause: _this.handlePlayerPause,
246 | onPlayerProgress: _this.handlePlayerProgress
247 | }), showTrimmer && React.createElement(Trimmer, {
248 | onPausePlayer: _this.handlePlayerPause,
249 | showTrimmer: _this.state.videoDataURL,
250 | duration: _this.webVideo.videoData.duration,
251 | onTrim: _this.handleVideoTrim,
252 | timeLimit: _this.props.timeLimit,
253 | timeRangeLimit: _this.props.timeRange,
254 | timeRange: _this.state.timeRange,
255 | currentTime: _this.state.playedSeconds
256 | }), !decoding && !encoding && videoDataURL && React.createElement(Controls, {
257 | onDownload: function onDownload() {
258 | return _this.handleDownloadVideo(_this.state.encodedVideo);
259 | },
260 | canDownload: encoded,
261 | showEncodeBtn: _this.props.showEncodeBtn,
262 | onReselectFile: _this.handleReselectFile,
263 | onEncode: function onEncode() {
264 | return _this.handleEncodeVideo(_this.state.timeRange);
265 | },
266 | onPlayPauseClick: _this.handlePlayPauseVideo,
267 | processing: encoding,
268 | playing: _this.state.playVideo
269 | }));
270 | });
271 |
272 | _defineProperty(_assertThisInitialized(_this), "handleDownloadVideo", function (encodedVideo) {
273 | var blobURL = readBlobURL(encodedVideo);
274 | download(blobURL, "trimmed.mp4");
275 | });
276 |
277 | _defineProperty(_assertThisInitialized(_this), "VideoPlayerNoTrimmer", function () {
278 | return React.createElement(_this2.VideoPlayerWithTrimmer, null);
279 | });
280 |
281 | _this.webVideo.on("processingFile", function () {
282 | return _this.updateIsDecoding(true);
283 | });
284 |
285 | _this.webVideo.on("processedFile", function () {
286 | return _this.updateIsDecoding(false);
287 | });
288 |
289 | _this.webVideo.on("FFMPEGStdout", _this.handleFFMPEGStdout);
290 |
291 | _this.webVideo.on("FFMPEGReady", _this.handleFFMPEGReady);
292 |
293 | _this.webVideo.on("FFMPEGFileReceived", _this.handleFFMPEGFileReceived);
294 |
295 | _this.webVideo.on("FFMPEGDone", _this.handleFFMPEGDone);
296 |
297 | return _this;
298 | }
299 |
300 | _createClass(ReactVideoTrimmer, [{
301 | key: "render",
302 | value: function render() {
303 | var _this$state2 = this.state,
304 | decoding = _this$state2.decoding,
305 | encoding = _this$state2.encoding,
306 | encoded = _this$state2.encoded,
307 | videoDataURL = _this$state2.videoDataURL,
308 | ffmpegReady = _this$state2.ffmpegReady;
309 | return React.createElement("div", {
310 | className: "rvt-main-container"
311 | }, !ffmpegReady && React.createElement(Status, null, React.createElement(Icon, {
312 | name: "spin",
313 | className: "rvt-icon-spin"
314 | }), this.props.loadingFFMPEGText || "PLEASE WAIT..."), ffmpegReady && encoded && React.createElement(this.VideoPlayerNoTrimmer, null), ffmpegReady && !encoded && React.createElement(React.Fragment, null, !decoding && !encoding && !videoDataURL && React.createElement(FilePicker, {
315 | onFileSelected: this.handleFileSelected,
316 | minSize: this.props.minSize,
317 | maxSize: this.props.maxSize
318 | }), (decoding || encoding) && React.createElement(Status, null, React.createElement(Icon, {
319 | name: "spin",
320 | className: "rvt-icon-spin"
321 | }), encoding ? "ENCODING VIDEO" : "DECODING VIDEO", "..."), React.createElement(this.VideoPlayerWithTrimmer, {
322 | showTrimmer: true
323 | })));
324 | }
325 | }]);
326 |
327 | return ReactVideoTrimmer;
328 | }(React.PureComponent);
329 |
330 | _defineProperty(ReactVideoTrimmer, "propTypes", {
331 | onVideoEncode: PropTypes.func,
332 | showEncodeBtn: PropTypes.bool,
333 | timeLimit: PropTypes.number,
334 | loadingFFMPEGText: PropTypes.string
335 | });
336 |
337 | export var preloadWebVideo = webVideoLoader;
338 | export default ReactVideoTrimmer;
--------------------------------------------------------------------------------
/dist/es/libs/WebVideo.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
4 |
5 | function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
6 |
7 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
8 |
9 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
10 |
11 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
12 |
13 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
14 |
15 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
16 |
17 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
18 |
19 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
20 |
21 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
22 |
23 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
24 |
25 | import { EventEmitter } from "events";
26 | import { readDataURL, arrayBufferToBlob, readArrayBuffer } from "./utils";
27 | import workerClient from "ffmpeg-webworker";
28 | import { fromS } from "./formatSeconds";
29 |
30 | var WebVideo =
31 | /*#__PURE__*/
32 | function (_EventEmitter) {
33 | _inherits(WebVideo, _EventEmitter);
34 |
35 | function WebVideo(videoFile) {
36 | var _this;
37 |
38 | _classCallCheck(this, WebVideo);
39 |
40 | _this = _possibleConstructorReturn(this, _getPrototypeOf(WebVideo).call(this));
41 |
42 | _defineProperty(_assertThisInitialized(_this), "handleDoneClientDone", function (result) {
43 | // console.log(result);
44 | // if (!this.optimizedVideo) {
45 | // this.optimizedVideo = true;
46 | // const converted = arrayBufferToBlob(result[0].data);
47 | // // console.log(converted);
48 | // workerClient.inputFile = converted;
49 | // setTimeout(this.optimizeVideo, 500);
50 | // } else {
51 | var converted = arrayBufferToBlob(result[0].data);
52 |
53 | _this.emit("FFMPEGDone", result); // }
54 |
55 | });
56 |
57 | _defineProperty(_assertThisInitialized(_this), "trimVideo", function () {
58 | var start = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
59 | var length = arguments.length > 1 ? arguments[1] : undefined;
60 | var startSeconds = fromS(start, "hh:mm:ss");
61 | workerClient.runCommand("-ss ".concat(startSeconds, " -c copy -t ").concat(length, " sliced-output.mp4"));
62 | });
63 |
64 | _defineProperty(_assertThisInitialized(_this), "optimizeVideo", function () {
65 | workerClient.runCommand("-strict -2 -vcodec libx264 -crf 23 output.mp4", 253554432);
66 | });
67 |
68 | _defineProperty(_assertThisInitialized(_this), "_videoData", {});
69 |
70 | _defineProperty(_assertThisInitialized(_this), "_videoFile", null);
71 |
72 | _defineProperty(_assertThisInitialized(_this), "optimizedVideo", false);
73 |
74 | _defineProperty(_assertThisInitialized(_this), "_videoBuffer", {});
75 |
76 | _defineProperty(_assertThisInitialized(_this), "readAsArrayBuffer",
77 | /*#__PURE__*/
78 | _asyncToGenerator(
79 | /*#__PURE__*/
80 | regeneratorRuntime.mark(function _callee() {
81 | return regeneratorRuntime.wrap(function _callee$(_context) {
82 | while (1) {
83 | switch (_context.prev = _context.next) {
84 | case 0:
85 | _context.next = 2;
86 | return readArrayBuffer(_this._videoFile);
87 |
88 | case 2:
89 | _this._videoBuffer = _context.sent;
90 | return _context.abrupt("return", _this.videoBuffer);
91 |
92 | case 4:
93 | case "end":
94 | return _context.stop();
95 | }
96 | }
97 | }, _callee);
98 | })));
99 |
100 | _defineProperty(_assertThisInitialized(_this), "convertBufferToBlob", function (buffer) {
101 | var blob = null;
102 | buffer = buffer || _this.videoBuffer;
103 |
104 | if (buffer.byteLength) {
105 | blob = arrayBufferToBlob(buffer);
106 | }
107 |
108 | return blob;
109 | });
110 |
111 | _defineProperty(_assertThisInitialized(_this), "readAsDataURL",
112 | /*#__PURE__*/
113 | function () {
114 | var _ref2 = _asyncToGenerator(
115 | /*#__PURE__*/
116 | regeneratorRuntime.mark(function _callee2(buffer, blob) {
117 | var dataURL;
118 | return regeneratorRuntime.wrap(function _callee2$(_context2) {
119 | while (1) {
120 | switch (_context2.prev = _context2.next) {
121 | case 0:
122 | buffer = buffer || _this.videoBuffer;
123 | blob = blob || _this.convertBufferToBlob(buffer);
124 | dataURL = null;
125 |
126 | if (!blob) {
127 | _context2.next = 7;
128 | break;
129 | }
130 |
131 | _context2.next = 6;
132 | return readDataURL(blob);
133 |
134 | case 6:
135 | dataURL = _context2.sent;
136 |
137 | case 7:
138 | return _context2.abrupt("return", dataURL);
139 |
140 | case 8:
141 | case "end":
142 | return _context2.stop();
143 | }
144 | }
145 | }, _callee2);
146 | }));
147 |
148 | return function (_x, _x2) {
149 | return _ref2.apply(this, arguments);
150 | };
151 | }());
152 |
153 | _defineProperty(_assertThisInitialized(_this), "decode",
154 | /*#__PURE__*/
155 | function () {
156 | var _ref3 = _asyncToGenerator(
157 | /*#__PURE__*/
158 | regeneratorRuntime.mark(function _callee3(file) {
159 | var arrayBuffer, dataURL, videoObjectUrl, video;
160 | return regeneratorRuntime.wrap(function _callee3$(_context3) {
161 | while (1) {
162 | switch (_context3.prev = _context3.next) {
163 | case 0:
164 | _this.videoFile = file;
165 |
166 | _this.emit("processingFile"); // Read File As ArrayBuffer
167 |
168 |
169 | _context3.next = 4;
170 | return _this.readAsArrayBuffer();
171 |
172 | case 4:
173 | arrayBuffer = _context3.sent;
174 | _context3.next = 7;
175 | return _this.readAsDataURL(arrayBuffer);
176 |
177 | case 7:
178 | dataURL = _context3.sent;
179 | videoObjectUrl = URL.createObjectURL(_this.videoFile);
180 | video = document.createElement("video");
181 | video.src = videoObjectUrl;
182 |
183 | case 11:
184 | if (!((video.duration === Infinity || isNaN(video.duration)) && video.readyState < 2)) {
185 | _context3.next = 17;
186 | break;
187 | }
188 |
189 | _context3.next = 14;
190 | return new Promise(function (r) {
191 | return setTimeout(r, 1000);
192 | });
193 |
194 | case 14:
195 | video.currentTime = 10000000 * Math.random();
196 | _context3.next = 11;
197 | break;
198 |
199 | case 17:
200 | _this._videoData = video;
201 |
202 | _this.emit("processedFile");
203 |
204 | return _context3.abrupt("return", {
205 | dataURL: dataURL,
206 | arrayBuffer: arrayBuffer,
207 | blob: _this.convertBufferToBlob()
208 | });
209 |
210 | case 20:
211 | case "end":
212 | return _context3.stop();
213 | }
214 | }
215 | }, _callee3);
216 | }));
217 |
218 | return function (_x3) {
219 | return _ref3.apply(this, arguments);
220 | };
221 | }());
222 |
223 | _defineProperty(_assertThisInitialized(_this), "generateBufferChunks", function () {
224 | var arrayBuffer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
225 | return new Promise(function (resolve, reject) {
226 | try {
227 | var chunks = [];
228 | arrayBuffer = arrayBuffer.byteLength ? arrayBuffer : _this.videoBuffer;
229 | var typedBuffer = new Uint8Array(arrayBuffer);
230 | var microSec = 1000 * 60;
231 | var startChunk = 0;
232 |
233 | for (var i = microSec; i < typedBuffer.byteLength; i += microSec) {
234 | var _buffer = arrayBuffer.slice(startChunk, i);
235 |
236 | chunks.push(_buffer);
237 | startChunk = i;
238 | }
239 |
240 | resolve(chunks);
241 | } catch (e) {
242 | reject(e);
243 | }
244 | });
245 | });
246 |
247 | _defineProperty(_assertThisInitialized(_this), "extractFramesFromVideo", function () {
248 | var fps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 25;
249 | return new Promise(
250 | /*#__PURE__*/
251 | function () {
252 | var _ref4 = _asyncToGenerator(
253 | /*#__PURE__*/
254 | regeneratorRuntime.mark(function _callee5(resolve, reject) {
255 | var video, seekResolve, duration, canvas, context, _ref6, w, h, frames, interval, currentTime, base64ImageData;
256 |
257 | return regeneratorRuntime.wrap(function _callee5$(_context5) {
258 | while (1) {
259 | switch (_context5.prev = _context5.next) {
260 | case 0:
261 | _context5.prev = 0;
262 |
263 | _this.emit("extractingFrames");
264 |
265 | video = _this._videoData;
266 | video.addEventListener("seeked",
267 | /*#__PURE__*/
268 | _asyncToGenerator(
269 | /*#__PURE__*/
270 | regeneratorRuntime.mark(function _callee4() {
271 | return regeneratorRuntime.wrap(function _callee4$(_context4) {
272 | while (1) {
273 | switch (_context4.prev = _context4.next) {
274 | case 0:
275 | if (seekResolve) seekResolve();
276 |
277 | case 1:
278 | case "end":
279 | return _context4.stop();
280 | }
281 | }
282 | }, _callee4);
283 | })));
284 | duration = video.duration;
285 | canvas = document.createElement("canvas");
286 | context = canvas.getContext("2d");
287 | _ref6 = [video.videoWidth, video.videoHeight], w = _ref6[0], h = _ref6[1];
288 | canvas.width = w;
289 | canvas.height = h;
290 | frames = [];
291 | interval = 125 / fps;
292 | currentTime = 0;
293 |
294 | case 13:
295 | if (!(currentTime < duration)) {
296 | _context5.next = 23;
297 | break;
298 | }
299 |
300 | video.currentTime = currentTime;
301 | _context5.next = 17;
302 | return new Promise(function (r) {
303 | return seekResolve = r;
304 | });
305 |
306 | case 17:
307 | context.drawImage(video, 0, 0, w, h);
308 | base64ImageData = canvas.toDataURL();
309 | frames.push(base64ImageData);
310 | currentTime += interval;
311 | _context5.next = 13;
312 | break;
313 |
314 | case 23:
315 | _this.emit("extractedFrames");
316 |
317 | resolve(frames);
318 | _context5.next = 30;
319 | break;
320 |
321 | case 27:
322 | _context5.prev = 27;
323 | _context5.t0 = _context5["catch"](0);
324 | reject(_context5.t0);
325 |
326 | case 30:
327 | case "end":
328 | return _context5.stop();
329 | }
330 | }
331 | }, _callee5, null, [[0, 27]]);
332 | }));
333 |
334 | return function (_x4, _x5) {
335 | return _ref4.apply(this, arguments);
336 | };
337 | }());
338 | });
339 |
340 | _this.videoFile = videoFile;
341 | _this.workerClient = workerClient;
342 | workerClient.on("onReady", function () {
343 | return _this.emit("FFMPEGReady");
344 | });
345 | workerClient.on("onStdout", function (msg) {
346 | return _this.emit("FFMPEGStdout", msg);
347 | });
348 | workerClient.on("onFileReceived", function () {
349 | return _this.emit("FFMPEGFileReceived");
350 | });
351 | workerClient.on("onDone", _this.handleDoneClientDone);
352 | return _this;
353 | }
354 |
355 | _createClass(WebVideo, [{
356 | key: "videoFile",
357 | set: function set(file) {
358 | if (file && file.type) {
359 | workerClient.inputFile = file;
360 | }
361 |
362 | this._videoFile = file;
363 | },
364 | get: function get() {
365 | return this._videoFile;
366 | }
367 | }, {
368 | key: "duration",
369 | get: function get() {
370 | return this._videoData.duration || 0;
371 | }
372 | }, {
373 | key: "videoData",
374 | get: function get() {
375 | return this._videoData;
376 | }
377 | }, {
378 | key: "videoBuffer",
379 | get: function get() {
380 | return this._videoBuffer;
381 | }
382 | }]);
383 |
384 | return WebVideo;
385 | }(EventEmitter);
386 |
387 | export default WebVideo;
--------------------------------------------------------------------------------
/dist/es/libs/formatSeconds.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import zeroFill from "zero-fill";
4 | /**
5 | * Original from https://www.npmjs.com/package/hh-mm-ss
6 | */
7 | // Time units with their corresponding values in miliseconds
8 |
9 | var HOUR = 3600000;
10 | var MINUTE = 60000;
11 | var SECOND = 1000;
12 | var TIME_FORMAT_ERRMSG = "Time format error"; // =============================================================================
13 | // Export functions
14 | // =============================================================================
15 |
16 | export function fromMs(ms) {
17 | var format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "mm:ss";
18 |
19 | if (typeof ms !== "number" || Number.isNaN(ms)) {
20 | throw new Error("NaN error");
21 | }
22 |
23 | var absMs = Math.abs(ms);
24 | var negative = ms < 0;
25 | var hours = Math.floor(absMs / HOUR);
26 | var minutes = Math.floor(absMs % HOUR / MINUTE);
27 | var seconds = Math.floor(absMs % MINUTE / SECOND);
28 | var miliseconds = Math.floor(absMs % SECOND);
29 | return formatTime({
30 | negative: negative,
31 | hours: hours,
32 | minutes: minutes,
33 | seconds: seconds,
34 | miliseconds: miliseconds
35 | }, format);
36 | }
37 | export function fromS(s) {
38 | var format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "mm:ss";
39 |
40 | if (typeof s !== "number" || Number.isNaN(s)) {
41 | throw new Error("NaN error");
42 | }
43 |
44 | var ms = s * SECOND;
45 | return fromMs(ms, format);
46 | }
47 | export function toMs(time) {
48 | var format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "mm:ss";
49 | var re;
50 |
51 | if (["mm:ss", "mm:ss.sss", "hh:mm:ss", "hh:mm:ss.sss"].includes(format)) {
52 | re = /^(-)?(?:(\d\d+):)?(\d\d):(\d\d)(\.\d+)?$/;
53 | } else if (format === "hh:mm") {
54 | re = /^(-)?(\d\d):(\d\d)(?::(\d\d)(?:(\.\d+))?)?$/;
55 | } else {
56 | throw new Error(TIME_FORMAT_ERRMSG);
57 | }
58 |
59 | var result = re.exec(time);
60 | if (!result) throw new Error();
61 | var negative = result[1] === "-";
62 | var hours = result[2] | 0;
63 | var minutes = result[3] | 0;
64 | var seconds = result[4] | 0;
65 | var miliseconds = Math.floor(1000 * result[5] | 0);
66 |
67 | if (minutes > 60 || seconds > 60) {
68 | throw new Error();
69 | }
70 |
71 | return (negative ? -1 : 1) * (hours * HOUR + minutes * MINUTE + seconds * SECOND + miliseconds);
72 | }
73 | export function toS(time) {
74 | var format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "mm:ss";
75 | var ms = toMs(time, format);
76 | return Math.floor(ms / SECOND);
77 | } // =============================================================================
78 | // Utility functions
79 | // =============================================================================
80 |
81 | function formatTime(time, format) {
82 | var showMs;
83 | var showSc;
84 | var showHr;
85 |
86 | switch (format.toLowerCase()) {
87 | case "hh:mm:ss.sss":
88 | showMs = true;
89 | showSc = true;
90 | showHr = true;
91 | break;
92 |
93 | case "hh:mm:ss":
94 | showMs = !!time.miliseconds;
95 | showSc = true;
96 | showHr = true;
97 | break;
98 |
99 | case "hh:mm":
100 | showMs = !!time.miliseconds;
101 | showSc = showMs || !!time.seconds;
102 | showHr = true;
103 | break;
104 |
105 | case "mm:ss":
106 | showMs = !!time.miliseconds;
107 | showSc = true;
108 | showHr = !!time.hours;
109 | break;
110 |
111 | case "mm:ss.sss":
112 | showMs = true;
113 | showSc = true;
114 | showHr = !!time.hours;
115 | break;
116 |
117 | default:
118 | throw new Error(TIME_FORMAT_ERRMSG);
119 | }
120 |
121 | var hh = zeroFill(2, time.hours);
122 | var mm = zeroFill(2, time.minutes);
123 | var ss = zeroFill(2, time.seconds);
124 | var sss = zeroFill(3, time.miliseconds);
125 | return (time.negative ? "-" : "") + (showHr ? showMs ? "".concat(hh, ":").concat(mm, ":").concat(ss, ".").concat(sss) : showSc ? "".concat(hh, ":").concat(mm, ":").concat(ss) : "".concat(hh, ":").concat(mm) : showMs ? "".concat(mm, ":").concat(ss, ".").concat(sss) : "".concat(mm, ":").concat(ss));
126 | }
--------------------------------------------------------------------------------
/dist/es/libs/preloadWebVideo.js:
--------------------------------------------------------------------------------
1 | import WebVideo from "./WebVideo";
2 | export default (function () {
3 | var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
4 | return new WebVideo(opts);
5 | });
--------------------------------------------------------------------------------
/dist/es/libs/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * detect if a file is an video.
3 | * @param {File} file
4 | */
5 | export var isVideo = function isVideo(file) {
6 | return file.type.indexOf("video") > -1;
7 | };
8 | /**
9 | * create range [min .. max]
10 | */
11 |
12 | export var range = function range(min, max) {
13 | return Array.apply(null, {
14 | length: max - min + 1
15 | }).map(function (v, i) {
16 | return i + min;
17 | });
18 | };
19 | /**
20 | * FileReader via promise
21 | * @param {File} file
22 | * @param {string} dataType
23 | * @return {Promise}
24 | */
25 |
26 | export var readFile = function readFile(file) {
27 | var dataType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "ArrayBuffer";
28 | return new Promise(function (resolve, reject) {
29 | var reader = new FileReader();
30 | reader["readAs" + dataType](file);
31 |
32 | reader.onload = function () {
33 | return resolve(reader.result);
34 | };
35 |
36 | reader.onerror = function (err) {
37 | return reject(err);
38 | };
39 | });
40 | };
41 | /**
42 | * Read File/Blob to ArrayBuffer
43 | * @param {File} file
44 | * @return {Promise}
45 | */
46 |
47 | export var readArrayBuffer = function readArrayBuffer(file) {
48 | return readFile(file, "ArrayBuffer");
49 | };
50 | /**
51 | * Read File/Blob to Base64
52 | * @param {File} file
53 | * @return {Promise}
54 | */
55 |
56 | export var readDataURL = function readDataURL(file) {
57 | return readFile(file, "DataURL");
58 | };
59 | export var readBlobURL = function readBlobURL(file) {
60 | return URL.createObjectURL(file);
61 | };
62 | export var download = function download(url, name) {
63 | var link = document.createElement("a");
64 | link.href = url;
65 | link.download = name;
66 | link.click();
67 | };
68 | export var rename = function rename(filename, ext, stamp) {
69 | return "".concat(filename.replace(/\.\w+$/, "")).concat(stamp || "", ".").concat(ext);
70 | };
71 | /**
72 | * format seconds to [minutes, integer, decimal(2)]
73 | * @param {number} seconds
74 | */
75 |
76 | export var formatSeconds = function formatSeconds(seconds) {
77 | return [Math.floor(seconds / 60), Math.floor(seconds % 60), Math.round(seconds % 1 * 100)];
78 | };
79 | export var leftZero = function leftZero(num, count) {
80 | return ("000000" + num).slice(-count);
81 | };
82 | export var noop = function noop() {};
83 | export var arrayBufferToBlob = function arrayBufferToBlob(buffer) {
84 | return new Blob([new Uint8Array(buffer, 0, buffer.byteLength)], {
85 | type: "video/webm",
86 | name: "video.webm"
87 | });
88 | };
--------------------------------------------------------------------------------
/dist/es/style.js:
--------------------------------------------------------------------------------
1 | import "./styles/main-container.scss";
2 | import "./styles/controls.scss";
3 | import "./styles/dragger.scss";
4 | import "./styles/file-picker.scss";
5 | import "./styles/icon.scss";
6 | import "./styles/player.scss";
7 | import "./styles/status.scss";
8 | import "./styles/trimmer.scss";
--------------------------------------------------------------------------------
/dist/es/styles/controls.scss:
--------------------------------------------------------------------------------
1 | .btn-reset {
2 | padding: 0;
3 | border: 0;
4 | outline: 0;
5 | background: none;
6 | }
7 |
8 | .rvt-controls-cont {
9 | margin-top: 10px;
10 | text-align: center;
11 | .seconds {
12 | font-size: 12px;
13 | line-height: 36px;
14 | margin-left: 10px;
15 | display: inline-block;
16 | overflow: hidden;
17 | color: #aaa;
18 | }
19 | }
20 |
21 | @keyframes spin {
22 | from {
23 | transform: rotate(0);
24 | }
25 |
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | }
30 |
31 | .rvt-icon-spin {
32 | animation: spin infinite 1s linear;
33 | }
34 |
35 | .rvt-controller-item,
36 | .rvt-file-picker-control {
37 | @extend .btn-reset;
38 |
39 | display: inline-block;
40 | font-size: 16px;
41 | text-align: center;
42 | color: #999;
43 | padding: 10px;
44 | margin-right: 10px;
45 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
46 | cursor: pointer;
47 |
48 | &:hover {
49 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.16);
50 | }
51 |
52 | .rvt-icon {
53 | display: block;
54 | }
55 | }
56 |
57 | .rvt-controller-dropdown {
58 | display: inline-block;
59 | position: relative;
60 |
61 | .rvt-controller-list-wrap {
62 | position: relative;
63 | }
64 |
65 | .rvt-controller-list {
66 | position: absolute;
67 | width: 60px;
68 | top: 0;
69 | left: 0;
70 | visibility: hidden;
71 | opacity: 0;
72 | transition-durvtion: 0.3s;
73 | transition-property: opacity, visibility;
74 | list-style: none;
75 | background: #fff;
76 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
77 | padding: 0;
78 | margin: 0;
79 | z-index: 10;
80 |
81 | a {
82 | @extend .btn-reset;
83 |
84 | display: block;
85 | width: 100%;
86 | text-align: center;
87 | padding: 5px 0;
88 | color: inherit;
89 | font-size: 12px;
90 | cursor: pointer;
91 |
92 | &:hover {
93 | background: #333;
94 | color: #fff;
95 | }
96 | }
97 | }
98 |
99 | &:hover {
100 | .rvt-controller-list {
101 | opacity: 1;
102 | visibility: visible;
103 | }
104 | }
105 | }
106 |
107 | .rvt-controller-clipper {
108 | position: absolute;
109 | width: 100%;
110 | height: 100%;
111 | }
112 |
--------------------------------------------------------------------------------
/dist/es/styles/dragger.scss:
--------------------------------------------------------------------------------
1 | .rvt-dragger {
2 | position: absolute;
3 | top: 0;
4 | bottom: 0;
5 | width: 1px;
6 | background: #999;
7 | cursor: col-resize;
8 |
9 | &::after {
10 | content: "";
11 | position: absolute;
12 | left: -2px;
13 | right: -2px;
14 | top: 0;
15 | bottom: 0;
16 | }
17 |
18 | &:hover {
19 | background: #333;
20 | }
21 | }
22 |
23 | .rvt-drag-current {
24 | background: #038c7f;
25 | }
26 |
--------------------------------------------------------------------------------
/dist/es/styles/file-picker.scss:
--------------------------------------------------------------------------------
1 | .rvt-file-picker {
2 | width: 100%;
3 | text-align: center;
4 | padding: 50px;
5 | border: 1px solid #038c7f;
6 | background-color: #ccfdf856;
7 | cursor: pointer;
8 | color: #038c7f;
9 | & input {
10 | visibility: hidden;
11 | position: absolute;
12 | right: 0;
13 | left: 0;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/dist/es/styles/icon.scss:
--------------------------------------------------------------------------------
1 | .rvt-icon {
2 | width: 1em;
3 | height: 1em;
4 | fill: #038c7f;
5 | vertical-align: middle;
6 | }
7 |
--------------------------------------------------------------------------------
/dist/es/styles/main-container.scss:
--------------------------------------------------------------------------------
1 | .btn-reset {
2 | padding: 0;
3 | border: 0;
4 | outline: 0;
5 | background: none;
6 | }
7 |
8 | .rvt-main-container {
9 | > * {
10 | box-sizing: border-box;
11 | }
12 | .rvt-icon-spin {
13 | animation: spin infinite 1s linear;
14 | }
15 | }
16 | @keyframes spin {
17 | from {
18 | transform: rotate(0);
19 | }
20 |
21 | to {
22 | transform: rotate(360deg);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/dist/es/styles/player.scss:
--------------------------------------------------------------------------------
1 | .rvt-player-cont {
2 | position: relative;
3 | // &::before {
4 | // -webkit-user-drag: none;
5 | // -khtml-user-drag: none;
6 | // -moz-user-drag: none;
7 | // -o-user-drag: none;
8 | // user-drag: none;
9 |
10 | // -webkit-touch-callout: none; /* iOS Safari */
11 | // -webkit-user-select: none; /* Chrome/Safari/Opera */
12 | // -khtml-user-select: none; /* Konqueror */
13 | // -moz-user-select: none; /* Firefox */
14 | // -ms-user-select: none; /* Internet Explorer/Edge*/
15 | // user-select: none; /* Non-prefixed version, currently
16 | // not supported by any browser */
17 |
18 | // content: "";
19 | // width: 100%;
20 | // height: 100%;
21 | // position: absolute;
22 | // left: 0;
23 | // top: 0;
24 | // }
25 | & .rvt-player-time-range-cont {
26 | margin: 5px;
27 | text-align: center;
28 | & .rvt-player-time-range {
29 | color: rgb(143, 143, 143);
30 | font-size: 14px;
31 | padding: 5px;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/dist/es/styles/status.scss:
--------------------------------------------------------------------------------
1 | .rvt-status {
2 | width: 100%;
3 | text-align: center;
4 | padding: 50px;
5 | background-color: #ccfdf856;
6 | cursor: pointer;
7 | font-size: 20px;
8 | color: #038c7f;
9 | & input {
10 | visibility: hidden;
11 | position: absolute;
12 | right: 0;
13 | left: 0;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/dist/es/styles/trimmer.scss:
--------------------------------------------------------------------------------
1 | .rvt-trimmer-cont {
2 | display: block;
3 | width: 100%;
4 | height: 15px;
5 | position: relative;
6 | margin-bottom: 20px;
7 | margin-top: 30px;
8 | background-color: #ccfdf856;
9 |
10 | > * {
11 | box-sizing: border-box;
12 | }
13 | & .rvt-thumb {
14 | position: relative;
15 | display: inline-block;
16 | }
17 | }
18 | .rvt-trimmer {
19 | background: #038c7f;
20 | position: absolute;
21 | top: 0;
22 | right: 0;
23 | width: 100%;
24 | height: 100%;
25 | z-index: 1200;
26 | }
27 |
28 | // cursorColor = #0cf;
29 | .rvt-player-cursor-current {
30 | position: absolute;
31 | font-size: 12px;
32 | top: -22px;
33 | padding: 1px 3px;
34 | text-align: center;
35 | color: #fff;
36 | transform: translate(-50%) scale(0.8);
37 | background: #038c7f;
38 |
39 | .rvt-player-num {
40 | font-family: monospace;
41 | }
42 |
43 | &::after {
44 | content: "";
45 | position: absolute;
46 | border: 5px solid transparent;
47 | border-top-color: #038c7f;
48 | bottom: -9px;
49 | left: 50%;
50 | margin-left: -5px;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/dist/style.css:
--------------------------------------------------------------------------------
1 | .btn-reset, .rvt-controller-item,
2 | .rvt-file-picker-control, .rvt-controller-dropdown .rvt-controller-list a {
3 | padding: 0;
4 | border: 0;
5 | outline: 0;
6 | background: none; }
7 |
8 | .rvt-main-container > * {
9 | box-sizing: border-box; }
10 |
11 | .rvt-main-container .rvt-icon-spin {
12 | animation: spin infinite 1s linear; }
13 |
14 | @keyframes spin {
15 | from {
16 | transform: rotate(0); }
17 | to {
18 | transform: rotate(360deg); } }
19 |
20 | .btn-reset, .rvt-controller-item,
21 | .rvt-file-picker-control, .rvt-controller-dropdown .rvt-controller-list a {
22 | padding: 0;
23 | border: 0;
24 | outline: 0;
25 | background: none; }
26 |
27 | .rvt-controls-cont {
28 | margin-top: 10px;
29 | text-align: center; }
30 | .rvt-controls-cont .seconds {
31 | font-size: 12px;
32 | line-height: 36px;
33 | margin-left: 10px;
34 | display: inline-block;
35 | overflow: hidden;
36 | color: #aaa; }
37 |
38 | @keyframes spin {
39 | from {
40 | transform: rotate(0); }
41 | to {
42 | transform: rotate(360deg); } }
43 |
44 | .rvt-icon-spin {
45 | animation: spin infinite 1s linear; }
46 |
47 | .rvt-controller-item,
48 | .rvt-file-picker-control {
49 | display: inline-block;
50 | font-size: 16px;
51 | text-align: center;
52 | color: #999;
53 | padding: 10px;
54 | margin-right: 10px;
55 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
56 | cursor: pointer; }
57 | .rvt-controller-item:hover,
58 | .rvt-file-picker-control:hover {
59 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.16); }
60 | .rvt-controller-item .rvt-icon,
61 | .rvt-file-picker-control .rvt-icon {
62 | display: block; }
63 |
64 | .rvt-controller-dropdown {
65 | display: inline-block;
66 | position: relative; }
67 | .rvt-controller-dropdown .rvt-controller-list-wrap {
68 | position: relative; }
69 | .rvt-controller-dropdown .rvt-controller-list {
70 | position: absolute;
71 | width: 60px;
72 | top: 0;
73 | left: 0;
74 | visibility: hidden;
75 | opacity: 0;
76 | transition-durvtion: 0.3s;
77 | transition-property: opacity, visibility;
78 | list-style: none;
79 | background: #fff;
80 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
81 | padding: 0;
82 | margin: 0;
83 | z-index: 10; }
84 | .rvt-controller-dropdown .rvt-controller-list a {
85 | display: block;
86 | width: 100%;
87 | text-align: center;
88 | padding: 5px 0;
89 | color: inherit;
90 | font-size: 12px;
91 | cursor: pointer; }
92 | .rvt-controller-dropdown .rvt-controller-list a:hover {
93 | background: #333;
94 | color: #fff; }
95 | .rvt-controller-dropdown:hover .rvt-controller-list {
96 | opacity: 1;
97 | visibility: visible; }
98 |
99 | .rvt-controller-clipper {
100 | position: absolute;
101 | width: 100%;
102 | height: 100%; }
103 |
104 | .rvt-dragger {
105 | position: absolute;
106 | top: 0;
107 | bottom: 0;
108 | width: 1px;
109 | background: #999;
110 | cursor: col-resize; }
111 | .rvt-dragger::after {
112 | content: "";
113 | position: absolute;
114 | left: -2px;
115 | right: -2px;
116 | top: 0;
117 | bottom: 0; }
118 | .rvt-dragger:hover {
119 | background: #333; }
120 |
121 | .rvt-drag-current {
122 | background: #038c7f; }
123 |
124 | .rvt-icon {
125 | width: 1em;
126 | height: 1em;
127 | fill: #038c7f;
128 | vertical-align: middle; }
129 |
130 | .rvt-file-picker {
131 | width: 100%;
132 | text-align: center;
133 | padding: 50px;
134 | border: 1px solid #038c7f;
135 | background-color: #ccfdf856;
136 | cursor: pointer;
137 | color: #038c7f; }
138 | .rvt-file-picker input {
139 | visibility: hidden;
140 | position: absolute;
141 | right: 0;
142 | left: 0; }
143 |
144 | .rvt-player-cont {
145 | position: relative; }
146 | .rvt-player-cont .rvt-player-time-range-cont {
147 | margin: 5px;
148 | text-align: center; }
149 | .rvt-player-cont .rvt-player-time-range-cont .rvt-player-time-range {
150 | color: #8f8f8f;
151 | font-size: 14px;
152 | padding: 5px; }
153 |
154 | .rvt-status {
155 | width: 100%;
156 | text-align: center;
157 | padding: 50px;
158 | background-color: #ccfdf856;
159 | cursor: pointer;
160 | font-size: 20px;
161 | color: #038c7f; }
162 | .rvt-status input {
163 | visibility: hidden;
164 | position: absolute;
165 | right: 0;
166 | left: 0; }
167 |
168 | .rvt-trimmer-cont {
169 | display: block;
170 | width: 100%;
171 | height: 15px;
172 | position: relative;
173 | margin-bottom: 20px;
174 | margin-top: 30px;
175 | background-color: #ccfdf856; }
176 | .rvt-trimmer-cont > * {
177 | box-sizing: border-box; }
178 | .rvt-trimmer-cont .rvt-thumb {
179 | position: relative;
180 | display: inline-block; }
181 |
182 | .rvt-trimmer {
183 | background: #038c7f;
184 | position: absolute;
185 | top: 0;
186 | right: 0;
187 | width: 100%;
188 | height: 100%;
189 | z-index: 1200; }
190 |
191 | .rvt-player-cursor-current {
192 | position: absolute;
193 | font-size: 12px;
194 | top: -22px;
195 | padding: 1px 3px;
196 | text-align: center;
197 | color: #fff;
198 | transform: translate(-50%) scale(0.8);
199 | background: #038c7f; }
200 | .rvt-player-cursor-current .rvt-player-num {
201 | font-family: monospace; }
202 | .rvt-player-cursor-current::after {
203 | content: "";
204 | position: absolute;
205 | border: 5px solid transparent;
206 | border-top-color: #038c7f;
207 | bottom: -9px;
208 | left: 50%;
209 | margin-left: -5px; }
210 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | react-video-trimmer
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/BASE.MD:
--------------------------------------------------------------------------------
1 | # react-video-trimmer
2 |
3 | Amazing React component for manipulating video length
4 |
5 | ```js
6 | import ReactVideoTrimmer, { preloadWebVideo } from "react-video-trimmer";
7 | // Redundant, ReactVideoTrimmer is in scope, only call when it is not in scope and you require preloading of ffmpeg.js
8 | preloadWebVideo({});
9 |
10 |
11 |
17 |
;
18 | ```
19 |
--------------------------------------------------------------------------------
/examples/theme.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | font-family: sans-serif;
5 | }
6 |
7 | .container > p {
8 | font-size: 1rem;
9 | }
10 |
11 | .container > em {
12 | font-size: 0.8rem;
13 | }
14 |
15 | .dropzone {
16 | flex: 1;
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | padding: 20px;
21 | border-width: 2px;
22 | border-radius: 2px;
23 | border-color: #eeeeee;
24 | border-style: dashed;
25 | background-color: #fafafa;
26 | color: #bdbdbd;
27 | outline: none;
28 | transition: border 0.24s ease-in-out;
29 | }
30 |
31 | .dropzone:focus {
32 | border-color: #2196f3;
33 | }
34 |
35 | .dropzone.disabled {
36 | opacity: 0.6;
37 | }
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-video-trimmer",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "files": [
7 | "dist/"
8 | ],
9 | "scripts": {
10 | "clean": "rimraf ./dist",
11 | "test": "cross-env NODE_ENV=test npm run eslint:src && jest --coverage && npm run typescript",
12 | "build": "npm run clean && npm run build:umd && npm run build:es",
13 | "build:umd": "cross-env NODE_ENV=es rollup -c",
14 | "build:es": "cross-env BABEL_ENV=es babel ./src --out-dir ./dist/es --ignore '**/*.spec.js'",
15 | "start": "styleguidist server",
16 | "styleguide": "styleguidist build",
17 | "test:watch": "cross-env NODE_ENV=test jest --watch",
18 | "eslint:src": "eslint .",
19 | "commitmsg": "commitlint -e",
20 | "precommit": "pretty-quick --staged",
21 | "prepublish": "npm run build",
22 | "logo": "cd logo && sketchtool export artboards logo.sketch",
23 | "imagemin": "imagemin --out-dir=logo --plugin=pngquant --plugin=svgo",
24 | "size": "size-limit",
25 | "size:why": "size-limit --why",
26 | "typescript": "tsc --project ./typings/tests"
27 | },
28 | "size-limit": [
29 | {
30 | "path": "dist/index.js",
31 | "limit": "7 KB"
32 | },
33 | {
34 | "path": "dist/es/index.js",
35 | "limit": "7 KB"
36 | }
37 | ],
38 | "keywords": [
39 | "react-component",
40 | "video",
41 | "trim",
42 | "ffmpeg",
43 | "react"
44 | ],
45 | "author": "",
46 | "license": "ISC",
47 | "peerDependencies": {
48 | "react": ">= 16.8"
49 | },
50 | "dependencies": {
51 | "attr-accept": "^1.1.3",
52 | "eslint-config-airbnb": "^17.1.1",
53 | "ffmpeg-webworker": "^1.3.0",
54 | "ffmpeg.js": "^3.1.9001",
55 | "file-selector": "^0.1.11",
56 | "hh-mm-ss": "^1.2.0",
57 | "prop-types": "^15.7.2",
58 | "react-dropzone": "^10.1.9",
59 | "react-player": "^1.11.1",
60 | "webpack-blocks": "^2.0.1",
61 | "webworker-file": "^0.2.0",
62 | "zero-fill": "^2.2.3"
63 | },
64 | "devDependencies": {
65 | "@babel/cli": "^7.5.5",
66 | "@babel/core": "^7.5.5",
67 | "@babel/plugin-proposal-class-properties": "^7.5.5",
68 | "@babel/plugin-proposal-do-expressions": "^7.5.0",
69 | "@babel/plugin-proposal-export-default-from": "^7.5.2",
70 | "@babel/plugin-proposal-logical-assignment-operators": "^7.2.0",
71 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
72 | "@babel/plugin-proposal-optional-chaining": "^7.2.0",
73 | "@babel/plugin-proposal-pipeline-operator": "^7.5.0",
74 | "@babel/plugin-transform-runtime": "^7.5.5",
75 | "@babel/preset-env": "^7.5.5",
76 | "@babel/preset-react": "^7.0.0",
77 | "@babel/register": "^7.0.0",
78 | "@commitlint/cli": "^7.0.0",
79 | "@commitlint/config-angular": "^7.0.1",
80 | "@commitlint/prompt": "^7.0.0",
81 | "@commitlint/prompt-cli": "^7.0.0",
82 | "@types/react": "^16.8.3",
83 | "@types/react-dom": "^16.8.1",
84 | "babel-plugin-add-module-exports": "^1.0.0",
85 | "babel-plugin-dynamic-import-node": "^2.2.0",
86 | "babel-polyfill": "^6.26.0",
87 | "commitizen": "^2.10.1",
88 | "cross-env": "^5.2.0",
89 | "husky": "^0.14.3",
90 | "imagemin-cli": "^3.0.0",
91 | "imagemin-pngquant": "^6.0.0",
92 | "lint-staged": "^7.2.2",
93 | "markdownlint-cli": "^0.13.0",
94 | "prettier": "*",
95 | "pretty-quick": "^1.11.1",
96 | "react": "^16.8.2",
97 | "react-dom": "^16.8.2",
98 | "react-styleguidist": "^9.0.1",
99 | "rimraf": "^2.5.2",
100 | "rollup": "^1.17.0",
101 | "rollup-plugin-babel": "^4.3.3",
102 | "rollup-plugin-commonjs": "^10.0.1",
103 | "rollup-plugin-copy": "^3.0.0",
104 | "rollup-plugin-node-builtins": "^2.1.2",
105 | "rollup-plugin-node-globals": "^1.4.0",
106 | "rollup-plugin-node-resolve": "^5.2.0",
107 | "rollup-plugin-scss": "^1.0.1",
108 | "rollup-plugin-uglify": "^6.0.2",
109 | "sinon": "^3.2.1",
110 | "size-limit": "^0.19.2",
111 | "style-loader": "^0.18.2",
112 | "styled-components": "^4.1.2",
113 | "tslint": "^5.9.1",
114 | "typescript": "^3.2.4",
115 | "webpack": "^4.29.5"
116 | },
117 | "repository": {
118 | "type": "git",
119 | "url": "https://github.com/limistah/react-video-trimmer.git"
120 | },
121 | "config": {
122 | "commitizen": {
123 | "path": "@commitlint/prompt"
124 | }
125 | },
126 | "husky": {
127 | "hooks": {
128 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook"
129 | }
130 | },
131 | "engines": {
132 | "node": ">= 8"
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from "rollup-plugin-babel";
2 | import resolve from "rollup-plugin-node-resolve";
3 | import commonjs from "rollup-plugin-commonjs";
4 | import { uglify } from "rollup-plugin-uglify";
5 | import scss from "rollup-plugin-scss";
6 | import builtins from "rollup-plugin-node-builtins";
7 | import globals from "rollup-plugin-node-globals";
8 | import copy from "rollup-plugin-copy";
9 |
10 | const umdGlobals = {
11 | react: "React",
12 | "prop-types": "PropTypes",
13 | Blob: "Blob",
14 | URL: "URL",
15 | Worker: "Worker",
16 | FileReader: "FileReader",
17 | Uint8Array: "Uint8Array"
18 | };
19 |
20 | export default {
21 | input: "./src/index.js",
22 | output: {
23 | file: "dist/index.js",
24 | format: "umd",
25 | name: "reactVideoTrimmer",
26 | globals: umdGlobals,
27 | sourcemap: "inline",
28 | exports: "named"
29 | },
30 | external: Object.keys(umdGlobals),
31 | plugins: [
32 | babel({
33 | exclude: "node_modules/**"
34 | }),
35 | resolve({ preferBuiltins: true }),
36 | globals(),
37 | builtins(),
38 | commonjs(),
39 | scss({
40 | output: "./dist/style.css",
41 | failOnError: true
42 | }),
43 | copy({
44 | targets: [{ src: "src/styles/*", dest: "dist/es/styles" }]
45 | }),
46 | uglify()
47 | ]
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/Controls.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "./Icon";
3 |
4 | const Controls = ({
5 | onPlayPauseClick,
6 | playing,
7 | onReselectFile,
8 | processing,
9 | onEncode,
10 | showEncodeBtn,
11 | canDownload,
12 | onDownload
13 | }) => {
14 | return (
15 |
45 | );
46 | };
47 |
48 | export default Controls;
49 |
--------------------------------------------------------------------------------
/src/components/Dragger.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 |
3 | import PropTypes from "prop-types";
4 |
5 | export default class Dragger extends PureComponent {
6 | _screenX = null;
7 | _screenY = null;
8 | _ox = null;
9 | _oy = null;
10 |
11 | handleMouseDown = e => {
12 | this._screenX = e.screenX;
13 | this._screenY = e.screenY;
14 | this._ox = this.props.x;
15 | this._oy = this.props.y;
16 |
17 | window.addEventListener("mousemove", this.handleMouseMove, false);
18 | window.addEventListener("mouseup", this.handleMouseUp, false);
19 | };
20 |
21 | handleMouseMove = e => {
22 | this.props.onDrag({
23 | x: e.screenX - this._screenX + this._ox,
24 | y: e.screenY - this._screenY + this._oy
25 | });
26 | };
27 |
28 | handleMouseUp = () => {
29 | window.removeEventListener("mousemove", this.handleMouseMove);
30 | window.removeEventListener("mouseup", this.handleMouseUp);
31 | const handler = this.props.onDragStop || (() => {});
32 | handler();
33 | };
34 |
35 | render() {
36 | return (
37 |
45 | {this.props.children}
46 |
47 | );
48 | }
49 |
50 | static defaultProps = {
51 | onDrag() {},
52 | x: 0,
53 | y: 0
54 | };
55 |
56 | static propTypes = {
57 | x: PropTypes.number,
58 | y: PropTypes.number,
59 | onDrag: PropTypes.func,
60 | className: PropTypes.string,
61 | children: PropTypes.element
62 | };
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/FilePicker.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useRef } from "react";
2 | import { isVideo, noop } from "../libs/utils";
3 | import Icon from "./Icon";
4 | import Dropzone from "react-dropzone";
5 |
6 | function FilePicker(props) {
7 | const onDrop = useCallback(acceptedFiles => {
8 | if (acceptedFiles.length) {
9 | const video = acceptedFiles[0];
10 | const handler = props.onFileSelected || noop;
11 | handler(video);
12 | }
13 | }, []);
14 | const MAX_SIZE = props.maxSize || 10000024;
15 | const MIN_SIZE = props.minSize || 0;
16 | // const handleFileChange = useCallback(e => {
17 | // if (e.target.files.length) {
18 | // const video = e.target.files[0];
19 | // if (isVideo(video)) {
20 | // const handler = props.onFileSelected || noop;
21 | // handler(video);
22 | // } else {
23 | // return alert("Unsupported File Type");
24 | // }
25 | // }
26 | // });
27 | const toMB = byte => Math.round(byte / 1000000);
28 | return (
29 |
35 | {({ getRootProps, getInputProps, isDragActive }) => (
36 |
37 |
38 |
39 | {isDragActive ? (
40 |
Drop the video here ...
41 | ) : (
42 | <>
43 |
Drag 'n' drop a video here, or click to select one
44 |
45 |
46 | ({toMB(MIN_SIZE)} - {toMB(MAX_SIZE)}MB)
47 |
48 |
49 | >
50 | )}
51 |
52 | )}
53 |
54 | );
55 | }
56 |
57 | export default FilePicker;
58 |
--------------------------------------------------------------------------------
/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const Download = ({ className }) => (
5 |
13 | );
14 |
15 | const Music = ({ className }) => (
16 |
24 | );
25 |
26 | const Play = ({ className }) => (
27 |
37 | );
38 |
39 | const Pause = ({ className }) => (
40 |
50 | );
51 |
52 | const Replay = ({ className }) => (
53 |
61 | );
62 |
63 | const Spin = ({ className }) => (
64 |
71 | );
72 |
73 | const Icon = props => {
74 | let El = Download;
75 | switch (props.name) {
76 | case "music":
77 | El = Music;
78 | break;
79 | case "play":
80 | El = Play;
81 | break;
82 | case "pause":
83 | El = Pause;
84 | break;
85 | case "replay":
86 | El = Replay;
87 | break;
88 | case "spin":
89 | El = Spin;
90 | break;
91 | default:
92 | El = Download;
93 | break;
94 | }
95 | return (
96 |
99 | );
100 | };
101 |
102 | Icon.propTypes = {
103 | name: PropTypes.string
104 | };
105 |
106 | export default Icon;
107 |
--------------------------------------------------------------------------------
/src/components/Player.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactPlayer from "react-player";
3 | import { formatSeconds, noop, leftZero } from "../libs/utils";
4 |
5 | class Player extends React.Component {
6 | state = {
7 | playing: this.props.playVideo || false
8 | };
9 | componentWillReceiveProps(newProps) {
10 | const newTimeRange = newProps.timeRange;
11 | const oldTimeRange = this.props.timeRange;
12 | const canSeek =
13 | (oldTimeRange && newTimeRange.start !== oldTimeRange.start) ||
14 | (!oldTimeRange && newTimeRange.start > 0);
15 | if (canSeek) {
16 | this.setState({ playing: false });
17 | this.player.seekTo(newTimeRange.start, "seconds");
18 | }
19 | if (newProps.playVideo !== this.props.playVideo) {
20 | this.setState({ playing: newProps.playVideo });
21 | }
22 | }
23 | handlePlayerProgress = data => {
24 | if (data.loaded) {
25 | const { playedSeconds } = data;
26 | const startTimeRange = this.props.timeRange.start;
27 | const endTimeRange = this.props.timeRange.end;
28 | const playedSecondsIsLowerThanStartTime = playedSeconds <= startTimeRange;
29 | const playedSecondsIsGreaterThanEndTime = playedSeconds >= endTimeRange;
30 | if (playedSecondsIsLowerThanStartTime) {
31 | this.player.seekTo(startTimeRange, "seconds");
32 | }
33 | if (playedSecondsIsGreaterThanEndTime) {
34 | this.player.seekTo(startTimeRange, "seconds");
35 | // this.setState({ playing: false });
36 | }
37 | const handler = this.props.onPlayerProgress || noop;
38 | handler(playedSeconds);
39 | }
40 | };
41 | displaySeconds(seconds) {
42 | return seconds.toFixed(2) + "s";
43 | }
44 | handleOnPause = () => {
45 | const handler = this.props.onPlayerPause || noop;
46 | handler();
47 | };
48 | handleOnPlay = () => {
49 | const handler = this.props.onPlayerPlay || noop;
50 | handler();
51 | };
52 | render() {
53 | const { start, end } = this.props.timeRange;
54 | return (
55 | {}}>
56 | {/*
*/}
57 |
(this.player = el)}
63 | playing={this.state.playing}
64 | style={{
65 | margin: "0 auto"
66 | }}
67 | />
68 |
69 |
70 | From: {this.displaySeconds(start)}
71 |
72 |
73 | To: {this.displaySeconds(end)}
74 |
75 |
76 | Selected {this.displaySeconds(end - start)} of{" "}
77 | {this.displaySeconds(this.props.timeLimit)} allowed
78 |
79 |
80 |
81 | );
82 | }
83 | }
84 |
85 | export default Player;
86 |
--------------------------------------------------------------------------------
/src/components/Status.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Status = ({ children }) => {
4 | return {children}
;
5 | };
6 |
7 | export default Status;
8 |
--------------------------------------------------------------------------------
/src/components/Trimmer.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import Dragger from "./Dragger";
3 | import { noop, formatSeconds, leftZero } from "../libs/utils";
4 |
5 | const TrimmerOverLay = props => {
6 | return (
7 |
11 | );
12 | };
13 |
14 | const TimeStamp = props => {
15 | const formated = formatSeconds(props.time);
16 | return (
17 |
18 | {formated[0]}'
19 | {formated[1]}
20 | {!props.noMicroSeconds && (
21 | <>
22 | .{leftZero(formated[2], 2)}
23 | >
24 | )}
25 |
26 | );
27 | };
28 |
29 | class Trimmer extends PureComponent {
30 | pos2Time = pos => {
31 | return pos / this.props.widthDurationRatio;
32 | };
33 |
34 | time2pos = time => {
35 | return time * this.props.widthDurationRatio;
36 | };
37 |
38 | keepInRange = x => {
39 | const containerWidth = this.props.containerWidth;
40 | if (x < 0) {
41 | return 0;
42 | }
43 |
44 | if (x > containerWidth) {
45 | return containerWidth;
46 | }
47 |
48 | return x;
49 | };
50 |
51 | withinTimeLimit = (time, isDragEnd = true) => {
52 | const timeLimit = this.props.timeLimit;
53 |
54 | let startTime = this.props.startTime;
55 | let endTime = time;
56 |
57 | if (!isDragEnd) {
58 | startTime = time;
59 | endTime = this.props.endTime;
60 | }
61 |
62 | const duration = this.props.duration;
63 | let timeTillEnd = duration - endTime;
64 |
65 | const currentRange = duration - startTime - timeTillEnd;
66 | return timeLimit ? currentRange <= timeLimit : true;
67 | };
68 |
69 | withinTimeRange = (time, isDragEnd = true) => {
70 | const timeRange = this.props.timeRangeLimit;
71 | let interval = time - this.props.startTime;
72 | if (!isDragEnd) {
73 | interval = this.props.endTime - time;
74 | }
75 | return timeRange ? interval >= timeRange : true;
76 | };
77 |
78 | handleDragStart = pos => {
79 | const pos2Time = this.pos2Time(this.keepInRange(pos.x));
80 | let time = pos2Time;
81 |
82 | const currentTime = this.props.currentTime;
83 | const currentTimeIsWithinRange = this.withinTimeRange(time, false);
84 | const currentTimeIsWithinLimit = this.withinTimeLimit(time, false);
85 |
86 | if (
87 | time >= currentTime ||
88 | !currentTimeIsWithinRange ||
89 | !currentTimeIsWithinLimit
90 | ) {
91 | time = this.props.startTime;
92 | const handler = this.props.onPausePlayer || (() => {});
93 | handler();
94 | }
95 | this.props.onStartTimeChange(time);
96 | };
97 | handleDragEnd = pos => {
98 | const pos2Time = this.pos2Time(this.keepInRange(pos.x));
99 | let time = pos2Time;
100 |
101 | const endTime = this.props.endTime;
102 | const currentTime = this.props.currentTime;
103 |
104 | const currentTimeIsWithinRange = this.withinTimeRange(time);
105 | const currentTimeIsWithinLimit = this.withinTimeLimit(time);
106 |
107 | if (
108 | currentTime >= time ||
109 | !currentTimeIsWithinRange ||
110 | !currentTimeIsWithinLimit
111 | ) {
112 | time = this.props.endTime;
113 | const handler = this.props.onPausePlayer || (() => {});
114 | handler();
115 | }
116 |
117 | this.props.onEndTimeChange(time);
118 | };
119 | handleDragStop = () => {
120 | const handler = this.props.onGetData || noop;
121 | handler({ start: this.props.startTime, end: this.props.endTime });
122 | };
123 | getTrimmerWidth = width => {
124 | return this.props.containerWidth - width;
125 | };
126 | render() {
127 | const start = this.time2pos(this.props.startTime);
128 | const end = this.time2pos(this.props.endTime);
129 | const current = this.time2pos(this.props.currentTime);
130 | return (
131 |
132 |
133 |
138 |
139 |
140 | {}} onDragStop={() => {}}>
141 |
142 |
143 |
148 |
149 |
150 |
151 |
152 | );
153 | }
154 | }
155 |
156 | export class VideoTrimmer extends PureComponent {
157 | state = {
158 | start: 0,
159 | end: 0
160 | };
161 | get widthDurationRatio() {
162 | return this.containerWidth / this.props.duration;
163 | }
164 | get containerWidth() {
165 | return this.containerRef.getBoundingClientRect().width;
166 | }
167 |
168 | handleStartTimeChange = time => {
169 | this.setState({ start: time });
170 | };
171 | handleGetTrimData = () => {
172 | const trimmerHandler = this.props.onTrim || noop;
173 | setTimeout(
174 | () =>
175 | trimmerHandler({
176 | start: this.state.start || this.props.timeRange.start,
177 | end: this.state.end || this.props.timeRange.end
178 | }),
179 | 200
180 | );
181 | };
182 | handleEndTimeChange = time => {
183 | this.setState({ end: time });
184 | };
185 |
186 | render() {
187 | return (
188 | (this.containerRef = e)}>
189 | {this.props.showTrimmer && (
190 |
203 | )}
204 |
205 | );
206 | }
207 | }
208 |
209 | export default VideoTrimmer;
210 |
--------------------------------------------------------------------------------
/src/icons/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/music.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/pause.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/icons/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/replay.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/spin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import FilePicker from "./components/FilePicker";
3 | import Status from "./components/Status";
4 | import Player from "./components/Player";
5 | import Controls from "./components/Controls";
6 | import Trimmer from "./components/Trimmer";
7 | import WebVideo from "./libs/WebVideo";
8 | import webVideoLoader from "./libs/preloadWebVideo";
9 | import Icon from "./components/Icon";
10 | import { noop, arrayBufferToBlob, readBlobURL, download } from "./libs/utils";
11 | import "./style.js";
12 | import PropTypes from "prop-types";
13 |
14 | class ReactVideoTrimmer extends React.PureComponent {
15 | /**
16 | * @type {WebVideo}
17 | */
18 | webVideo = webVideoLoader({});
19 |
20 | static propTypes = {
21 | onVideoEncode: PropTypes.func,
22 | showEncodeBtn: PropTypes.bool,
23 | timeLimit: PropTypes.number,
24 | loadingFFMPEGText: PropTypes.string
25 | };
26 |
27 | constructor(props) {
28 | super(props);
29 | this.webVideo.on("processingFile", () => this.updateIsDecoding(true));
30 | this.webVideo.on("processedFile", () => this.updateIsDecoding(false));
31 |
32 | this.webVideo.on("FFMPEGStdout", this.handleFFMPEGStdout);
33 | this.webVideo.on("FFMPEGReady", this.handleFFMPEGReady);
34 | this.webVideo.on("FFMPEGFileReceived", this.handleFFMPEGFileReceived);
35 | this.webVideo.on("FFMPEGDone", this.handleFFMPEGDone);
36 | }
37 |
38 | handleFFMPEGStdout = msg => {
39 | // console.log(msg);
40 | };
41 |
42 | handleFFMPEGReady = () => {
43 | // console.log("FFMPEG is Ready");
44 | this.setState({ ffmpegReady: true });
45 | };
46 |
47 | handleFFMPEGFileReceived = () => {
48 | // console.log("FFMPEG Received File");
49 | };
50 |
51 | handleFFMPEGDone = result => {
52 | this.setState({
53 | timeRange: { start: 0, end: this.state.timeRange.end }
54 | });
55 | const videoBlob = arrayBufferToBlob(result[0].data);
56 | setTimeout(() => {
57 | this.decodeVideoFile(videoBlob, () => {
58 | const handler = this.props.onVideoEncode || noop;
59 | handler(result);
60 | this.setState({
61 | encoding: false,
62 | encoded: true,
63 | encodedVideo: videoBlob
64 | });
65 | });
66 | }, 300);
67 | };
68 |
69 | defaultState = {
70 | decoding: false,
71 | encoding: false,
72 | encoded: false,
73 | playVideo: false,
74 | videoDataURL: "",
75 | videoFrames: [],
76 | isDecoding: false,
77 | timeRange: { start: 5, end: this.props.timeLimit || 15 },
78 | encodedVideo: null,
79 | playedSeconds: 0,
80 | ffmpegReady: false
81 | };
82 |
83 | state = this.defaultState;
84 |
85 | updateVideoDataURL = dataURL => this.setState({ videoDataURL: dataURL });
86 |
87 | updateVideoFrames = frames => this.setState({ videoFrames: frames });
88 |
89 | updateIsDecoding = state => this.setState({ updateIsDecoding: state });
90 | updateVideoDuration = duration =>
91 | this.setState({ updateVideoDuration: duration });
92 |
93 | decodeVideoFile = (file, doneCB = noop) => {
94 | this.setState({ decoding: true });
95 | const webVideo = this.webVideo;
96 | webVideo.videoFile = file;
97 | webVideo
98 | .decode(file)
99 | .then(({ blob, arrayBuffer, dataURL }) => {
100 | this.updateVideoDataURL(dataURL);
101 | const timeRangeStart = this.state.timeRange.start;
102 | const duration = this.webVideo.videoData.duration;
103 | const timeLimit = timeRangeStart + (this.props.timeLimit || 10);
104 | const timeRangeEnd = duration > timeLimit ? timeLimit : duration;
105 | this.setState({
106 | timeRange: { start: timeRangeStart, end: timeRangeEnd },
107 | playedSeconds: (timeRangeEnd - timeRangeStart) / 2 + timeRangeStart
108 | });
109 | this.setState({ decoding: false });
110 | doneCB();
111 | })
112 | .catch(e => console.log(e));
113 | };
114 | handleFileSelected = file => {
115 | this.decodeVideoFile(file);
116 | };
117 |
118 | handleVideoTrim = time => {
119 | this.setState({ timeRange: time });
120 | };
121 | handleEncodeVideo = timeRange => {
122 | this.setState({ encoding: true, videoDataURL: "", playVideo: false });
123 | const timeDifference = timeRange.end - timeRange.start;
124 | // console.log(timeRange);
125 | this.webVideo.trimVideo(timeRange.start, timeDifference);
126 | };
127 | handlePlayPauseVideo = () => {
128 | const { playVideo } = this.state;
129 | this.setState({ playVideo: !playVideo });
130 | };
131 | handlePlayerPause = () => {
132 | // console.log("pause video");
133 | this.setState({ playVideo: false });
134 | };
135 | handlePlayerPlay = () => {
136 | this.setState({ playVideo: true });
137 | };
138 | handlePlayerProgress = seconds => {
139 | if (this.state.playVideo) {
140 | this.setState({ playedSeconds: seconds });
141 | }
142 | };
143 | handleReselectFile = () => {
144 | this.setState({
145 | ...this.defaultState,
146 | ffmpegReady: true
147 | });
148 | };
149 | VideoPlayerWithTrimmer = ({ showTrimmer }) => {
150 | const { decoding, encoding, encoded, videoDataURL } = this.state;
151 | return (
152 | <>
153 | {!decoding && !encoding && videoDataURL && (
154 |
163 | )}
164 | {showTrimmer && (
165 |
175 | )}
176 |
177 | {!decoding && !encoding && videoDataURL && (
178 | this.handleDownloadVideo(this.state.encodedVideo)}
180 | canDownload={encoded}
181 | showEncodeBtn={this.props.showEncodeBtn}
182 | onReselectFile={this.handleReselectFile}
183 | onEncode={() => this.handleEncodeVideo(this.state.timeRange)}
184 | onPlayPauseClick={this.handlePlayPauseVideo}
185 | processing={encoding}
186 | playing={this.state.playVideo}
187 | />
188 | )}
189 | >
190 | );
191 | };
192 | handleDownloadVideo = encodedVideo => {
193 | const blobURL = readBlobURL(encodedVideo);
194 | download(blobURL, "trimmed.mp4");
195 | };
196 | VideoPlayerNoTrimmer = () => {
197 | return ;
198 | };
199 | render() {
200 | const {
201 | decoding,
202 | encoding,
203 | encoded,
204 | videoDataURL,
205 | ffmpegReady
206 | } = this.state;
207 | return (
208 |
209 | {!ffmpegReady && (
210 |
211 |
212 | {this.props.loadingFFMPEGText || "PLEASE WAIT..."}
213 |
214 | )}
215 | {ffmpegReady && encoded && }
216 | {ffmpegReady && !encoded && (
217 | <>
218 | {!decoding && !encoding && !videoDataURL && (
219 |
224 | )}
225 | {(decoding || encoding) && (
226 |
227 |
228 | {encoding ? "ENCODING VIDEO" : "DECODING VIDEO"}...
229 |
230 | )}
231 |
232 | >
233 | )}
234 |
235 | );
236 | }
237 | }
238 |
239 | export const preloadWebVideo = webVideoLoader;
240 |
241 | export default ReactVideoTrimmer;
242 |
--------------------------------------------------------------------------------
/src/libs/WebVideo.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from "events";
2 | import { readDataURL, arrayBufferToBlob, readArrayBuffer } from "./utils";
3 | import workerClient from "ffmpeg-webworker";
4 | import { fromS } from "./formatSeconds";
5 |
6 | class WebVideo extends EventEmitter {
7 | constructor(videoFile) {
8 | super();
9 | this.videoFile = videoFile;
10 | this.workerClient = workerClient;
11 |
12 | workerClient.on("onReady", () => this.emit("FFMPEGReady"));
13 | workerClient.on("onStdout", msg => this.emit("FFMPEGStdout", msg));
14 | workerClient.on("onFileReceived", () => this.emit("FFMPEGFileReceived"));
15 | workerClient.on("onDone", this.handleDoneClientDone);
16 | }
17 | handleDoneClientDone = result => {
18 | // console.log(result);
19 | // if (!this.optimizedVideo) {
20 | // this.optimizedVideo = true;
21 | // const converted = arrayBufferToBlob(result[0].data);
22 | // // console.log(converted);
23 | // workerClient.inputFile = converted;
24 | // setTimeout(this.optimizeVideo, 500);
25 | // } else {
26 | const converted = arrayBufferToBlob(result[0].data);
27 | this.emit("FFMPEGDone", result);
28 | // }
29 | };
30 | trimVideo = (start = 0, length) => {
31 | const startSeconds = fromS(start, "hh:mm:ss");
32 | workerClient.runCommand(
33 | `-ss ${startSeconds} -c copy -t ${length} sliced-output.mp4`
34 | );
35 | };
36 |
37 | optimizeVideo = () => {
38 | workerClient.runCommand(
39 | `-strict -2 -vcodec libx264 -crf 23 output.mp4`,
40 | 253554432
41 | );
42 | };
43 | _videoData = {};
44 | _videoFile = null;
45 | optimizedVideo = false;
46 | /**
47 | * @type {ArrayBuffer}
48 | */
49 | _videoBuffer = {};
50 |
51 | readAsArrayBuffer = async () => {
52 | this._videoBuffer = await readArrayBuffer(this._videoFile);
53 | return this.videoBuffer;
54 | };
55 |
56 | /**
57 | * @returns {Blob}
58 | * @returns {String}
59 | */
60 | convertBufferToBlob = buffer => {
61 | let blob = null;
62 | buffer = buffer || this.videoBuffer;
63 | if (buffer.byteLength) {
64 | blob = arrayBufferToBlob(buffer);
65 | }
66 | return blob;
67 | };
68 |
69 | /**
70 | * @returns {File}
71 | */
72 | readAsDataURL = async (buffer, blob) => {
73 | buffer = buffer || this.videoBuffer;
74 | blob = blob || this.convertBufferToBlob(buffer);
75 | let dataURL = null;
76 | if (blob) {
77 | dataURL = await readDataURL(blob);
78 | }
79 | return dataURL;
80 | };
81 |
82 | set videoFile(file) {
83 | if (file && file.type) {
84 | workerClient.inputFile = file;
85 | }
86 | this._videoFile = file;
87 | }
88 |
89 | get videoFile() {
90 | return this._videoFile;
91 | }
92 |
93 | get duration() {
94 | return this._videoData.duration || 0;
95 | }
96 |
97 | get videoData() {
98 | return this._videoData;
99 | }
100 | get videoBuffer() {
101 | return this._videoBuffer;
102 | }
103 |
104 | decode = async file => {
105 | this.videoFile = file;
106 | this.emit("processingFile");
107 | // Read File As ArrayBuffer
108 | const arrayBuffer = await this.readAsArrayBuffer();
109 | // convert to dataURL
110 | const dataURL = await this.readAsDataURL(arrayBuffer);
111 |
112 | let videoObjectUrl = URL.createObjectURL(this.videoFile);
113 | let video = document.createElement("video");
114 | video.src = videoObjectUrl;
115 | while (
116 | (video.duration === Infinity || isNaN(video.duration)) &&
117 | video.readyState < 2
118 | ) {
119 | await new Promise(r => setTimeout(r, 1000));
120 | video.currentTime = 10000000 * Math.random();
121 | }
122 | this._videoData = video;
123 | this.emit("processedFile");
124 | return { dataURL, arrayBuffer, blob: this.convertBufferToBlob() };
125 | };
126 |
127 | generateBufferChunks = (arrayBuffer = []) => {
128 | return new Promise((resolve, reject) => {
129 | try {
130 | let chunks = [];
131 | arrayBuffer = arrayBuffer.byteLength ? arrayBuffer : this.videoBuffer;
132 | const typedBuffer = new Uint8Array(arrayBuffer);
133 | const microSec = 1000 * 60;
134 | let startChunk = 0;
135 | for (let i = microSec; i < typedBuffer.byteLength; i += microSec) {
136 | const _buffer = arrayBuffer.slice(startChunk, i);
137 | chunks.push(_buffer);
138 | startChunk = i;
139 | }
140 | resolve(chunks);
141 | } catch (e) {
142 | reject(e);
143 | }
144 | });
145 | };
146 | extractFramesFromVideo = (fps = 25) => {
147 | return new Promise(async (resolve, reject) => {
148 | try {
149 | this.emit("extractingFrames");
150 | let video = this._videoData;
151 | let seekResolve;
152 | video.addEventListener("seeked", async function() {
153 | if (seekResolve) seekResolve();
154 | });
155 | let duration = video.duration;
156 |
157 | let canvas = document.createElement("canvas");
158 | let context = canvas.getContext("2d");
159 | let [w, h] = [video.videoWidth, video.videoHeight];
160 | canvas.width = w;
161 | canvas.height = h;
162 | let frames = [];
163 | let interval = 125 / fps;
164 | let currentTime = 0;
165 |
166 | while (currentTime < duration) {
167 | video.currentTime = currentTime;
168 | await new Promise(r => (seekResolve = r));
169 |
170 | context.drawImage(video, 0, 0, w, h);
171 | let base64ImageData = canvas.toDataURL();
172 | frames.push(base64ImageData);
173 |
174 | currentTime += interval;
175 | }
176 | this.emit("extractedFrames");
177 | resolve(frames);
178 | } catch (e) {
179 | reject(e);
180 | }
181 | });
182 | };
183 | }
184 |
185 | export default WebVideo;
186 |
--------------------------------------------------------------------------------
/src/libs/formatSeconds.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | import zeroFill from "zero-fill";
3 |
4 | /**
5 | * Original from https://www.npmjs.com/package/hh-mm-ss
6 | */
7 |
8 | // Time units with their corresponding values in miliseconds
9 | const HOUR = 3600000;
10 | const MINUTE = 60000;
11 | const SECOND = 1000;
12 |
13 | const TIME_FORMAT_ERRMSG = "Time format error";
14 |
15 | // =============================================================================
16 | // Export functions
17 | // =============================================================================
18 |
19 | export function fromMs(ms, format = "mm:ss") {
20 | if (typeof ms !== "number" || Number.isNaN(ms)) {
21 | throw new Error("NaN error");
22 | }
23 |
24 | let absMs = Math.abs(ms);
25 |
26 | let negative = ms < 0;
27 | let hours = Math.floor(absMs / HOUR);
28 | let minutes = Math.floor((absMs % HOUR) / MINUTE);
29 | let seconds = Math.floor((absMs % MINUTE) / SECOND);
30 | let miliseconds = Math.floor(absMs % SECOND);
31 |
32 | return formatTime(
33 | {
34 | negative,
35 | hours,
36 | minutes,
37 | seconds,
38 | miliseconds
39 | },
40 | format
41 | );
42 | }
43 |
44 | export function fromS(s, format = "mm:ss") {
45 | if (typeof s !== "number" || Number.isNaN(s)) {
46 | throw new Error("NaN error");
47 | }
48 |
49 | let ms = s * SECOND;
50 |
51 | return fromMs(ms, format);
52 | }
53 |
54 | export function toMs(time, format = "mm:ss") {
55 | let re;
56 |
57 | if (["mm:ss", "mm:ss.sss", "hh:mm:ss", "hh:mm:ss.sss"].includes(format)) {
58 | re = /^(-)?(?:(\d\d+):)?(\d\d):(\d\d)(\.\d+)?$/;
59 | } else if (format === "hh:mm") {
60 | re = /^(-)?(\d\d):(\d\d)(?::(\d\d)(?:(\.\d+))?)?$/;
61 | } else {
62 | throw new Error(TIME_FORMAT_ERRMSG);
63 | }
64 |
65 | let result = re.exec(time);
66 | if (!result) throw new Error();
67 |
68 | let negative = result[1] === "-";
69 | let hours = result[2] | 0;
70 | let minutes = result[3] | 0;
71 | let seconds = result[4] | 0;
72 | let miliseconds = Math.floor((1000 * result[5]) | 0);
73 |
74 | if (minutes > 60 || seconds > 60) {
75 | throw new Error();
76 | }
77 |
78 | return (
79 | (negative ? -1 : 1) *
80 | (hours * HOUR + minutes * MINUTE + seconds * SECOND + miliseconds)
81 | );
82 | }
83 |
84 | export function toS(time, format = "mm:ss") {
85 | let ms = toMs(time, format);
86 | return Math.floor(ms / SECOND);
87 | }
88 |
89 | // =============================================================================
90 | // Utility functions
91 | // =============================================================================
92 |
93 | function formatTime(time, format) {
94 | let showMs;
95 | let showSc;
96 | let showHr;
97 |
98 | switch (format.toLowerCase()) {
99 | case "hh:mm:ss.sss":
100 | showMs = true;
101 | showSc = true;
102 | showHr = true;
103 | break;
104 | case "hh:mm:ss":
105 | showMs = !!time.miliseconds;
106 | showSc = true;
107 | showHr = true;
108 | break;
109 | case "hh:mm":
110 | showMs = !!time.miliseconds;
111 | showSc = showMs || !!time.seconds;
112 | showHr = true;
113 | break;
114 | case "mm:ss":
115 | showMs = !!time.miliseconds;
116 | showSc = true;
117 | showHr = !!time.hours;
118 | break;
119 | case "mm:ss.sss":
120 | showMs = true;
121 | showSc = true;
122 | showHr = !!time.hours;
123 | break;
124 | default:
125 | throw new Error(TIME_FORMAT_ERRMSG);
126 | }
127 |
128 | let hh = zeroFill(2, time.hours);
129 | let mm = zeroFill(2, time.minutes);
130 | let ss = zeroFill(2, time.seconds);
131 | let sss = zeroFill(3, time.miliseconds);
132 |
133 | return (
134 | (time.negative ? "-" : "") +
135 | (showHr
136 | ? showMs
137 | ? `${hh}:${mm}:${ss}.${sss}`
138 | : showSc
139 | ? `${hh}:${mm}:${ss}`
140 | : `${hh}:${mm}`
141 | : showMs
142 | ? `${mm}:${ss}.${sss}`
143 | : `${mm}:${ss}`)
144 | );
145 | }
146 |
--------------------------------------------------------------------------------
/src/libs/preloadWebVideo.js:
--------------------------------------------------------------------------------
1 | import WebVideo from "./WebVideo";
2 |
3 | export default (opts = {}) => new WebVideo(opts);
4 |
--------------------------------------------------------------------------------
/src/libs/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * detect if a file is an video.
3 | * @param {File} file
4 | */
5 | export const isVideo = file => file.type.indexOf("video") > -1;
6 |
7 | /**
8 | * create range [min .. max]
9 | */
10 | export const range = (min, max) =>
11 | Array.apply(null, { length: max - min + 1 }).map((v, i) => i + min);
12 |
13 | /**
14 | * FileReader via promise
15 | * @param {File} file
16 | * @param {string} dataType
17 | * @return {Promise}
18 | */
19 | export const readFile = (file, dataType = "ArrayBuffer") =>
20 | new Promise((resolve, reject) => {
21 | const reader = new FileReader();
22 | reader["readAs" + dataType](file);
23 | reader.onload = () => resolve(reader.result);
24 | reader.onerror = err => reject(err);
25 | });
26 |
27 | /**
28 | * Read File/Blob to ArrayBuffer
29 | * @param {File} file
30 | * @return {Promise}
31 | */
32 | export const readArrayBuffer = file => readFile(file, "ArrayBuffer");
33 |
34 | /**
35 | * Read File/Blob to Base64
36 | * @param {File} file
37 | * @return {Promise}
38 | */
39 | export const readDataURL = file => readFile(file, "DataURL");
40 |
41 | export const readBlobURL = file => URL.createObjectURL(file);
42 |
43 | export const download = (url, name) => {
44 | const link = document.createElement("a");
45 | link.href = url;
46 | link.download = name;
47 | link.click();
48 | };
49 |
50 | export const rename = (filename, ext, stamp) =>
51 | `${filename.replace(/\.\w+$/, "")}${stamp || ""}.${ext}`;
52 |
53 | /**
54 | * format seconds to [minutes, integer, decimal(2)]
55 | * @param {number} seconds
56 | */
57 | export const formatSeconds = seconds => [
58 | Math.floor(seconds / 60),
59 | Math.floor(seconds % 60),
60 | Math.round((seconds % 1) * 100)
61 | ];
62 |
63 | export const leftZero = (num, count) => {
64 | return ("000000" + num).slice(-count);
65 | };
66 |
67 | export const noop = () => {};
68 |
69 | export const arrayBufferToBlob = buffer =>
70 | new Blob([new Uint8Array(buffer, 0, buffer.byteLength)], {
71 | type: "video/webm",
72 | name: "video.webm"
73 | });
74 |
--------------------------------------------------------------------------------
/src/style.js:
--------------------------------------------------------------------------------
1 | import "./styles/main-container.scss";
2 |
3 | import "./styles/controls.scss";
4 |
5 | import "./styles/dragger.scss";
6 |
7 | import "./styles/file-picker.scss";
8 |
9 | import "./styles/icon.scss";
10 |
11 | import "./styles/player.scss";
12 |
13 | import "./styles/status.scss";
14 |
15 | import "./styles/trimmer.scss";
16 |
--------------------------------------------------------------------------------
/src/styles/controls.scss:
--------------------------------------------------------------------------------
1 | .btn-reset {
2 | padding: 0;
3 | border: 0;
4 | outline: 0;
5 | background: none;
6 | }
7 |
8 | .rvt-controls-cont {
9 | margin-top: 10px;
10 | text-align: center;
11 | .seconds {
12 | font-size: 12px;
13 | line-height: 36px;
14 | margin-left: 10px;
15 | display: inline-block;
16 | overflow: hidden;
17 | color: #aaa;
18 | }
19 | }
20 |
21 | @keyframes spin {
22 | from {
23 | transform: rotate(0);
24 | }
25 |
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | }
30 |
31 | .rvt-icon-spin {
32 | animation: spin infinite 1s linear;
33 | }
34 |
35 | .rvt-controller-item,
36 | .rvt-file-picker-control {
37 | @extend .btn-reset;
38 |
39 | display: inline-block;
40 | font-size: 16px;
41 | text-align: center;
42 | color: #999;
43 | padding: 10px;
44 | margin-right: 10px;
45 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
46 | cursor: pointer;
47 |
48 | &:hover {
49 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.16);
50 | }
51 |
52 | .rvt-icon {
53 | display: block;
54 | }
55 | }
56 |
57 | .rvt-controller-dropdown {
58 | display: inline-block;
59 | position: relative;
60 |
61 | .rvt-controller-list-wrap {
62 | position: relative;
63 | }
64 |
65 | .rvt-controller-list {
66 | position: absolute;
67 | width: 60px;
68 | top: 0;
69 | left: 0;
70 | visibility: hidden;
71 | opacity: 0;
72 | transition-durvtion: 0.3s;
73 | transition-property: opacity, visibility;
74 | list-style: none;
75 | background: #fff;
76 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
77 | padding: 0;
78 | margin: 0;
79 | z-index: 10;
80 |
81 | a {
82 | @extend .btn-reset;
83 |
84 | display: block;
85 | width: 100%;
86 | text-align: center;
87 | padding: 5px 0;
88 | color: inherit;
89 | font-size: 12px;
90 | cursor: pointer;
91 |
92 | &:hover {
93 | background: #333;
94 | color: #fff;
95 | }
96 | }
97 | }
98 |
99 | &:hover {
100 | .rvt-controller-list {
101 | opacity: 1;
102 | visibility: visible;
103 | }
104 | }
105 | }
106 |
107 | .rvt-controller-clipper {
108 | position: absolute;
109 | width: 100%;
110 | height: 100%;
111 | }
112 |
--------------------------------------------------------------------------------
/src/styles/dragger.scss:
--------------------------------------------------------------------------------
1 | .rvt-dragger {
2 | position: absolute;
3 | top: 0;
4 | bottom: 0;
5 | width: 1px;
6 | background: #999;
7 | cursor: col-resize;
8 |
9 | &::after {
10 | content: "";
11 | position: absolute;
12 | left: -2px;
13 | right: -2px;
14 | top: 0;
15 | bottom: 0;
16 | }
17 |
18 | &:hover {
19 | background: #333;
20 | }
21 | }
22 |
23 | .rvt-drag-current {
24 | background: #038c7f;
25 | }
26 |
--------------------------------------------------------------------------------
/src/styles/file-picker.scss:
--------------------------------------------------------------------------------
1 | .rvt-file-picker {
2 | width: 100%;
3 | text-align: center;
4 | padding: 50px;
5 | border: 1px solid #038c7f;
6 | background-color: #ccfdf856;
7 | cursor: pointer;
8 | color: #038c7f;
9 | & input {
10 | visibility: hidden;
11 | position: absolute;
12 | right: 0;
13 | left: 0;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/styles/icon.scss:
--------------------------------------------------------------------------------
1 | .rvt-icon {
2 | width: 1em;
3 | height: 1em;
4 | fill: #038c7f;
5 | vertical-align: middle;
6 | }
7 |
--------------------------------------------------------------------------------
/src/styles/main-container.scss:
--------------------------------------------------------------------------------
1 | .btn-reset {
2 | padding: 0;
3 | border: 0;
4 | outline: 0;
5 | background: none;
6 | }
7 |
8 | .rvt-main-container {
9 | > * {
10 | box-sizing: border-box;
11 | }
12 | .rvt-icon-spin {
13 | animation: spin infinite 1s linear;
14 | }
15 | }
16 | @keyframes spin {
17 | from {
18 | transform: rotate(0);
19 | }
20 |
21 | to {
22 | transform: rotate(360deg);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/styles/player.scss:
--------------------------------------------------------------------------------
1 | .rvt-player-cont {
2 | position: relative;
3 | // &::before {
4 | // -webkit-user-drag: none;
5 | // -khtml-user-drag: none;
6 | // -moz-user-drag: none;
7 | // -o-user-drag: none;
8 | // user-drag: none;
9 |
10 | // -webkit-touch-callout: none; /* iOS Safari */
11 | // -webkit-user-select: none; /* Chrome/Safari/Opera */
12 | // -khtml-user-select: none; /* Konqueror */
13 | // -moz-user-select: none; /* Firefox */
14 | // -ms-user-select: none; /* Internet Explorer/Edge*/
15 | // user-select: none; /* Non-prefixed version, currently
16 | // not supported by any browser */
17 |
18 | // content: "";
19 | // width: 100%;
20 | // height: 100%;
21 | // position: absolute;
22 | // left: 0;
23 | // top: 0;
24 | // }
25 | & .rvt-player-time-range-cont {
26 | margin: 5px;
27 | text-align: center;
28 | & .rvt-player-time-range {
29 | color: rgb(143, 143, 143);
30 | font-size: 14px;
31 | padding: 5px;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/styles/status.scss:
--------------------------------------------------------------------------------
1 | .rvt-status {
2 | width: 100%;
3 | text-align: center;
4 | padding: 50px;
5 | background-color: #ccfdf856;
6 | cursor: pointer;
7 | font-size: 20px;
8 | color: #038c7f;
9 | & input {
10 | visibility: hidden;
11 | position: absolute;
12 | right: 0;
13 | left: 0;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/styles/trimmer.scss:
--------------------------------------------------------------------------------
1 | .rvt-trimmer-cont {
2 | display: block;
3 | width: 100%;
4 | height: 15px;
5 | position: relative;
6 | margin-bottom: 20px;
7 | margin-top: 30px;
8 | background-color: #ccfdf856;
9 |
10 | > * {
11 | box-sizing: border-box;
12 | }
13 | & .rvt-thumb {
14 | position: relative;
15 | display: inline-block;
16 | }
17 | }
18 | .rvt-trimmer {
19 | background: #038c7f;
20 | position: absolute;
21 | top: 0;
22 | right: 0;
23 | width: 100%;
24 | height: 100%;
25 | z-index: 1200;
26 | }
27 |
28 | // cursorColor = #0cf;
29 | .rvt-player-cursor-current {
30 | position: absolute;
31 | font-size: 12px;
32 | top: -22px;
33 | padding: 1px 3px;
34 | text-align: center;
35 | color: #fff;
36 | transform: translate(-50%) scale(0.8);
37 | background: #038c7f;
38 |
39 | .rvt-player-num {
40 | font-family: monospace;
41 | }
42 |
43 | &::after {
44 | content: "";
45 | position: absolute;
46 | border: 5px solid transparent;
47 | border-top-color: #038c7f;
48 | bottom: -9px;
49 | left: 50%;
50 | margin-left: -5px;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const { createConfig, babel, match, sass, css } = require("webpack-blocks");
3 |
4 | module.exports = {
5 | title: "react-video-trimmer",
6 | styleguideDir: path.join(__dirname, "docs"),
7 | webpackConfig: createConfig([
8 | babel(),
9 | match(["*.scss", "*.css", "!*node_modules*"], [css(), sass()])
10 | ]),
11 | exampleMode: "expand",
12 | usageMode: "expand",
13 | showSidebar: false,
14 | serverPort: 8080,
15 | moduleAliases: {
16 | "react-video-trimmer": path.resolve(__dirname, "./src")
17 | },
18 | require: [
19 | path.resolve(__dirname, "styleguide/setup.js"),
20 | path.join(__dirname, "examples/theme.css")
21 | ],
22 | sections: [
23 | {
24 | name: "",
25 | content: path.resolve(__dirname, "examples/BASE.MD")
26 | }
27 | ]
28 | };
29 |
--------------------------------------------------------------------------------
/styleguide/setup.js:
--------------------------------------------------------------------------------
1 | import "babel-polyfill";
2 |
--------------------------------------------------------------------------------
/testSetup.js:
--------------------------------------------------------------------------------
1 | const jest = require('jest');
2 | // https://www.npmjs.com/package/jest-dom
3 | require('jest-dom/extend-expect');
4 |
5 | // TODO: Ignore warnings about act(), it refers to having async side effects updating the state;
6 | // This happens because our async getFilesFromEvent() fn
7 | // See https://github.com/kentcdodds/react-testing-library/issues/281,
8 | // https://github.com/facebook/react/issues/14769
9 | jest.spyOn(console, 'error').mockImplementation(() => jest.fn());
10 |
--------------------------------------------------------------------------------