react-timeline-editor icon indicating copy to clipboard operation
react-timeline-editor copied to clipboard

How to split and remove data tracks in the timeline?

Open gpiechnik2 opened this issue 1 year ago • 8 comments

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.

gpiechnik2 avatar May 12 '24 00:05 gpiechnik2

hey, did you make split work?

sanil011 avatar Jun 17 '24 07:06 sanil011

@sanil011 nope, i switched to custom timeline connected to remotion player

gpiechnik2 avatar Jun 17 '24 13:06 gpiechnik2

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 {
      }
    }}

theLeon33 avatar Jul 08 '24 01:07 theLeon33

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

sanil011 avatar Jul 20 '24 10:07 sanil011

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: @.***>

theLeon33 avatar Jul 21 '24 14:07 theLeon33

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.

Screenshot 2024-07-22 at 1 27 08 AM

sanil011 avatar Jul 21 '24 20:07 sanil011

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 avatar Jul 22 '24 06:07 theLeon33

@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 image

ktjayamanna avatar Aug 14 '24 22:08 ktjayamanna