diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index ee9ae0a31..2c27519fe 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -151,11 +151,7 @@ void MatrixGenerator::GizmoMove(const QPointF &p, const QVector2D &scale, const gizmo_x2_dragger_.Drag(new_pos2.x()); gizmo_y2_dragger_.Drag(new_pos2.y()); - - InvalidateVisible(position_input_, position_input_); } - - InvalidateVisible(gizmo_drag_, gizmo_drag_); } void MatrixGenerator::GizmoRelease() diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index 37b896492..9bb9e02e4 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -150,8 +150,6 @@ void PolygonGenerator::GizmoMove(const QPointF &p, const QVector2D &scale, const gizmo_x_dragger_.Drag(new_pos.x()); gizmo_y_dragger_.Drag(new_pos.y()); - - InvalidateVisible(gizmo_drag_, gizmo_drag_); } void PolygonGenerator::GizmoRelease() diff --git a/app/node/inputdragger.cpp b/app/node/inputdragger.cpp index f6e0cb60c..9c62f520c 100644 --- a/app/node/inputdragger.cpp +++ b/app/node/inputdragger.cpp @@ -61,9 +61,9 @@ void NodeInputDragger::Start(NodeInput *input, const rational &time, int track) track); // We disable default signal emitting during the drag - input_->blockSignals(true); + //input_->blockSignals(true); input_->insert_keyframe(dragging_key_); - input_->blockSignals(false); + //input_->blockSignals(false); emit input_->KeyframeAdded(dragging_key_); } @@ -76,7 +76,7 @@ void NodeInputDragger::Drag(const QVariant& value) end_value_ = value; - input_->blockSignals(true); + //input_->blockSignals(true); if (input_->is_keyframing()) { dragging_key_->set_value(value); @@ -84,7 +84,7 @@ void NodeInputDragger::Drag(const QVariant& value) input_->set_standard_value(value, track_); } - input_->blockSignals(false); + //input_->blockSignals(false); } void NodeInputDragger::End() diff --git a/app/node/node.cpp b/app/node/node.cpp index 7f525b2a3..ece63ac12 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -175,19 +175,6 @@ void Node::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *s SendInvalidateCache(range, source); } -void Node::InvalidateVisible(NodeInput *from, NodeInput* source) -{ - Q_UNUSED(from) - - foreach (NodeParam* param, params_) { - if (param->type() == NodeParam::kOutput) { - foreach (NodeEdgePtr edge, param->edges()) { - edge->input()->parentNode()->InvalidateVisible(edge->input(), source); - } - } - } -} - TimeRange Node::InputTimeAdjustment(NodeInput *, const TimeRange &input_time) const { // Default behavior is no time adjustment at all diff --git a/app/node/node.h b/app/node/node.h index 5427f1643..46ac12f80 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -325,11 +325,6 @@ public: */ virtual void InvalidateCache(const TimeRange& range, NodeInput* from, NodeInput* source); - /** - * @brief Signal through node graph to only invalidate frames that are currently visible on a ViewerWidget - */ - virtual void InvalidateVisible(NodeInput *from, NodeInput* source); - /** * @brief Adjusts time that should be sent to nodes connected to certain inputs. * diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index cf9f67c7c..29eab8c88 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -99,15 +99,6 @@ void ViewerOutput::InvalidateCache(const TimeRange &range, NodeInput *from, Node Node::InvalidateCache(range, from, source); } -void ViewerOutput::InvalidateVisible(NodeInput* from, NodeInput *source) -{ - if (from == texture_input()) { - emit VisibleInvalidated(source); - } - - Node::InvalidateVisible(from, source); -} - void ViewerOutput::set_video_params(const VideoParams &video) { video_params_ = video; diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 99aba5dac..c94291650 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -63,7 +63,6 @@ public: } virtual void InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput* source) override; - virtual void InvalidateVisible(NodeInput *from, NodeInput* source) override; const VideoParams& video_params() const { return video_params_; @@ -115,8 +114,6 @@ signals: void GraphChangedFrom(NodeInput* source); - void VisibleInvalidated(NodeInput* source); - void LengthChanged(const rational& length); void SizeChanged(int width, int height); diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index 8be335067..b684d11e3 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -21,6 +21,7 @@ #include "audioplaybackcache.h" #include +#include #include #include "common/filefunctions.h" @@ -42,6 +43,14 @@ void AudioPlaybackCache::SetParameters(const AudioRenderingParams ¶ms) return; } + params_ = params; + + // Restart empty file so there's always "something" to play + QFile f(filename_); + if (f.open(QFile::WriteOnly)) { + f.close(); + } + // Our current audio cache is unusable, so we truncate it automatically InvalidateAll(); diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 77f2a1523..8da5859d5 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -101,10 +101,8 @@ void RenderBackend::CancelQueue() QFuture RenderBackend::Hash(const rational &time) { - RenderWorker* instance = GetInstanceFromPool(); - return QtConcurrent::run(&thread_pool_, - instance, + GetInstanceFromPool(), &RenderWorker::Hash, time); } @@ -115,10 +113,8 @@ QFuture RenderBackend::RenderFrame(const rational &time, bool clear_qu thread_pool_.clear(); } - RenderWorker* instance = GetInstanceFromPool(); - return QtConcurrent::run(&thread_pool_, - instance, + GetInstanceFromPool(), &RenderWorker::RenderFrame, time); } @@ -195,6 +191,12 @@ RenderWorker *RenderBackend::GetInstanceFromPool() if (viewer_node_) { instance->Init(viewer_node_); } + + connect(instance, + &RenderWorker::FinishedJob, + this, + &RenderBackend::WorkerFinished, + Qt::QueuedConnection); } instance->SetAvailable(false); @@ -225,6 +227,11 @@ void RenderBackend::AudioCallback() */ } +void RenderBackend::WorkerFinished() +{ + static_cast(sender())->SetAvailable(true); +} + bool RenderBackend::ConformWaitInfo::operator==(const RenderBackend::ConformWaitInfo &rhs) const { return rhs.stream == stream diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index e2bf33ef5..c5edc3e82 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -109,6 +109,8 @@ private: private slots: void AudioCallback(); + void WorkerFinished(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 5e71c28f8..edf3481b0 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -39,7 +39,7 @@ RenderWorker::~RenderWorker() Close(); } -QByteArray RenderWorker::Hash(const rational &time) const +QByteArray RenderWorker::Hash(const rational &time) { if (!viewer_) { return QByteArray(); @@ -55,6 +55,8 @@ QByteArray RenderWorker::Hash(const rational &time) const viewer_->Hash(hasher, time); + emit FinishedJob(); + return hasher.result(); } @@ -69,10 +71,6 @@ FramePtr RenderWorker::RenderFrame(const rational &time) QVariant texture = table.Get(NodeParam::kTexture); - if (texture.isNull()) { - return nullptr; - } - FramePtr frame = Frame::Create(); frame->set_video_params(video_params_); frame->set_timestamp(time); @@ -86,6 +84,8 @@ FramePtr RenderWorker::RenderFrame(const rational &time) TextureToFrame(texture, frame, video_download_matrix_); } + emit FinishedJob(); + return frame; } diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index 0afdb6b26..d51470a63 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -84,7 +84,7 @@ public: * * SHA-1 hash or empty QByteArray if no viewer node is set. */ - QByteArray Hash(const rational &time) const; + QByteArray Hash(const rational &time); /** * @brief Render the frame at this time @@ -127,6 +127,8 @@ protected: signals: void AudioConformUnavailable(); + void FinishedJob(); + private: NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time); diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 65009bef4..0bf903b39 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -174,9 +174,9 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) false); } else if (!selected_keys_.isEmpty()) { foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - NodeInput* input_parent = keypair.key->key()->parent(); + //NodeInput* input_parent = keypair.key->key()->parent(); - input_parent->blockSignals(true); + //input_parent->blockSignals(true); rational node_time = GetAdjustedTime(GetTimeTarget(), keypair.key->key()->parent()->parentNode(), @@ -191,9 +191,9 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) // We emit a custom value changed signal while the keyframe is being dragged so only the currently viewed // frame gets rendered in this time - input_parent->blockSignals(false); + //input_parent->blockSignals(false); - input_parent->parentNode()->InvalidateVisible(input_parent, input_parent); + //input_parent->parentNode()->InvalidateVisible(input_parent, input_parent); } } } @@ -352,7 +352,7 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include new_opposing_pos = dragging_bezier_point_opposing_start_; } - NodeInput* input_parent = dragging_bezier_point_->key()->parent(); + //NodeInput* input_parent = dragging_bezier_point_->key()->parent(); if (undoable) { QUndoCommand* command = new QUndoCommand(); @@ -360,7 +360,7 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include // Similar to the code in MouseRelease, we manipulated the signalling earlier and need to set the keys back to their // original position to allow the input to signal correctly when the undo command is pushed. - input_parent->blockSignals(true); + //input_parent->blockSignals(true); dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(), dragging_bezier_point_start_); @@ -382,11 +382,11 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include command); } - input_parent->blockSignals(false); + //input_parent->blockSignals(false); Core::instance()->undo_stack()->push(command); } else { - input_parent->blockSignals(true); + //input_parent->blockSignals(true); dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(), new_bezier_pos); @@ -394,9 +394,9 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include dragging_bezier_point_->key()->set_bezier_control(opposing_type, new_opposing_pos); - input_parent->blockSignals(false); + //input_parent->blockSignals(false); - input_parent->parentNode()->InvalidateVisible(input_parent, input_parent); + //input_parent->parentNode()->InvalidateVisible(input_parent, input_parent); } } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index ab1f20b49..da2d636ae 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -233,7 +233,7 @@ void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant dragger_.Drag(value); - input_->parentNode()->InvalidateVisible(input_, input_); + //input_->parentNode()->InvalidateVisible(input_, input_); } else if (dragger_.IsStarted()) { diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 8ab4d7169..cf1e2ba30 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -101,12 +101,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) : // Start background renderers renderer_ = new OpenGLBackend(this); - /* - connect(renderer_, &RenderBackend::CachedTimeReady, this, &ViewerWidget::RendererCachedTime); - connect(renderer_, &RenderBackend::CachedTimeReady, ruler(), &TimeRuler::CacheTimeReady); - connect(renderer_, &RenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange); - connect(renderer_, &RenderBackend::GeneratedFrame, this, &ViewerWidget::RendererGeneratedFrame); - */ connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); @@ -160,9 +154,12 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); connect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters); - connect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible); + connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedRange); connect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack); + n->audio_playback_cache()->SetParameters(AudioRenderingParams(n->audio_params(), + SampleFormat::kInternalFormat)); + SizeChangedSlot(n->video_params().width(), n->video_params().height()); LengthChangedSlot(n->GetLength()); @@ -191,6 +188,9 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache()); waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints()); } + + // Set texture to new texture (or null if no viewer node is available) + ForceUpdate(); } void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) @@ -203,7 +203,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); disconnect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters); - disconnect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible); + disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedRange); disconnect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack); // Effectively disables the viewer and clears the state @@ -270,9 +270,6 @@ void ViewerWidget::ConnectViewerNode(ViewerOutput *node, ColorManager* color_man override_color_manager_ = color_manager; TimeBasedWidget::ConnectViewerNode(node); - - // Set texture to new texture (or null if no viewer node is available) - ForceUpdate(); } void ViewerWidget::SetColorMenuEnabled(bool enabled) @@ -341,6 +338,12 @@ void ViewerWidget::SetGizmos(Node *node) void ViewerWidget::UpdateTextureFromNode(const rational& time) { + if (!FrameExistsAtTime(time)) { + // There is definitely no frame here, we can immediately flip to showing nothing + SetDisplayImage(nullptr, false); + return; + } + { QMutexLocker locker(playback_queue_.lock()); @@ -348,12 +351,17 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time) const ViewerPlaybackFrame& pf = playback_queue_.first(); if (pf.timestamp == time) { + // Frame was in queue, no need to decode anything SetDisplayImage(pf.frame, true); - QtConcurrent::run(this, &ViewerWidget::FillPlaybackQueue); return; + } else { + + // Skip this frame playback_queue_.removeFirst(); + RequestNextFrameForQueue(); + } } } @@ -363,28 +371,14 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time) } // Frame was not in queue, will require decoding - if (FrameExistsAtTime(time)) { - QString frame_fn = GetCachedFilenameFromTime(time); + QFutureWatcher* watcher = new QFutureWatcher(); - if (frame_fn.isEmpty()) { + connect(watcher, + &QFutureWatcher::finished, + this, + &ViewerWidget::RendererGeneratedFrame); - QFutureWatcher* watcher = new QFutureWatcher(); - connect(watcher, - &QFutureWatcher::finished, - this, - &ViewerWidget::RendererGeneratedFrame); - - watcher->setFuture(renderer_->RenderFrame(time, true)); - - } else { - - FramePtr f = DecodeCachedImage(frame_fn); - SetDisplayImage(f, false); - - } - } else { - SetDisplayImage(nullptr, false); - } + watcher->setFuture(renderer_->RenderFrame(time, true)); } void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) @@ -486,43 +480,19 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp void ViewerWidget::FillPlaybackQueue() { - playback_queue_.lock()->lock(); - foreach (ViewerWindow* window, windows_) { - window->queue()->lock()->lock(); - } - + // FIXME: Replace with an asynchronous approach while (playback_queue_.size() < 8) { - // Load frame from cache - FramePtr frame = nullptr; - - rational rtime = Timecode::timestamp_to_time(playback_queue_next_frame_, - timebase()); - - QString frame_fn = GetCachedFilenameFromTime(rtime); - - if (!frame_fn.isEmpty()) { - frame = DecodeCachedImage(frame_fn); - } - - ViewerPlaybackFrame f = {rtime, frame}; - - playback_queue_.append(f); + rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_, + timebase()); + playback_queue_next_frame_ += playback_speed_; + QFuture future = renderer_->RenderFrame(next_time, false); + future.waitForFinished(); + playback_queue_.AppendTimewise({future.result()->timestamp(), future.result()}, playback_speed_); foreach (ViewerWindow* window, windows_) { - window->queue()->append(f); + window->queue()->AppendTimewise({future.result()->timestamp(), future.result()}, playback_speed_); } - - if (!playback_speed_) { - break; - } - - playback_queue_next_frame_ += playback_speed_; } - - foreach (ViewerWindow* window, windows_) { - window->queue()->lock()->unlock(); - } - playback_queue_.lock()->unlock(); } QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) @@ -531,12 +501,12 @@ QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) QByteArray hash = GetConnectedNode()->video_frame_cache()->GetHash(time); if (!hash.isEmpty()) { - return GetConnectedNode()->video_frame_cache()->CachePathName( hash, PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline)); } } + return QString(); } @@ -597,6 +567,25 @@ void ViewerWidget::SetDisplayImage(FramePtr frame, bool main_only) emit LoadedBuffer(frame.get()); } +void ViewerWidget::RequestNextFrameForQueue() +{ + rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_, + timebase()); + + if (!FrameExistsAtTime(next_time)) { + return; + } + + playback_queue_next_frame_ += playback_speed_; + + QFutureWatcher* watcher = new QFutureWatcher(); + connect(watcher, + &QFutureWatcher::finished, + this, + &ViewerWidget::RendererGeneratedFrameForQueue); + watcher->setFuture(renderer_->RenderFrame(next_time, false)); +} + void ViewerWidget::UpdateStack() { if (GetConnectedNode() @@ -663,8 +652,8 @@ void ViewerWidget::ContextMenuSetCustomSafeMargins() QMessageBox::warning(this, tr("Invalid custom ratio"), - tr("Failed to parse \"%1\" into an aspect ratio. Please format a rational " - "fraction with a ':' or a '/' separator.").arg(s), + tr("Failed to parse \"%1\" into an aspect ratio. Please format a " + "rational fraction with a ':' or a '/' separator.").arg(s), QMessageBox::Ok); } } @@ -688,6 +677,18 @@ void ViewerWidget::RendererGeneratedFrame() SetDisplayImage(frame, false); } +void ViewerWidget::RendererGeneratedFrameForQueue() +{ + QFutureWatcher* watcher = static_cast*>(sender()); + FramePtr frame = watcher->result(); + watcher->deleteLater(); + + // Ignore this signal if we've paused now + if (IsPlaying()) { + playback_queue_.AppendTimewise({frame->timestamp(), frame}, playback_speed_); + } +} + void ViewerWidget::UpdateRendererParameters() { RenderMode::Mode render_mode = RenderMode::kOffline; @@ -990,15 +991,6 @@ void ViewerWidget::PlaybackTimerUpdate() } } -void ViewerWidget::RendererCachedTime(const rational &time, qint64 job_time) -{ - if (GetTime() == time && job_time > frame_cache_job_time_) { - frame_cache_job_time_ = job_time; - - ForceUpdate(); - } -} - void ViewerWidget::SizeChangedSlot(int width, int height) { sizer_->SetChildSize(width, height); @@ -1034,12 +1026,11 @@ void ViewerWidget::SetZoomFromMenu(QAction *action) sizer_->SetZoom(action->data().toInt()); } -void ViewerWidget::InvalidateVisible(NodeInput* source) +void ViewerWidget::ViewerInvalidatedRange(const TimeRange &range) { - /* - renderer_->NodeGraphChanged(source); - renderer_->RenderFrame(GetTime()); - */ + if (GetTime() >= range.in() && GetTime() < range.out()) { + ForceUpdate(); + } } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 635a4ff60..62c05b12f 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -188,6 +188,8 @@ private: void SetDisplayImage(FramePtr frame, bool main_only); + void RequestNextFrameForQueue(); + QStackedWidget* stack_; ViewerSizer* sizer_; @@ -226,8 +228,6 @@ private: private slots: void PlaybackTimerUpdate(); - void RendererCachedTime(const rational& time, qint64 job_time); - void SizeChangedSlot(int width, int height); void LengthChangedSlot(const rational& length); @@ -240,7 +240,7 @@ private slots: void SetZoomFromMenu(QAction* action); - void InvalidateVisible(NodeInput *source); + void ViewerInvalidatedRange(const TimeRange &range); void UpdateStack(); @@ -258,6 +258,8 @@ private slots: void RendererGeneratedFrame(); + void RendererGeneratedFrameForQueue(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h index a08816051..3cb6443d9 100644 --- a/app/widget/viewer/viewerqueue.h +++ b/app/widget/viewer/viewerqueue.h @@ -36,7 +36,28 @@ class ViewerQueue : public QLinkedList { public: ViewerQueue() = default; - QMutex* lock() { + void AppendTimewise(const ViewerPlaybackFrame& f, int playback_speed) + { + if (this->isEmpty() || f.timestamp > this->last().timestamp == playback_speed > 0) { + this->append(f); + } else { + reverse_iterator i = this->rbegin(); + + while (i != this->rend()) { + reverse_iterator next = i + 1; + + if (next->timestamp < f.timestamp == playback_speed > 0) { + this->insert(i.base(), f); + break; + } else { + i = next; + } + } + } + } + + QMutex* lock() + { return &queue_lock_; }