@ldelia/react-media 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/reproduction-widget/models/Reproduction.d.ts +1 -2
- package/dist/components/reproduction-widget/models/Reproduction.js +14 -9
- package/dist/components/timeline/RangeSelectorCanvas/RangeSelectorCanvas.js +8 -2
- package/dist/stories/timeline.stories.d.ts +1 -0
- package/dist/stories/timeline.stories.js +68 -1
- package/package.json +7 -7
|
@@ -10,7 +10,6 @@ export declare const REPRODUCTION_STATES: {
|
|
|
10
10
|
};
|
|
11
11
|
type ReproductionState = (typeof REPRODUCTION_STATES)[keyof typeof REPRODUCTION_STATES];
|
|
12
12
|
type Handler = (args: object) => void;
|
|
13
|
-
declare const dispatchOnReadyHandlers: unique symbol;
|
|
14
13
|
declare const dispatchOnSongStartHandlers: unique symbol;
|
|
15
14
|
declare const dispatchOnCountingInHandlers: unique symbol;
|
|
16
15
|
declare const dispatchOnPlayHandlers: unique symbol;
|
|
@@ -27,7 +26,6 @@ export declare class Reproduction {
|
|
|
27
26
|
private loopInterval;
|
|
28
27
|
private loopRange;
|
|
29
28
|
private countingInCounter;
|
|
30
|
-
private [dispatchOnReadyHandlers];
|
|
31
29
|
private [dispatchOnSongStartHandlers];
|
|
32
30
|
private [dispatchOnCountingInHandlers];
|
|
33
31
|
private [dispatchOnPlayHandlers];
|
|
@@ -72,6 +70,7 @@ export declare class Reproduction {
|
|
|
72
70
|
seekTo(seconds: number): void;
|
|
73
71
|
getVolume(): number;
|
|
74
72
|
setVolume(volume: number): void;
|
|
73
|
+
setLoopRange(from: number, to: number): boolean;
|
|
75
74
|
getAvailablePlaybackRates(): number[];
|
|
76
75
|
setPlaybackRate(playbackRate: number): void;
|
|
77
76
|
isAvailable(): boolean;
|
|
@@ -15,7 +15,6 @@ const EVENTS = {
|
|
|
15
15
|
FINISH: 'FINISH',
|
|
16
16
|
ERROR: 'ERROR',
|
|
17
17
|
};
|
|
18
|
-
const dispatchOnReadyHandlers = Symbol();
|
|
19
18
|
const dispatchOnSongStartHandlers = Symbol();
|
|
20
19
|
const dispatchOnCountingInHandlers = Symbol();
|
|
21
20
|
const dispatchOnPlayHandlers = Symbol();
|
|
@@ -25,7 +24,6 @@ const dispatchOnFinishHandlers = Symbol();
|
|
|
25
24
|
const dispatchOnErrorHandlers = Symbol();
|
|
26
25
|
export class Reproduction {
|
|
27
26
|
constructor(player, requiresCountingIn, songTempo, volume) {
|
|
28
|
-
this[dispatchOnReadyHandlers] = [];
|
|
29
27
|
this[dispatchOnSongStartHandlers] = [];
|
|
30
28
|
this[dispatchOnCountingInHandlers] = [];
|
|
31
29
|
this[dispatchOnPlayHandlers] = [];
|
|
@@ -191,15 +189,11 @@ export class Reproduction {
|
|
|
191
189
|
}, tickInterval);
|
|
192
190
|
}
|
|
193
191
|
playLoop(from, to) {
|
|
194
|
-
if (!
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (to <= from) {
|
|
192
|
+
if (!this.setLoopRange(from, to)) {
|
|
198
193
|
return;
|
|
199
194
|
}
|
|
200
195
|
clearInterval(this.loopInterval);
|
|
201
196
|
this.loopInterval = null;
|
|
202
|
-
this.loopRange = { from, to };
|
|
203
197
|
this.seekTo(from);
|
|
204
198
|
this.play();
|
|
205
199
|
const loopCheckInterval = 100;
|
|
@@ -274,6 +268,13 @@ export class Reproduction {
|
|
|
274
268
|
}
|
|
275
269
|
this.player.setVolume(volume);
|
|
276
270
|
}
|
|
271
|
+
setLoopRange(from, to) {
|
|
272
|
+
if (!Number.isFinite(from) || !Number.isFinite(to) || to <= from) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
this.loopRange = { from, to };
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
277
278
|
getAvailablePlaybackRates() {
|
|
278
279
|
return this.player.getAvailablePlaybackRates();
|
|
279
280
|
}
|
|
@@ -289,7 +290,9 @@ export class Reproduction {
|
|
|
289
290
|
countInAndPlay(timeout, limit) {
|
|
290
291
|
// the initial count starts instantly, no need to wait
|
|
291
292
|
this.countingInCounter++;
|
|
292
|
-
this.dispatch(Reproduction.EVENTS.COUNTING_IN, {
|
|
293
|
+
this.dispatch(Reproduction.EVENTS.COUNTING_IN, {
|
|
294
|
+
countingInCounter: this.countingInCounter,
|
|
295
|
+
});
|
|
293
296
|
const interval = setInterval(() => {
|
|
294
297
|
this.countingInCounter++;
|
|
295
298
|
if (this.countingInCounter === limit) {
|
|
@@ -303,7 +306,9 @@ export class Reproduction {
|
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
308
|
else {
|
|
306
|
-
this.dispatch(Reproduction.EVENTS.COUNTING_IN, {
|
|
309
|
+
this.dispatch(Reproduction.EVENTS.COUNTING_IN, {
|
|
310
|
+
countingInCounter: this.countingInCounter,
|
|
311
|
+
});
|
|
307
312
|
}
|
|
308
313
|
}, timeout);
|
|
309
314
|
}
|
|
@@ -45,7 +45,7 @@ const RangeSelectorCanvas = ({ selectedRange, onChange, onRangeChange, }) => {
|
|
|
45
45
|
context.fillRect(pixelX0, 0, pixelX1 - pixelX0, context.canvas.height);
|
|
46
46
|
context.globalAlpha = 1.0;
|
|
47
47
|
};
|
|
48
|
-
// Check if mouse is near the start or end edge of the selection
|
|
48
|
+
// Check if the mouse pointer is near the start or end edge of the selection
|
|
49
49
|
const isNearSelectionEdge = (pixel) => {
|
|
50
50
|
if (selectedRange.length !== 2)
|
|
51
51
|
return { isNear: false, edge: null };
|
|
@@ -180,4 +180,10 @@ const RangeSelectorCanvas = ({ selectedRange, onChange, onRangeChange, }) => {
|
|
|
180
180
|
}, [selection, zoomContextValue]);
|
|
181
181
|
return (React.createElement(OverlayCanvas, { ref: canvasRef, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, style: { cursor: cursorStyle }, className: 'media-timeline-range-selector-canvas' }));
|
|
182
182
|
};
|
|
183
|
-
|
|
183
|
+
const areEqual = (prevProps, nextProps) => {
|
|
184
|
+
return (prevProps.selectedRange[0] === nextProps.selectedRange[0] &&
|
|
185
|
+
prevProps.selectedRange[1] === nextProps.selectedRange[1] &&
|
|
186
|
+
prevProps.onChange === nextProps.onChange &&
|
|
187
|
+
prevProps.onRangeChange === nextProps.onRangeChange);
|
|
188
|
+
};
|
|
189
|
+
export default React.memo(RangeSelectorCanvas, areEqual);
|
|
@@ -9,3 +9,4 @@ export declare const WithCustomClassName: import("storybook/internal/csf").Annot
|
|
|
9
9
|
export declare const WithoutTimeBlocks: import("storybook/internal/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, TimelineProps>;
|
|
10
10
|
export declare const Minimalist: import("storybook/internal/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, TimelineProps>;
|
|
11
11
|
export declare const WithSelectedRangeAndMarkers: import("storybook/internal/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, TimelineProps>;
|
|
12
|
+
export declare const MediaPlaybackSimulation: import("storybook/internal/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, TimelineProps>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
import { Timeline } from '../components/timeline';
|
|
4
4
|
import './timeline.stories.custom.css';
|
|
@@ -17,6 +17,65 @@ export default {
|
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
19
|
const Template = (args) => (React.createElement(StyledTimeline, Object.assign({}, args)));
|
|
20
|
+
const PlaybackSimulation = (args) => {
|
|
21
|
+
const [currentValue, setCurrentValue] = useState(args.value || 0);
|
|
22
|
+
const [selectedRange, setSelectedRange] = useState(null);
|
|
23
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
24
|
+
const intervalRef = useRef(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (isPlaying && currentValue < args.duration) {
|
|
27
|
+
intervalRef.current = setInterval(() => {
|
|
28
|
+
setCurrentValue((prev) => {
|
|
29
|
+
const next = prev + 0.05; // Advance 50ms (0.05 seconds)
|
|
30
|
+
return next >= args.duration ? args.duration : next;
|
|
31
|
+
});
|
|
32
|
+
}, 50); // Update every 50ms
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (intervalRef.current) {
|
|
36
|
+
clearInterval(intervalRef.current);
|
|
37
|
+
intervalRef.current = null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return () => {
|
|
41
|
+
if (intervalRef.current) {
|
|
42
|
+
clearInterval(intervalRef.current);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}, [isPlaying, currentValue, args.duration]);
|
|
46
|
+
const handlePlayPause = () => {
|
|
47
|
+
if (currentValue >= args.duration) {
|
|
48
|
+
setCurrentValue(0); // Reset to beginning if we reached the end
|
|
49
|
+
}
|
|
50
|
+
setIsPlaying(!isPlaying);
|
|
51
|
+
};
|
|
52
|
+
const handleReset = () => {
|
|
53
|
+
setIsPlaying(false);
|
|
54
|
+
setCurrentValue(0);
|
|
55
|
+
};
|
|
56
|
+
const onRangeChangeHandler = (values) => {
|
|
57
|
+
setSelectedRange(values);
|
|
58
|
+
};
|
|
59
|
+
return (React.createElement("div", null,
|
|
60
|
+
React.createElement("div", { style: { marginBottom: '10px' } },
|
|
61
|
+
React.createElement("button", { onClick: handlePlayPause, style: { marginRight: '10px' } }, isPlaying ? 'Pause' : 'Play'),
|
|
62
|
+
React.createElement("button", { onClick: handleReset }, "Reset"),
|
|
63
|
+
React.createElement("span", { style: { marginLeft: '20px' } },
|
|
64
|
+
"Time: ",
|
|
65
|
+
currentValue.toFixed(2),
|
|
66
|
+
"s / ",
|
|
67
|
+
args.duration,
|
|
68
|
+
"s"),
|
|
69
|
+
React.createElement("span", { style: { marginLeft: '20px' } },
|
|
70
|
+
"Selected range:",
|
|
71
|
+
' ',
|
|
72
|
+
selectedRange ? `${selectedRange[0]}-${selectedRange[1]}` : 'N/A')),
|
|
73
|
+
React.createElement(StyledTimeline, Object.assign({}, args, { value: currentValue, onChange: (value) => {
|
|
74
|
+
var _a;
|
|
75
|
+
setCurrentValue(value);
|
|
76
|
+
(_a = args.onChange) === null || _a === void 0 ? void 0 : _a.call(args, value);
|
|
77
|
+
}, onRangeChange: onRangeChangeHandler }))));
|
|
78
|
+
};
|
|
20
79
|
export const Default = Template.bind({});
|
|
21
80
|
Default.args = {
|
|
22
81
|
duration: 305,
|
|
@@ -61,3 +120,11 @@ WithSelectedRangeAndMarkers.args = {
|
|
|
61
120
|
selectedRange: [20, 30],
|
|
62
121
|
markers: [90, 108],
|
|
63
122
|
};
|
|
123
|
+
export const MediaPlaybackSimulation = PlaybackSimulation.bind({});
|
|
124
|
+
MediaPlaybackSimulation.args = {
|
|
125
|
+
duration: 120, // 2 minutes
|
|
126
|
+
value: 0,
|
|
127
|
+
zoomLevel: 0,
|
|
128
|
+
selectedRange: [30, 45], // Show a selected range
|
|
129
|
+
markers: [15, 60, 90], // Add some markers
|
|
130
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ldelia/react-media",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "A React components collection for media-related features.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"keywords": [
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"@babel/preset-react": "^7.24.7",
|
|
115
115
|
"@babel/preset-typescript": "^7.24.7",
|
|
116
116
|
"@chromatic-com/storybook": "^5.0.1",
|
|
117
|
-
"@storybook/addon-docs": "^10.2.
|
|
118
|
-
"@storybook/addon-links": "^10.2.
|
|
119
|
-
"@storybook/addon-onboarding": "^10.2.
|
|
117
|
+
"@storybook/addon-docs": "^10.2.13",
|
|
118
|
+
"@storybook/addon-links": "^10.2.13",
|
|
119
|
+
"@storybook/addon-onboarding": "^10.2.13",
|
|
120
120
|
"@storybook/react-docgen-typescript-plugin": "^1.0.6--canary.9.0c3f3b7.0",
|
|
121
|
-
"@storybook/react-webpack5": "^10.2.
|
|
121
|
+
"@storybook/react-webpack5": "^10.2.13",
|
|
122
122
|
"@testing-library/jest-dom": "^6.4.6",
|
|
123
123
|
"@testing-library/react": "^16.0.0",
|
|
124
124
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"babel-loader": "^9.1.3",
|
|
132
132
|
"eslint-config-prettier": "^9.1.0",
|
|
133
133
|
"eslint-plugin-prettier": "^5.1.3",
|
|
134
|
-
"eslint-plugin-storybook": "^10.2.
|
|
134
|
+
"eslint-plugin-storybook": "^10.2.13",
|
|
135
135
|
"jest-environment-jsdom": "^30.2.0",
|
|
136
136
|
"postcss-flexbugs-fixes": "^5.0.2",
|
|
137
137
|
"postcss-normalize": "^10.0.1",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"prettier": "^3.3.2",
|
|
140
140
|
"prop-types": "^15.8.1",
|
|
141
141
|
"react-docgen-typescript-plugin": "^1.0.8",
|
|
142
|
-
"storybook": "^10.2.
|
|
142
|
+
"storybook": "^10.2.13",
|
|
143
143
|
"ts-loader": "^9.5.1",
|
|
144
144
|
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
|
145
145
|
"webpack": "^5.105.2"
|