react-timeline-editor
react-timeline-editor copied to clipboard
How to split and remove data tracks in the timeline?
Firstly - great library! It has everything, almost everything, I'm looking for. Currently, I'm searching for the most effective way to remove tracks and split them in timelines.
hey, did you make split work?
@sanil011 nope, i switched to custom timeline connected to remotion player
hey bro, there is a api named onClickActionOnly that helpful, code just like this
onClickActionOnly={(e, { action, time, row }) => {
if (counter.splitStatus) {
const newAction = {
...action,
start: time,
id: `${action.id + time}`,
};
const oldAction = {
...action,
end: time,
};
const newRow = {
...row,
actions: [
...row.actions.filter((item) => item.id !== action.id),
newAction,
oldAction,
],
};
setData((pre) => {
const rowIndex = pre.findIndex((item) => item.id === newRow.id);
pre[rowIndex] = newRow;
return [...pre];
});
} else {
}
}}
can you show me your project, because I am using same tech remotion and this npm package and got lot of issue like there is time difference like timeline show more video but video is less. can you introduce me to your custom timeline package .If you can then it would be a great help to me
first, translate document in to EN second, there are many api provided to split the origin array just like what i commented, onClickActionOnly, an api that arguments included action and row, u can manual it both to change the source array to achieve split the timeline
On Sat, Jul 20, 2024 at 6:56 PM Sanil @.***> wrote:
can you show me your project, because I am using same tech remotion and this npm package and got lot of issue like there is time difference like timeline show more video but video is less. can you introduce me to your custom timeline package .If you can then it would be a great help to me
— Reply to this email directly, view it on GitHub https://github.com/xzdarcy/react-timeline-editor/issues/51#issuecomment-2241086264, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOOVA4WUG7E4ZSJMYWYQ2TLZNI66LAVCNFSM6AAAAABHSKFQ2CVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENBRGA4DMMRWGQ . You are receiving this because you commented.Message ID: @.***>
thanks for your reply @theLeon33 but I did the split timeline. I am facing two issues. first, there is a gap between the mouse cursor and timeline cursor (blue cursor in image). second, difference between player time and timeline time. You can see in the image.
sry, I didn't get what u mean but if u want to split the timeline precisely, set the start and end time will be helpful, also can u rewrite the effects or engine API
@theLeon33 I did something like this. Everytime I split the audio and create a new action block it replicates the OG Audio. Any thoughts?
// Relative path: src/pages/splitter.js P.S. Do not remove this line
import { Timeline } from '@xzdarcy/react-timeline-editor';
import React, { useRef, useEffect, useState } from 'react';
import { loadMockData, scale, scaleWidth, startLeft, mockEffect } from '../mock';
import TimelinePlayer from '../player';
import { Button, Alert } from 'react-bootstrap';
import useTimelineStore from '../store/useTimelineStore'; // Import the Zustand store
const Splitter = () => {
const { data, loading, setData, setLoading } = useTimelineStore();
console.log("Splitter.js: data", data);
const [splitMode, setSplitMode] = useState(false);
const [selectedAction, setSelectedAction] = useState(null);
const timelineState = useRef();
const playerPanel = useRef();
const autoScrollWhenPlay = useRef(true);
useEffect(() => {
loadMockData().then((loadedData) => {
setData(loadedData);
setLoading(false);
});
}, [setData, setLoading]);
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === 'Delete' && selectedAction) {
handleDeleteAction();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [selectedAction]);
const handleActionSplit = (action, time, row) => {
const newAction = {
...action,
start: time,
id: `${action.id}-${time}`, // Ensure unique ID
};
const oldAction = {
...action,
end: time,
};
const updatedData = data.map((r) =>
r.id === row.id
? {
...r,
actions: [
...r.actions.filter((item) => item.id !== action.id),
oldAction,
newAction,
],
}
: r
);
setData(updatedData);
};
const toggleSplitMode = () => {
setSplitMode(!splitMode);
};
const handleDeleteAction = () => {
if (!selectedAction) return;
const updatedData = data.map((row) => ({
...row,
actions: row.actions.filter((action) => action.id !== selectedAction.id),
}));
setData(updatedData);
setSelectedAction(null);
};
const handleSelectAction = (action) => {
setSelectedAction(action);
};
const handleSwitchChange = (e) => {
autoScrollWhenPlay.current = e.target.checked;
};
const handleDataChange = (updatedData) => {
const newData = updatedData.map((row) => ({
...row,
actions: row.actions.map((action) => {
const originalLength = action.data.originalLength;
const end = Math.min(action.end, action.start + originalLength);
const movable = action.end <= action.start + originalLength;
return {
...action,
end,
movable,
};
}),
}));
setData(newData);
};
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<Button onClick={toggleSplitMode} style={{ marginBottom: '10px' }}>
{splitMode ? 'Disable Split Audio' : 'Enable Split Audio'}
</Button>
<Button
onClick={handleDeleteAction}
style={{ marginBottom: '10px', marginLeft: '10px' }}
disabled={!selectedAction}
>
Delete Selected Action
</Button>
<Alert variant="info" style={{ marginTop: '10px' }}>
{splitMode
? 'Double-click on an action block to split it. To do this, you must keep Split Audio Enabled. Press the "Delete" key or use the "Delete Selected Action" button to remove a selected action.'
: 'Click "Enable Split Audio" to activate Splitting mode, allowing you to double-click on action blocks to split them.'}
</Alert>
<div>
<label style={{ marginBottom: 20 }}>
<input
type="checkbox"
defaultChecked={autoScrollWhenPlay.current}
onChange={handleSwitchChange}
/>
{autoScrollWhenPlay.current ? "Enable runtime autoscroll" : "Disable runtime autoscroll"}
</label>
</div>
<div id="player-ground-1" ref={playerPanel}></div>
<TimelinePlayer timelineState={timelineState} autoScrollWhenPlay={autoScrollWhenPlay} />
<Timeline
scale={scale}
scaleWidth={scaleWidth}
startLeft={startLeft}
autoScroll={true}
ref={timelineState}
editorData={data}
effects={mockEffect}
onChange={handleDataChange}
dragLine={true} // Snapping always enabled
onClickActionOnly={(e, { action }) => {
handleSelectAction(action);
}}
onDoubleClickAction={(e, { action, time, row }) => {
if (splitMode) {
handleActionSplit(action, time, row);
}
}}
style={{
height: "500px",
width: "95%",
marginLeft: "80px",
marginRight: "80px",
marginTop: "20px",
cursor: splitMode ? 'text' : 'default',
}}
/>
</div>
);
};
export default Splitter;
// relativePath: src/mock.js P.S. Do not remove this line
import audioControl from './audioControl';
import { Howl } from 'howler';
export const scaleWidth = 160;
export const scale = 3;
export const startLeft = 20;
export const mockEffect = {
effect0: {
id: 'effect0',
name: 'Play Sound Effect',
source: {
start: ({ action, engine, isPlaying, time }) => {
if (isPlaying) {
const src = action.data.src;
audioControl.start({ id: src, src, startTime: action.start, engine, time });
}
},
enter: ({ action, engine, isPlaying, time }) => {
if (isPlaying) {
const src = action.data.src;
audioControl.start({ id: src, src, startTime: action.start, engine, time });
}
},
leave: ({ action, engine }) => {
const src = action.data.src;
audioControl.stop({ id: src, engine });
},
stop: ({ action, engine }) => {
const src = action.data.src;
audioControl.stop({ id: src, engine });
},
},
},
};
const audioFiles = [
{ id: 'action0', src: '/track1.mp3', name: 'Background Music 1' },
{ id: 'action1', src: '/track2.mp3', name: 'Background Music 2' },
{ id: 'action2', src: '/track3.mp3', name: 'Background Music 3' },
{ id: 'action3', src: '/track4.mp3', name: 'Background Music 4' },
];
export const loadMockData = () => {
return new Promise((resolve) => {
const mockData = [];
let previousEnd = 0;
audioFiles.forEach((file, index) => {
const sound = new Howl({
src: [file.src],
onload: function () {
const start = previousEnd;
const end = start + sound.duration();
previousEnd = end;
mockData.push({
id: `row${index}`,
actions: [
{
id: file.id,
start: start,
end: end,
effectId: 'effect0',
data: {
src: file.src,
name: file.name,
originalLength: sound.duration(),
},
},
],
});
if (mockData.length === audioFiles.length) {
resolve(mockData);
}
}
});
});
});
};
UI works correctly though