Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to split and remove data tracks in the timeline? #51

Open
gpiechnik2 opened this issue May 12, 2024 · 8 comments
Open

How to split and remove data tracks in the timeline? #51

gpiechnik2 opened this issue May 12, 2024 · 8 comments

Comments

@gpiechnik2
Copy link

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.

@sanil011
Copy link

hey, did you make split work?

@gpiechnik2
Copy link
Author

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

@theLeon33
Copy link

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

@sanil011
Copy link

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

@theLeon33
Copy link

theLeon33 commented Jul 21, 2024 via email

@sanil011
Copy link

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

@theLeon33
Copy link

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

@ktjayamanna
Copy link

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants