Commit 6fb5c447 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Fix inconsistent behavior of advanced timeline operations.

Related to #225
parent 940053c4
Pipeline #5985 passed with stage
in 27 minutes and 34 seconds
......@@ -1718,6 +1718,8 @@ void Bin::showClipProperties(const std::shared_ptr<ProjectClip> &clip, bool forc
delete w;
}
m_propertiesPanel->setProperty("clipId", clip->AbstractProjectItem::clipId());
// Setup timeline targets
emit setupTargets(clip->hasVideo(), clip->hasAudio());
auto *lay = static_cast<QVBoxLayout *>(m_propertiesPanel->layout());
if (lay == nullptr) {
lay = new QVBoxLayout(m_propertiesPanel);
......
......@@ -470,6 +470,8 @@ signals:
void clipNameChanged(const QString &);
/** @brief A clip was updated, request panel update. */
void refreshPanel(const QString &id);
/** @brief Upon selection, activate timeline target tracks. */
void setupTargets(bool hasVideo, bool hasAudio);
};
#endif
......@@ -251,6 +251,10 @@ void MainWindow::init()
connect(m_clipMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward);
connect(pCore->bin(), &Bin::findInTimeline, this, &MainWindow::slotClipInTimeline, Qt::DirectConnection);
connect(pCore->bin(), &Bin::setupTargets, this, [&] (bool hasVideo, bool hasAudio) {
getCurrentTimeline()->controller()->setTargetTracks({hasVideo ? getCurrentTimeline()->model()->getFirstVideoTrackIndex() : -1, hasAudio ? getCurrentTimeline()->model()->getFirstAudioTrackIndex() : -1});
}
);
// TODO deprecated, replace with Bin methods if necessary
/*connect(m_projectList, SIGNAL(loadingIsOver()), this, SLOT(slotElapsedTime()));
......
......@@ -890,7 +890,7 @@ bool ProjectManager::updateTimeline(int pos, int scrollPos)
pCore->monitorManager()->projectMonitor()->setProducer(m_mainTimelineModel->producer(), pos);
pCore->monitorManager()->projectMonitor()->adjustRulerSize(m_mainTimelineModel->duration() - 1, m_project->getGuideModel());
pCore->window()->getMainTimeline()->controller()->setZone(m_project->zone());
pCore->window()->getMainTimeline()->controller()->setTargetTracks(m_project->targetTracks());
//pCore->window()->getMainTimeline()->controller()->setTargetTracks(m_project->targetTracks());
pCore->window()->getMainTimeline()->controller()->setScrollPos(m_project->getDocumentProperty(QStringLiteral("scrollPos")).toInt());
int activeTrackPosition = m_project->getDocumentProperty(QStringLiteral("activeTrack"), QString::number( - 1)).toInt();
if (activeTrackPosition > -1 && activeTrackPosition < m_mainTimelineModel->getTracksCount()) {
......
......@@ -313,7 +313,6 @@ bool TimelineFunctions::extractZone(const std::shared_ptr<TimelineItemModel> &ti
std::function<bool(void)> undo = []() { return true; };
std::function<bool(void)> redo = []() { return true; };
bool result = true;
result = breakAffectedGroups(timeline, tracks, zone, undo, redo);
for (int &trackId : tracks) {
......@@ -323,7 +322,7 @@ bool TimelineFunctions::extractZone(const std::shared_ptr<TimelineItemModel> &ti
result = result && TimelineFunctions::liftZone(timeline, trackId, zone, undo, redo);
}
if (result && !liftOnly) {
result = TimelineFunctions::removeSpace(timeline, -1, zone, undo, redo);
result = TimelineFunctions::removeSpace(timeline, -1, zone, undo, redo, tracks);
}
pCore->pushUndo(undo, redo, liftOnly ? i18n("Lift zone") : i18n("Extract zone"));
return result;
......@@ -340,15 +339,15 @@ bool TimelineFunctions::insertZone(const std::shared_ptr<TimelineItemModel> &tim
auto it = timeline->m_allTracks.cbegin();
while (it != timeline->m_allTracks.cend()) {
int target_track = (*it)->getId();
if (!trackIds.contains(target_track) && !timeline->getTrackById_const(target_track)->shouldReceiveTimelineOp()) {
++it;
continue;
if (timeline->getTrackById_const(target_track)->shouldReceiveTimelineOp()) {
affectedTracks << target_track;
} else if (trackIds.contains(target_track)) {
// Track is marked as target but not active, remove it
trackIds.removeAll(target_track);
}
affectedTracks << target_track;
++it;
}
result = breakAffectedGroups(timeline, affectedTracks, QPoint(insertFrame, insertFrame + (zone.y() - zone.x())), undo, redo);
if (overwrite) {
// Cut all tracks
for (int target_track : affectedTracks) {
......@@ -367,13 +366,14 @@ bool TimelineFunctions::insertZone(const std::shared_ptr<TimelineItemModel> &tim
result = result && TimelineFunctions::requestClipCut(timeline, startClipId, insertFrame, undo, redo);
}
}
result = result && TimelineFunctions::requestInsertSpace(timeline, QPoint(insertFrame, insertFrame + (zone.y() - zone.x())), undo, redo);
result = result && TimelineFunctions::requestInsertSpace(timeline, QPoint(insertFrame, insertFrame + (zone.y() - zone.x())), undo, redo, affectedTracks);
}
if (result) {
if (!trackIds.isEmpty()) {
int newId = -1;
QString binClipId = QString("%1/%2/%3").arg(binId).arg(zone.x()).arg(zone.y() - 1);
result = timeline->requestClipInsertion(binClipId, trackIds.first(), insertFrame, newId, true, true, useTargets, undo, redo);
result = timeline->requestClipInsertion(binClipId, trackIds.first(), insertFrame, newId, true, true, useTargets, undo, redo, affectedTracks);
}
if (result) {
pCore->pushUndo(undo, redo, overwrite ? i18n("Overwrite zone") : i18n("Insert zone"));
......@@ -414,7 +414,7 @@ bool TimelineFunctions::liftZone(const std::shared_ptr<TimelineItemModel> &timel
return true;
}
bool TimelineFunctions::removeSpace(const std::shared_ptr<TimelineItemModel> &timeline, int trackId, QPoint zone, Fun &undo, Fun &redo)
bool TimelineFunctions::removeSpace(const std::shared_ptr<TimelineItemModel> &timeline, int trackId, QPoint zone, Fun &undo, Fun &redo, QVector<int> allowedTracks)
{
Q_UNUSED(trackId)
......@@ -422,58 +422,46 @@ bool TimelineFunctions::removeSpace(const std::shared_ptr<TimelineItemModel> &ti
auto it = timeline->m_allTracks.cbegin();
while (it != timeline->m_allTracks.cend()) {
int target_track = (*it)->getId();
if (timeline->m_videoTarget == target_track || timeline->m_audioTarget == target_track ||
timeline->getTrackById_const(target_track)->shouldReceiveTimelineOp()) {
if (timeline->getTrackById_const(target_track)->shouldReceiveTimelineOp()) {
std::unordered_set<int> subs = timeline->getItemsInRange(target_track, zone.y() - 1, -1, true);
clips.insert(subs.begin(), subs.end());
}
++it;
}
bool result = false;
if (!clips.empty()) {
int clipId = *clips.begin();
if (clips.size() > 1) {
int clipsGroup = timeline->m_groups->getRootId(clipId);
int res = timeline->requestClipsGroup(clips, undo, redo);
if (res > -1) {
result = timeline->requestGroupMove(clipId, res, 0, zone.x() - zone.y(), true, true, undo, redo);
if (result && res != clipsGroup) {
// Only ungroup if a group was created
result = timeline->requestClipUngroup(clipId, undo, redo);
}
if (!result) {
undo();
}
}
} else {
// only 1 clip to be moved
int clipStart = timeline->getItemPosition(clipId);
result = timeline->requestClipMove(clipId, timeline->getItemTrackId(clipId), clipStart - (zone.y() - zone.x()), true, true, true, true, undo, redo);
}
timeline->requestSetSelection(clips);
int itemId = *clips.begin();
int targetTrackId = timeline->getItemTrackId(itemId);
int targetPos = timeline->getItemPosition(itemId) + zone.x() - zone.y();
if (timeline->m_groups->isInGroup(itemId)) {
result = timeline->requestGroupMove(itemId, timeline->m_groups->getRootId(itemId), 0, zone.x() - zone.y(), true, true, undo, redo, true, true, allowedTracks);
} else if (timeline->isClip(itemId)) {
result = timeline->requestClipMove(itemId, targetTrackId, targetPos, true, true, true, true, undo, redo);
} else {
result = timeline->requestCompositionMove(itemId, targetTrackId, timeline->m_allCompositions[itemId]->getForcedTrack(), targetPos, true, true, undo, redo);
}
timeline->requestClearSelection();
if (!result) {
undo();
}
return result;
}
bool TimelineFunctions::requestInsertSpace(const std::shared_ptr<TimelineItemModel> &timeline, QPoint zone, Fun &undo, Fun &redo, bool followTargets)
bool TimelineFunctions::requestInsertSpace(const std::shared_ptr<TimelineItemModel> &timeline, QPoint zone, Fun &undo, Fun &redo, QVector<int> allowedTracks)
{
timeline->requestClearSelection();
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
std::unordered_set<int> items;
if (!followTargets) {
if (allowedTracks.isEmpty()) {
// Select clips in all tracks
items = timeline->getItemsInRange(-1, zone.x(), -1, true);
} else {
// Select clips in target and active tracks only
auto it = timeline->m_allTracks.cbegin();
while (it != timeline->m_allTracks.cend()) {
int target_track = (*it)->getId();
if (timeline->m_videoTarget == target_track || timeline->m_audioTarget == target_track ||
timeline->getTrackById_const(target_track)->shouldReceiveTimelineOp()) {
std::unordered_set<int> subs = timeline->getItemsInRange(target_track, zone.x(), -1, true);
items.insert(subs.begin(), subs.end());
}
++it;
for (int target_track : allowedTracks) {
std::unordered_set<int> subs = timeline->getItemsInRange(target_track, zone.x(), -1, true);
items.insert(subs.begin(), subs.end());
}
}
if (items.empty()) {
......@@ -488,7 +476,7 @@ bool TimelineFunctions::requestInsertSpace(const std::shared_ptr<TimelineItemMod
// TODO the three move functions should be unified in a "requestItemMove" function
if (timeline->m_groups->isInGroup(itemId)) {
result =
result && timeline->requestGroupMove(itemId, timeline->m_groups->getRootId(itemId), 0, zone.y() - zone.x(), true, true, local_undo, local_redo);
result && timeline->requestGroupMove(itemId, timeline->m_groups->getRootId(itemId), 0, zone.y() - zone.x(), true, true, local_undo, local_redo, true, true, allowedTracks);
} else if (timeline->isClip(itemId)) {
result = result && timeline->requestClipMove(itemId, targetTrackId, targetPos, true, true, true, true, local_undo, local_redo);
} else {
......
......@@ -83,12 +83,12 @@ struct TimelineFunctions
static bool requestSpacerEndOperation(const std::shared_ptr<TimelineItemModel> &timeline, int itemId, int startPosition, int endPosition);
static bool extractZone(const std::shared_ptr<TimelineItemModel> &timeline, QVector<int> tracks, QPoint zone, bool liftOnly);
static bool liftZone(const std::shared_ptr<TimelineItemModel> &timeline, int trackId, QPoint zone, Fun &undo, Fun &redo);
static bool removeSpace(const std::shared_ptr<TimelineItemModel> &timeline, int trackId, QPoint zone, Fun &undo, Fun &redo);
static bool removeSpace(const std::shared_ptr<TimelineItemModel> &timeline, int trackId, QPoint zone, Fun &undo, Fun &redo, QVector<int> allowedTracks = QVector<int>());
/** @brief This function will insert a blank space starting at zone.x, and ending at zone.y. This will affect all the tracks
@returns true on success, false otherwise
*/
static bool requestInsertSpace(const std::shared_ptr<TimelineItemModel> &timeline, QPoint zone, Fun &undo, Fun &redo, bool followTargets = true);
static bool requestInsertSpace(const std::shared_ptr<TimelineItemModel> &timeline, QPoint zone, Fun &undo, Fun &redo, QVector<int> allowedTracks = QVector<int> ());
static bool insertZone(const std::shared_ptr<TimelineItemModel> &timeline, QList<int> trackIds, const QString &binId, int insertFrame, QPoint zone,
bool overwrite, bool useTargets = true);
......
......@@ -504,6 +504,19 @@ int TimelineItemModel::getFirstVideoTrackIndex() const
return trackId;
}
int TimelineItemModel::getFirstAudioTrackIndex() const
{
int trackId = -1;
auto it = m_allTracks.cbegin();
while (it != m_allTracks.cend()) {
if ((*it)->isAudioTrack()) {
trackId = (*it)->getId();
}
++it;
}
return trackId;
}
const QString TimelineItemModel::getTrackFullName(int tid) const
{
QString tag = getTrackTagById(tid);
......
......@@ -86,6 +86,7 @@ public:
/** @brief returns the lower video track index in timeline.
**/
int getFirstVideoTrackIndex() const;
int getFirstAudioTrackIndex() const;
const QString getTrackFullName(int tid) const;
void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) override;
void notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector<int> &roles) override;
......
......@@ -973,7 +973,7 @@ bool TimelineModel::requestClipInsertion(const QString &binClipId, int trackId,
}
bool TimelineModel::requestClipInsertion(const QString &binClipId, int trackId, int position, int &id, bool logUndo, bool refreshView, bool useTargets,
Fun &undo, Fun &redo)
Fun &undo, Fun &redo, QVector<int> allowedTracks)
{
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
......@@ -1003,17 +1003,21 @@ bool TimelineModel::requestClipInsertion(const QString &binClipId, int trackId,
if (m_audioTarget >= 0 && m_videoTarget == -1 && useTargets) {
// If audio target is set but no video target, only insert audio
trackId = m_audioTarget;
if (trackId > -1 && getTrackById_const(trackId)->isLocked()) {
if (trackId > -1 && (getTrackById_const(trackId)->isLocked() || !allowedTracks.contains(trackId))) {
trackId = -1;
}
} else if (useTargets && getTrackById_const(trackId)->isLocked()) {
} else if (useTargets && (getTrackById_const(trackId)->isLocked() || !allowedTracks.contains(trackId))) {
// Video target set but locked
trackId = m_audioTarget;
if (trackId > -1 && getTrackById_const(trackId)->isLocked()) {
if (trackId > -1 && (getTrackById_const(trackId)->isLocked() || !allowedTracks.contains(trackId))) {
trackId = -1;
}
}
if (trackId == -1) {
if (!allowedTracks.isEmpty()) {
// No active tracks, aborting
return true;
}
pCore->displayMessage(i18n("No available track for insert operation"), ErrorMessage);
return false;
}
......@@ -1026,6 +1030,9 @@ bool TimelineModel::requestClipInsertion(const QString &binClipId, int trackId,
} else {
target_track = m_audioTarget == -1 ? -1 : getTrackById_const(m_audioTarget)->isLocked() ? -1 : m_audioTarget;
}
if (useTargets && !allowedTracks.contains(target_track)) {
target_track = -1;
}
qDebug() << "CLIP HAS A+V: " << master->hasAudioAndVideo();
int mirror = getMirrorTrackId(trackId);
if (mirror > -1 && getTrackById_const(mirror)->isLocked()) {
......@@ -1356,7 +1363,7 @@ bool TimelineModel::requestGroupMove(int itemId, int groupId, int delta_track, i
}
bool TimelineModel::requestGroupMove(int itemId, int groupId, int delta_track, int delta_pos, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool moveMirrorTracks,
bool allowViewRefresh)
bool allowViewRefresh, QVector<int> allowedTracks)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_allGroups.count(groupId) > 0);
......@@ -1486,13 +1493,16 @@ bool TimelineModel::requestGroupMove(int itemId, int groupId, int delta_track, i
if (delta_track == 0) {
// Special case, we are moving on same track, avoid too many calculations
for (int item : sorted_clips) {
int current_track_id = getItemTrackId(item);
int target_position = getItemPosition(item) + delta_pos;
int current_track_id = getItemTrackId(item);
if (!allowedTracks.isEmpty() && !allowedTracks.contains(current_track_id)) {
continue;
}
int target_position = getItemPosition(item) + delta_pos;
if (isClip(item)) {
ok = ok && requestClipMove(item, current_track_id, target_position, moveMirrorTracks, updateThisView, finalMove, finalMove, local_undo, local_redo, true);
} else {
ok = ok &&
requestCompositionMove(item, current_track_id, m_allCompositions[item]->getForcedTrack(), target_position, updateThisView, finalMove, local_undo, local_redo);
requestCompositionMove(item, current_track_id, m_allCompositions[item]->getForcedTrack(), target_position, updateThisView, finalMove, local_undo, local_redo);
}
}
if (!ok) {
......
......@@ -395,7 +395,7 @@ public:
bool useTargets = true);
/* Same function, but accumulates undo and redo*/
bool requestClipInsertion(const QString &binClipId, int trackId, int position, int &id, bool logUndo, bool refreshView, bool useTargets, Fun &undo,
Fun &redo);
Fun &redo, QVector<int> allowedTracks = QVector<int>());
protected:
/* @brief Creates a new clip instance without inserting it.
......@@ -434,7 +434,7 @@ public:
*/
bool requestGroupMove(int itemId, int groupId, int delta_track, int delta_pos, bool moveMirrorTracks = true, bool updateView = true, bool logUndo = true);
bool requestGroupMove(int itemId, int groupId, int delta_track, int delta_pos, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool moveMirrorTracks = true,
bool allowViewRefresh = true);
bool allowViewRefresh = true, QVector<int> allowedTracks = QVector<int>());
/* @brief Deletes all clips inside the group that contains the given clip.
This action is undoable
......
......@@ -126,13 +126,13 @@ Rectangle {
if (trackHeadRoot.isAudio) {
if (trackHeadRoot.trackId == timeline.audioTarget) {
timeline.audioTarget = -1;
} else {
} else if (timeline.hasAudioTarget) {
timeline.audioTarget = trackHeadRoot.trackId;
}
} else {
if (trackHeadRoot.trackId == timeline.videoTarget) {
timeline.videoTarget = -1;
} else {
} else if (timeline.hasVideoTarget) {
timeline.videoTarget = trackHeadRoot.trackId;
}
}
......
......@@ -107,8 +107,12 @@ void TimelineController::setModel(std::shared_ptr<TimelineItemModel> model)
void TimelineController::setTargetTracks(QPair<int, int> targets)
{
setVideoTarget(targets.first >= 0 && targets.first < m_model->getTracksCount() ? m_model->getTrackIndexFromPosition(targets.first) : -1);
setAudioTarget(targets.second >= 0 && targets.second < m_model->getTracksCount() ? m_model->getTrackIndexFromPosition(targets.second) : -1);
//setVideoTarget(targets.first >= 0 && targets.first < m_model->getTracksCount() ? m_model->getTrackIndexFromPosition(targets.first) : -1);
//setAudioTarget(targets.second >= 0 && targets.second < m_model->getTracksCount() ? m_model->getTrackIndexFromPosition(targets.second) : -1);
m_hasVideoTarget = targets.first >= 0;
m_hasAudioTarget = targets.second >= 0;
setVideoTarget(targets.first);
setAudioTarget(targets.second);
}
std::shared_ptr<TimelineItemModel> TimelineController::getModel() const
......@@ -1578,11 +1582,9 @@ void TimelineController::extractZone(QPoint zone, bool liftOnly)
auto it = m_model->m_allTracks.cbegin();
while (it != m_model->m_allTracks.cend()) {
int target_track = (*it)->getId();
if (target_track != audioTarget() && target_track != videoTarget() && !m_model->getTrackById_const(target_track)->shouldReceiveTimelineOp()) {
++it;
continue;
if (m_model->getTrackById_const(target_track)->shouldReceiveTimelineOp()) {
tracks << target_track;
}
tracks << target_track;
++it;
}
if (m_zone == QPoint()) {
......@@ -1870,6 +1872,16 @@ int TimelineController::videoTarget() const
return m_model->m_videoTarget;
}
bool TimelineController::hasAudioTarget() const
{
return m_hasAudioTarget;
}
bool TimelineController::hasVideoTarget() const
{
return m_hasVideoTarget;
}
void TimelineController::resetTrackHeight()
{
int tracksCount = m_model->getTracksCount();
......@@ -2231,7 +2243,7 @@ bool TimelineController::endFakeMove(int clipId, int position, bool updateView,
// There is a clip, cut
res = res && TimelineFunctions::requestClipCut(m_model, startClipId, position, undo, redo);
}
res = res && TimelineFunctions::requestInsertSpace(m_model, QPoint(position, position + duration), undo, redo, false);
res = res && TimelineFunctions::requestInsertSpace(m_model, QPoint(position, position + duration), undo, redo);
}
res = res && m_model->getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, invalidateTimeline, undo, redo);
if (res) {
......@@ -2345,7 +2357,7 @@ bool TimelineController::endFakeGroupMove(int clipId, int groupId, int delta_tra
res = res && TimelineFunctions::requestClipCut(m_model, startClipId, target_position, undo, redo);
}
}
res = res && TimelineFunctions::requestInsertSpace(m_model, QPoint(min, max), undo, redo, false);
res = res && TimelineFunctions::requestInsertSpace(m_model, QPoint(min, max), undo, redo);
}
for (int item : sorted_clips) {
if (m_model->isClip(item)) {
......
......@@ -67,6 +67,8 @@ class TimelineController : public QObject
Q_PROPERTY(int activeTrack READ activeTrack WRITE setActiveTrack NOTIFY activeTrackChanged)
Q_PROPERTY(int audioTarget READ audioTarget WRITE setAudioTarget NOTIFY audioTargetChanged)
Q_PROPERTY(int videoTarget READ videoTarget WRITE setVideoTarget NOTIFY videoTargetChanged)
Q_PROPERTY(bool hasAudioTarget READ hasAudioTarget)
Q_PROPERTY(bool hasVideoTarget READ hasVideoTarget)
Q_PROPERTY(QColor videoColor READ videoColor NOTIFY colorsChanged)
Q_PROPERTY(QColor audioColor READ audioColor NOTIFY colorsChanged)
Q_PROPERTY(QColor lockedColor READ lockedColor NOTIFY colorsChanged)
......@@ -142,6 +144,8 @@ public:
Q_INVOKABLE int seekPosition() const { return m_seekPosition; }
Q_INVOKABLE int audioTarget() const;
Q_INVOKABLE int videoTarget() const;
Q_INVOKABLE bool hasAudioTarget() const;
Q_INVOKABLE bool hasVideoTarget() const;
Q_INVOKABLE int activeTrack() const { return m_activeTrack; }
Q_INVOKABLE QColor videoColor() const;
Q_INVOKABLE QColor audioColor() const;
......@@ -494,6 +498,8 @@ private:
int m_videoTarget;
int m_activeTrack;
int m_audioRef;
bool m_hasAudioTarget {false};
bool m_hasVideoTarget {false};
QPair<int, int> m_recordStart;
int m_recordTrack;
QPoint m_zone;
......
......@@ -37,6 +37,7 @@ TEST_CASE("Advanced trimming operations", "[Trimming]")
int tid1 = TrackModel::construct(timeline);
int tid2 = TrackModel::construct(timeline);
int tid3 = TrackModel::construct(timeline);
// Add an audio track
int tid4 = TrackModel::construct(timeline, -1, -1, QString(), true);
int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly);
......@@ -619,6 +620,9 @@ TEST_CASE("Insert/delete", "[Trimming2]")
timeline->m_audioTarget = tid2;
timeline->m_videoTarget = tid1;
// Make tracks active
timeline->setTrackProperty(tid1, QStringLiteral("kdenlive:timeline_active"), QStringLiteral("1"));
timeline->setTrackProperty(tid2, QStringLiteral("kdenlive:timeline_active"), QStringLiteral("1"));
REQUIRE(TimelineFunctions::insertZone(timeline, {tid1, tid2}, binId, 3 + 2, {l / 4, 3 * l / 4}, false));
timeline->m_audioTarget = -1;
timeline->m_videoTarget = -1;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment