@onesy/ui-react 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,9 @@ export interface IAudioRecorder extends ILine {
16
16
  IconStart?: IElementReference;
17
17
  IconPause?: IElementReference;
18
18
  IconStop?: IElementReference;
19
- onConfirm?: (value: Blob) => any;
19
+ onConfirm?: (value: Blob, meta: {
20
+ duration: number;
21
+ }) => any;
20
22
  onData?: (value: Blob) => any;
21
23
  onStart?: (event: React.MouseEvent<any>) => any;
22
24
  onPause?: (event: React.MouseEvent<any>) => any;
@@ -86,11 +86,14 @@ const AudioRecorder = react_1.default.forwardRef((props_, ref) => {
86
86
  root: react_1.default.useRef(null),
87
87
  mediaRecorder: react_1.default.useRef(null),
88
88
  mediaRecorderBytes: react_1.default.useRef([]),
89
- start: react_1.default.useRef(0),
89
+ startedAt: react_1.default.useRef(0),
90
90
  valuePaused: react_1.default.useRef(0),
91
91
  value: react_1.default.useRef(null),
92
92
  animationFrame: react_1.default.useRef(null),
93
- onData: react_1.default.useRef(null)
93
+ onData: react_1.default.useRef(null),
94
+ // fallback to duration calculation on desktop
95
+ // ie. for mobile where we can't easily determine duration
96
+ duration: react_1.default.useRef(0)
94
97
  };
95
98
  refs.onData.current = onData;
96
99
  const supported = (0, utils_1.isEnvironment)('browser') && ((_a = window.navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia);
@@ -105,7 +108,7 @@ const AudioRecorder = react_1.default.forwardRef((props_, ref) => {
105
108
  };
106
109
  }, []);
107
110
  const update = () => {
108
- setValue(refs.valuePaused.current + (date_1.OnesyDate.milliseconds - refs.start.current));
111
+ setValue(refs.valuePaused.current + (date_1.OnesyDate.milliseconds - refs.startedAt.current));
109
112
  refs.animationFrame.current = requestAnimationFrame(update);
110
113
  };
111
114
  const onStart = react_1.default.useCallback(async (event) => {
@@ -136,7 +139,10 @@ const AudioRecorder = react_1.default.forwardRef((props_, ref) => {
136
139
  onError(error);
137
140
  return;
138
141
  }
139
- refs.start.current = date_1.OnesyDate.milliseconds;
142
+ // reset duration
143
+ refs.duration.current = 0;
144
+ // started at milliseconds
145
+ refs.startedAt.current = date_1.OnesyDate.milliseconds;
140
146
  // ~60+ fps
141
147
  refs.animationFrame.current = requestAnimationFrame(update);
142
148
  setStatus('running');
@@ -145,9 +151,10 @@ const AudioRecorder = react_1.default.forwardRef((props_, ref) => {
145
151
  }, [onStart_, onError]);
146
152
  const onPause = react_1.default.useCallback((event) => {
147
153
  // media recorder
148
- if (refs.mediaRecorder.current) {
154
+ if (refs.mediaRecorder.current)
149
155
  refs.mediaRecorder.current.pause();
150
- }
156
+ // add so far to duration
157
+ refs.duration.current += date_1.OnesyDate.milliseconds - refs.startedAt.current;
151
158
  clear();
152
159
  // Remember previous value
153
160
  refs.valuePaused.current = refs.value.current;
@@ -157,43 +164,46 @@ const AudioRecorder = react_1.default.forwardRef((props_, ref) => {
157
164
  }, [onPause_]);
158
165
  const onStop = react_1.default.useCallback((event) => {
159
166
  // media recorder
160
- if (refs.mediaRecorder.current) {
167
+ if (refs.mediaRecorder.current)
161
168
  refs.mediaRecorder.current.stop();
162
- }
163
169
  clear();
164
170
  setStatus('initial');
165
171
  setValue(0);
166
- refs.start.current = 0;
167
172
  refs.valuePaused.current = 0;
168
173
  refs.value.current = 0;
169
174
  if ((0, utils_1.is)('function', onStop_))
170
175
  onStop_(event);
171
176
  }, [onStop_]);
172
177
  const onConfirm = react_1.default.useCallback(async (event) => {
178
+ var _a;
173
179
  // Stop
174
180
  onStop(event);
175
- setTimeout(() => {
176
- var _a;
177
- // Get the blob
178
- const mimeType = (_a = refs.mediaRecorder.current) === null || _a === void 0 ? void 0 : _a.mimeType;
179
- console.log('AudioRecorder onConfirm', mimeType);
180
- const blob = new Blob(refs.mediaRecorderBytes.current, { type: mimeType });
181
- // clean up
182
- refs.mediaRecorderBytes.current = [];
183
- console.log('AudioRecorder blob', blob, blob.size);
184
- if ((0, utils_1.is)('function', onConfirm_))
185
- onConfirm_(blob);
186
- }, 14);
181
+ // add so far to duration
182
+ refs.duration.current += date_1.OnesyDate.milliseconds - refs.startedAt.current;
183
+ await (0, utils_1.wait)(40);
184
+ // Get the blob
185
+ const mimeType = (_a = refs.mediaRecorder.current) === null || _a === void 0 ? void 0 : _a.mimeType;
186
+ let blob = new Blob(refs.mediaRecorderBytes.current, { type: mimeType });
187
+ // clean up
188
+ refs.mediaRecorderBytes.current = [];
189
+ const meta = {
190
+ // duration in seconds
191
+ duration: refs.duration.current / 1e3
192
+ };
193
+ const { blob: blobAudioFix, error } = await (0, utils_2.audioFix)(blob, meta.duration);
194
+ if (!error)
195
+ blob = blobAudioFix;
196
+ if ((0, utils_1.is)('function', onConfirm_))
197
+ onConfirm_(blob, meta);
187
198
  }, [onStop, onConfirm_]);
188
199
  const onResume = react_1.default.useCallback((event) => {
189
200
  // media recorder
190
- if (refs.mediaRecorder.current) {
201
+ if (refs.mediaRecorder.current)
191
202
  refs.mediaRecorder.current.resume();
192
- }
203
+ // record at milliseconds
204
+ refs.startedAt.current = date_1.OnesyDate.milliseconds;
193
205
  // ~60+ fps
194
206
  refs.animationFrame.current = requestAnimationFrame(update);
195
- // Update start, valuePaused value
196
- refs.start.current = date_1.OnesyDate.milliseconds;
197
207
  setStatus('running');
198
208
  if ((0, utils_1.is)('function', onResume_))
199
209
  onResume_(event);
@@ -5,7 +5,7 @@ const _excluded = ["size", "pause", "renderMain", "renderTime", "loading", "disa
5
5
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
6
6
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
7
7
  import React from 'react';
8
- import { getLeadingZerosNumber, is, isEnvironment } from '@onesy/utils';
8
+ import { getLeadingZerosNumber, is, isEnvironment, wait } from '@onesy/utils';
9
9
  import { classNames, style as styleMethod, useOnesyTheme } from '@onesy/style-react';
10
10
  import { OnesyDate, duration } from '@onesy/date';
11
11
  import IconMaterialMic from '@onesy/icons-material-rounded-react/IconMaterialMicW100';
@@ -18,7 +18,7 @@ import FadeElement from '../Fade';
18
18
  import TypeElement from '../Type';
19
19
  import TooltipElement from '../Tooltip';
20
20
  import IconButtonElement from '../IconButton';
21
- import { staticClassName } from '../utils';
21
+ import { audioFix, staticClassName } from '../utils';
22
22
  const useStyle = styleMethod(theme => ({
23
23
  '@keyframes pulse': {
24
24
  '0%': {
@@ -103,11 +103,14 @@ const AudioRecorder = /*#__PURE__*/React.forwardRef((props_, ref) => {
103
103
  root: React.useRef(null),
104
104
  mediaRecorder: React.useRef(null),
105
105
  mediaRecorderBytes: React.useRef([]),
106
- start: React.useRef(0),
106
+ startedAt: React.useRef(0),
107
107
  valuePaused: React.useRef(0),
108
108
  value: React.useRef(null),
109
109
  animationFrame: React.useRef(null),
110
- onData: React.useRef(null)
110
+ onData: React.useRef(null),
111
+ // fallback to duration calculation on desktop
112
+ // ie. for mobile where we can't easily determine duration
113
+ duration: React.useRef(0)
111
114
  };
112
115
  refs.onData.current = onData;
113
116
  const supported = isEnvironment('browser') && window.navigator.mediaDevices?.getUserMedia;
@@ -122,7 +125,7 @@ const AudioRecorder = /*#__PURE__*/React.forwardRef((props_, ref) => {
122
125
  };
123
126
  }, []);
124
127
  const update = () => {
125
- setValue(refs.valuePaused.current + (OnesyDate.milliseconds - refs.start.current));
128
+ setValue(refs.valuePaused.current + (OnesyDate.milliseconds - refs.startedAt.current));
126
129
  refs.animationFrame.current = requestAnimationFrame(update);
127
130
  };
128
131
  const onStart = React.useCallback(async event => {
@@ -157,7 +160,12 @@ const AudioRecorder = /*#__PURE__*/React.forwardRef((props_, ref) => {
157
160
  if (is('function', onError)) onError(error);
158
161
  return;
159
162
  }
160
- refs.start.current = OnesyDate.milliseconds;
163
+
164
+ // reset duration
165
+ refs.duration.current = 0;
166
+
167
+ // started at milliseconds
168
+ refs.startedAt.current = OnesyDate.milliseconds;
161
169
 
162
170
  // ~60+ fps
163
171
  refs.animationFrame.current = requestAnimationFrame(update);
@@ -166,9 +174,10 @@ const AudioRecorder = /*#__PURE__*/React.forwardRef((props_, ref) => {
166
174
  }, [onStart_, onError]);
167
175
  const onPause = React.useCallback(event => {
168
176
  // media recorder
169
- if (refs.mediaRecorder.current) {
170
- refs.mediaRecorder.current.pause();
171
- }
177
+ if (refs.mediaRecorder.current) refs.mediaRecorder.current.pause();
178
+
179
+ // add so far to duration
180
+ refs.duration.current += OnesyDate.milliseconds - refs.startedAt.current;
172
181
  clear();
173
182
 
174
183
  // Remember previous value
@@ -178,13 +187,10 @@ const AudioRecorder = /*#__PURE__*/React.forwardRef((props_, ref) => {
178
187
  }, [onPause_]);
179
188
  const onStop = React.useCallback(event => {
180
189
  // media recorder
181
- if (refs.mediaRecorder.current) {
182
- refs.mediaRecorder.current.stop();
183
- }
190
+ if (refs.mediaRecorder.current) refs.mediaRecorder.current.stop();
184
191
  clear();
185
192
  setStatus('initial');
186
193
  setValue(0);
187
- refs.start.current = 0;
188
194
  refs.valuePaused.current = 0;
189
195
  refs.value.current = 0;
190
196
  if (is('function', onStop_)) onStop_(event);
@@ -192,31 +198,39 @@ const AudioRecorder = /*#__PURE__*/React.forwardRef((props_, ref) => {
192
198
  const onConfirm = React.useCallback(async event => {
193
199
  // Stop
194
200
  onStop(event);
195
- setTimeout(() => {
196
- // Get the blob
197
- const mimeType = refs.mediaRecorder.current?.mimeType;
198
- console.log('AudioRecorder onConfirm', mimeType);
199
- const blob = new Blob(refs.mediaRecorderBytes.current, {
200
- type: mimeType
201
- });
202
201
 
203
- // clean up
204
- refs.mediaRecorderBytes.current = [];
205
- console.log('AudioRecorder blob', blob, blob.size);
206
- if (is('function', onConfirm_)) onConfirm_(blob);
207
- }, 14);
202
+ // add so far to duration
203
+ refs.duration.current += OnesyDate.milliseconds - refs.startedAt.current;
204
+ await wait(40);
205
+
206
+ // Get the blob
207
+ const mimeType = refs.mediaRecorder.current?.mimeType;
208
+ let blob = new Blob(refs.mediaRecorderBytes.current, {
209
+ type: mimeType
210
+ });
211
+
212
+ // clean up
213
+ refs.mediaRecorderBytes.current = [];
214
+ const meta = {
215
+ // duration in seconds
216
+ duration: refs.duration.current / 1e3
217
+ };
218
+ const {
219
+ blob: blobAudioFix,
220
+ error
221
+ } = await audioFix(blob, meta.duration);
222
+ if (!error) blob = blobAudioFix;
223
+ if (is('function', onConfirm_)) onConfirm_(blob, meta);
208
224
  }, [onStop, onConfirm_]);
209
225
  const onResume = React.useCallback(event => {
210
226
  // media recorder
211
- if (refs.mediaRecorder.current) {
212
- refs.mediaRecorder.current.resume();
213
- }
227
+ if (refs.mediaRecorder.current) refs.mediaRecorder.current.resume();
228
+
229
+ // record at milliseconds
230
+ refs.startedAt.current = OnesyDate.milliseconds;
214
231
 
215
232
  // ~60+ fps
216
233
  refs.animationFrame.current = requestAnimationFrame(update);
217
-
218
- // Update start, valuePaused value
219
- refs.start.current = OnesyDate.milliseconds;
220
234
  setStatus('running');
221
235
  if (is('function', onResume_)) onResume_(event);
222
236
  }, [onResume_]);
package/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @license UiReact v1.0.24
1
+ /** @license UiReact v1.0.26
2
2
  *
3
3
  * This source code is licensed under the MIT license found in the
4
4
  * LICENSE file in the root directory of this source tree.
package/esm/utils.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import { is, canvasFilterBrightness, canvasFilterContrast, canvasFilterSaturation, canvasFilterFade, canvasFilterInvert, canvasFilterOldPhoto, download, clamp, isEnvironment } from '@onesy/utils';
2
+ if (isEnvironment('browser')) {
3
+ window.Buffer = Buffer;
4
+ }
2
5
  export function reflow(element) {
3
6
  element?.offsetHeight;
4
7
  }
@@ -1253,4 +1256,46 @@ export const currencies = [{
1253
1256
  rounding: 0,
1254
1257
  code: 'ZMK',
1255
1258
  name_plural: 'Zambian kwachas'
1256
- }];
1259
+ }];
1260
+ export const audioFix = async (blob, duration) => {
1261
+ try {
1262
+ const arrayBuffer = await blob.arrayBuffer();
1263
+ const uint8Array = new Uint8Array(arrayBuffer);
1264
+
1265
+ // Function to find an EBML element by its ID
1266
+ function findElement(idHex) {
1267
+ const idBytes = new Uint8Array(idHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
1268
+ for (let i = 0; i < uint8Array.length - idBytes.length; i++) {
1269
+ if (uint8Array.slice(i, i + idBytes.length).toString() === idBytes.toString()) return i + idBytes.length; // Return position after the ID
1270
+ }
1271
+ return -1;
1272
+ }
1273
+
1274
+ // WebM duration EBML ID: 0x4489 (Segment Information -> Duration)
1275
+ const durationPos = findElement("4489");
1276
+ if (durationPos === -1) {
1277
+ throw new Error("Duration element not found in the WebM file.");
1278
+ }
1279
+
1280
+ // Encode new duration in IEEE 754 Float64 (Big Endian, 8 bytes)
1281
+ const durationBytes = new Uint8Array(new Float64Array([duration]).buffer).reverse();
1282
+
1283
+ // Overwrite duration bytes in the WebM file
1284
+ uint8Array.set(durationBytes, durationPos);
1285
+
1286
+ // Convert back to Blob
1287
+ const blobUpdated = new Blob([uint8Array], {
1288
+ type: "video/webm"
1289
+ });
1290
+ return {
1291
+ blob: blobUpdated,
1292
+ duration
1293
+ };
1294
+ } catch (error) {
1295
+ return {
1296
+ blob,
1297
+ duration,
1298
+ error
1299
+ };
1300
+ }
1301
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onesy/ui-react",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "UI for React",
5
5
  "repository": "https://github.com/onesy-me/onesy.git",
6
6
  "author": "Lazar <lazareric2@gmail.com>",
@@ -41,7 +41,7 @@
41
41
  "@onesy/icons-material-rounded-react": "^1.0.2",
42
42
  "@onesy/log": "^1.0.0",
43
43
  "@onesy/subscription": "^1.0.0",
44
- "@onesy/utils": "^1.0.0"
44
+ "@onesy/utils": "^1.0.2"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public",
package/types.d.ts CHANGED
@@ -38,4 +38,9 @@ export interface IMediaObject {
38
38
  urlSmall?: string;
39
39
  urlEmbed?: string;
40
40
  }
41
+ export interface IAudioFix {
42
+ blob: Blob;
43
+ duration?: number;
44
+ error?: any;
45
+ }
41
46
  export {};
package/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IPoint } from './types';
1
+ import { IAudioFix, IPoint } from './types';
2
2
  export declare function reflow(element: HTMLElement): void;
3
3
  export declare const staticClassName: (name: string, theme: any) => any;
4
4
  export declare const iconSizeToFontSize: (value: string | number) => any;
@@ -46,3 +46,4 @@ export declare const currencies: {
46
46
  code: string;
47
47
  name_plural: string;
48
48
  }[];
49
+ export declare const audioFix: (blob: Blob, duration: number) => Promise<IAudioFix>;
package/utils.js CHANGED
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.currencies = exports.iconFontSize = exports.formats = exports.toNumber = exports.caret = exports.keyboardStyleCommands = exports.keyboardStandardCommands = exports.getOverflowParent = exports.importIframeStyles = exports.replace = exports.sanitize = exports.minMaxBetweenNumbers = exports.controlPoint = exports.line = exports.angleToCoordinates = exports.matches = exports.save = exports.print = exports.canvasOldPhoto = exports.canvasInvert = exports.canvasFade = exports.canvasSaturation = exports.canvasContrast = exports.canvasBrightness = exports.image = exports.valueBreakpoints = exports.iconSizeToFontSize = exports.staticClassName = exports.reflow = void 0;
3
+ exports.audioFix = exports.currencies = exports.iconFontSize = exports.formats = exports.toNumber = exports.caret = exports.keyboardStyleCommands = exports.keyboardStandardCommands = exports.getOverflowParent = exports.importIframeStyles = exports.replace = exports.sanitize = exports.minMaxBetweenNumbers = exports.controlPoint = exports.line = exports.angleToCoordinates = exports.matches = exports.save = exports.print = exports.canvasOldPhoto = exports.canvasInvert = exports.canvasFade = exports.canvasSaturation = exports.canvasContrast = exports.canvasBrightness = exports.image = exports.valueBreakpoints = exports.iconSizeToFontSize = exports.staticClassName = exports.reflow = void 0;
4
4
  const utils_1 = require("@onesy/utils");
5
+ if ((0, utils_1.isEnvironment)('browser')) {
6
+ window.Buffer = Buffer;
7
+ }
5
8
  function reflow(element) {
6
9
  element === null || element === void 0 ? void 0 : element.offsetHeight;
7
10
  }
@@ -1420,3 +1423,41 @@ exports.currencies = [
1420
1423
  name_plural: 'Zambian kwachas'
1421
1424
  }
1422
1425
  ];
1426
+ const audioFix = async (blob, duration) => {
1427
+ try {
1428
+ const arrayBuffer = await blob.arrayBuffer();
1429
+ const uint8Array = new Uint8Array(arrayBuffer);
1430
+ // Function to find an EBML element by its ID
1431
+ function findElement(idHex) {
1432
+ const idBytes = new Uint8Array(idHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
1433
+ for (let i = 0; i < uint8Array.length - idBytes.length; i++) {
1434
+ if (uint8Array.slice(i, i + idBytes.length).toString() === idBytes.toString())
1435
+ return i + idBytes.length; // Return position after the ID
1436
+ }
1437
+ return -1;
1438
+ }
1439
+ // WebM duration EBML ID: 0x4489 (Segment Information -> Duration)
1440
+ const durationPos = findElement("4489");
1441
+ if (durationPos === -1) {
1442
+ throw new Error("Duration element not found in the WebM file.");
1443
+ }
1444
+ // Encode new duration in IEEE 754 Float64 (Big Endian, 8 bytes)
1445
+ const durationBytes = new Uint8Array(new Float64Array([duration]).buffer).reverse();
1446
+ // Overwrite duration bytes in the WebM file
1447
+ uint8Array.set(durationBytes, durationPos);
1448
+ // Convert back to Blob
1449
+ const blobUpdated = new Blob([uint8Array], { type: "video/webm" });
1450
+ return {
1451
+ blob: blobUpdated,
1452
+ duration
1453
+ };
1454
+ }
1455
+ catch (error) {
1456
+ return {
1457
+ blob,
1458
+ duration,
1459
+ error
1460
+ };
1461
+ }
1462
+ };
1463
+ exports.audioFix = audioFix;