Skip to content

Commit d15ae3a

Browse files
committed
feat: recorder optimizations
1 parent 33888a7 commit d15ae3a

File tree

7 files changed

+55
-52
lines changed

7 files changed

+55
-52
lines changed

example/app/import-modal.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ import { useRecorder } from '../src/contexts/RecorderContext';
1414
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller';
1515
import { recordedHapticSchema } from '../src/schemas/recordingSchema';
1616
import { ZodError } from 'zod';
17+
import { hapticEventsToRecordingEvents } from '../src/utils/hapticPlayback';
18+
import type { HapticEvent } from 'react-native-ahaps';
19+
20+
const getDuration = (events: HapticEvent[]) => {
21+
return events.reduce((maxTime, event) => {
22+
const eventEnd =
23+
event.type === 'continuous'
24+
? event.relativeTime + event.duration
25+
: event.relativeTime;
26+
return Math.max(maxTime, eventEnd);
27+
}, 0);
28+
};
1729

1830
export default function ImportModal() {
1931
const insets = useSafeAreaInsets();
@@ -48,12 +60,20 @@ export default function ImportModal() {
4860
return;
4961
}
5062

51-
// Create the recording object with the new title
63+
const { events, curves } = validationResult.data;
64+
65+
const duration = getDuration(events);
66+
67+
const recordingEvents = hapticEventsToRecordingEvents(events, curves);
68+
5269
const recording: RecordedHaptic = {
53-
...validationResult.data,
70+
id: Date.now().toString(),
5471
name: title,
55-
id: Date.now().toString(), // Generate new ID
56-
createdAt: Date.now(), // Update timestamp
72+
createdAt: Date.now(),
73+
duration,
74+
events,
75+
curves,
76+
recordingEvents,
5777
};
5878

5979
// Import the recording

example/src/components/RecordingItem.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ export default function RecordingItem({
5353

5454
const handleExport = async () => {
5555
try {
56-
const jsonData = JSON.stringify(recording, null, 2);
56+
// Only export the essential haptic data
57+
const exportData = {
58+
events: recording.events,
59+
curves: recording.curves,
60+
};
61+
const jsonData = JSON.stringify(exportData, null, 2);
5762

5863
await Share.share({
5964
message: jsonData,

example/src/components/RecordingsList.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import {
55
FlatList,
66
TouchableOpacity,
77
} from 'react-native';
8+
import { useAnimatedReaction, type SharedValue } from 'react-native-reanimated';
89
import type { RecordedHaptic } from '../types/recording';
910
import RecordingItem from './RecordingItem';
1011
import { useSafeAreaInsets } from 'react-native-safe-area-context';
1112
import { Link } from 'expo-router';
1213
import { KeyboardStickyView } from 'react-native-keyboard-controller';
14+
import { useState } from 'react';
15+
import { scheduleOnRN } from 'react-native-worklets';
1316

1417
interface RecordingsListProps {
1518
recordings: RecordedHaptic[];
1619
selectedId: string | null;
17-
playingId: string | null;
20+
isPlaying: SharedValue<boolean>;
1821
onSelect: (id: string | null) => void;
1922
onPlay: (id: string) => void;
2023
onPause: (id: string) => void;
@@ -25,15 +28,27 @@ interface RecordingsListProps {
2528
export default function RecordingsList({
2629
recordings,
2730
selectedId,
28-
playingId,
31+
isPlaying,
2932
onSelect,
3033
onPlay,
3134
onPause,
3235
onDelete,
3336
onNameChange,
3437
}: RecordingsListProps) {
38+
const [playingId, setPlayingId] = useState<string | null>(null);
3539
const insets = useSafeAreaInsets();
3640

41+
useAnimatedReaction(
42+
() => isPlaying.get(),
43+
(isPlaying) => {
44+
if (isPlaying && selectedId) {
45+
scheduleOnRN(setPlayingId, selectedId);
46+
} else {
47+
scheduleOnRN(setPlayingId, null);
48+
}
49+
}
50+
);
51+
3752
const handleSelect = (id: string) => {
3853
if (selectedId === id) {
3954
onSelect(null);

example/src/contexts/RecorderContext.tsx

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,8 @@ import {
77
} from 'react-native-reanimated';
88
import type { RecordedHaptic, RecordingEvent } from '../types/recording';
99
import type { HapticEvent, HapticCurve } from 'react-native-ahaps';
10-
import {
11-
startHaptic,
12-
stopAllHaptics,
13-
} from 'react-native-ahaps';
14-
import {
15-
trimHapticDataFromSeekTime,
16-
hapticEventsToRecordingEvents,
17-
} from '../utils/hapticPlayback';
10+
import { startHaptic, stopAllHaptics } from 'react-native-ahaps';
11+
import { trimHapticDataFromSeekTime } from '../utils/hapticPlayback';
1812
import { PIXELS_PER_MILLISECOND } from '../components/RecordingTimeline';
1913
import { scheduleOnRN } from 'react-native-worklets';
2014
import { storage, STORAGE_KEYS } from '../utils/storage';
@@ -60,7 +54,6 @@ const RecorderContext = createContext<RecorderContextValue | null>(null);
6054

6155
export function RecorderProvider({ children }: { children: ReactNode }) {
6256
const [recordings, _setRecordings] = useState<RecordedHaptic[]>(() => {
63-
// Load recordings from MMKV on initial mount
6457
try {
6558
const stored = storage.getString(STORAGE_KEYS.RECORDINGS);
6659
if (stored) {
@@ -172,9 +165,6 @@ export function RecorderProvider({ children }: { children: ReactNode }) {
172165

173166
isRecording.set(false);
174167

175-
// Note: The continuous player is managed by MiniContinuousPalette
176-
// and will be stopped automatically when the gesture ends
177-
178168
let events = recordingEvents.get();
179169

180170
if (wasContinuousActive) {
@@ -204,14 +194,13 @@ export function RecorderProvider({ children }: { children: ReactNode }) {
204194
duration,
205195
events: hapticEvents,
206196
curves: hapticCurves,
197+
recordingEvents: events,
207198
};
208199

209200
scheduleOnRN(addRecordingAndSelect, newRecording);
210201

211202
mode.set('playback');
212-
playbackEvents.set(
213-
hapticEventsToRecordingEvents(hapticEvents, hapticCurves)
214-
);
203+
playbackEvents.set(events);
215204
playbackTotalDuration.set(duration);
216205
playbackTime.set(0);
217206
playbackStartTime.set(0);
@@ -293,9 +282,7 @@ export function RecorderProvider({ children }: { children: ReactNode }) {
293282
const recording = recordings.find((r) => r.id === id);
294283
if (recording) {
295284
mode.set('playback');
296-
playbackEvents.set(
297-
hapticEventsToRecordingEvents(recording.events, recording.curves)
298-
);
285+
playbackEvents.set(recording.recordingEvents);
299286
playbackTotalDuration.set(recording.duration);
300287
playbackTime.set(0);
301288
playbackStartTime.set(0);

example/src/schemas/recordingSchema.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,41 @@
11
import { z } from 'zod';
22

3-
// Schema for haptic event parameters
43
const hapticEventParameterSchema = z.object({
54
type: z.enum(['intensity', 'sharpness']),
65
value: z.number().min(0).max(1),
76
});
87

9-
// Schema for transient haptic event
108
const transientHapticEventSchema = z.object({
119
type: z.literal('transient'),
1210
parameters: z.array(hapticEventParameterSchema),
1311
relativeTime: z.number().min(0),
1412
});
1513

16-
// Schema for continuous haptic event
1714
const continuousHapticEventSchema = z.object({
1815
type: z.literal('continuous'),
1916
parameters: z.array(hapticEventParameterSchema),
2017
relativeTime: z.number().min(0),
2118
duration: z.number().min(0),
2219
});
2320

24-
// Discriminated union for haptic events
2521
const hapticEventSchema = z.discriminatedUnion('type', [
2622
transientHapticEventSchema,
2723
continuousHapticEventSchema,
2824
]);
2925

30-
// Schema for haptic curve control points
3126
const hapticCurveControlPointSchema = z.object({
3227
relativeTime: z.number().min(0),
3328
value: z.number().min(0).max(1),
3429
});
3530

36-
// Schema for haptic curves
3731
const hapticCurveSchema = z.object({
3832
type: z.enum(['intensity', 'sharpness']),
3933
controlPoints: z.array(hapticCurveControlPointSchema),
4034
relativeTime: z.number().min(0),
4135
});
4236

43-
// Schema for recorded haptic
4437
export const recordedHapticSchema = z.object({
45-
id: z.string().min(1),
46-
name: z.string().min(1),
47-
createdAt: z.number().positive(),
48-
duration: z.number().min(0),
49-
events: z.array(hapticEventSchema).min(0),
38+
events: z.array(hapticEventSchema).min(1),
5039
curves: z.array(hapticCurveSchema).min(0),
5140
});
5241

example/src/screens/Recorder.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { View, StyleSheet, Text, Dimensions } from 'react-native';
22
import { useSafeAreaInsets } from 'react-native-safe-area-context';
33
import { useDerivedValue } from 'react-native-reanimated';
4-
import { useState, useEffect } from 'react';
54
import MiniTransientPalette from '../components/MiniTransientPalette';
65
import MiniContinuousPalette from '../components/MiniContinuousPalette';
76
import RecordingTimeline from '../components/RecordingTimeline';
@@ -50,19 +49,6 @@ function RecorderContent() {
5049
renameRecording,
5150
} = useRecorder();
5251

53-
const [playingId, setPlayingId] = useState<string | null>(null);
54-
55-
useEffect(() => {
56-
const interval = setInterval(() => {
57-
if (isPlaying.get() && selectedRecordingId) {
58-
setPlayingId(selectedRecordingId);
59-
} else {
60-
setPlayingId(null);
61-
}
62-
}, 50);
63-
return () => clearInterval(interval);
64-
}, [isPlaying, selectedRecordingId]);
65-
6652
const currentTime = useDerivedValue(() => {
6753
const m = mode.get();
6854
if (m === 'recording') return recordingTime.get();
@@ -208,7 +194,7 @@ function RecorderContent() {
208194
<RecordingsList
209195
recordings={recordings}
210196
selectedId={selectedRecordingId}
211-
playingId={playingId}
197+
isPlaying={isPlaying}
212198
onSelect={selectRecording}
213199
onPlay={handlePlayRecording}
214200
onPause={handlePauseRecording}

example/src/types/recording.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface RecordedHaptic {
77
duration: number;
88
events: HapticEvent[];
99
curves: HapticCurve[];
10+
recordingEvents: RecordingEvent[];
1011
}
1112

1213
export interface RecordingEvent {

0 commit comments

Comments
 (0)