From 4ac7a1bf0badfa38e739c54f0d678d379fe3fb25 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 17 Jun 2018 11:51:48 +0200 Subject: [PATCH] Add functionality for scrubbing in/out points in the clip list. --- clip_list.h | 2 +- jpeg_frame_view.h | 2 + mainwindow.cpp | 126 +++++++++++++++++++++++++++++++++++++++++++++- mainwindow.h | 15 ++++++ player.cpp | 2 +- 5 files changed, 144 insertions(+), 3 deletions(-) diff --git a/clip_list.h b/clip_list.h index 9db4d07..076d493 100644 --- a/clip_list.h +++ b/clip_list.h @@ -11,7 +11,7 @@ #include "defs.h" struct Clip { - int64_t pts_in = -1, pts_out = -1; + int64_t pts_in = -1, pts_out = -1; // pts_in is inclusive, pts_out is exclusive. std::string descriptions[NUM_CAMERAS]; unsigned stream_idx = 0; // For the playlist only. }; diff --git a/jpeg_frame_view.h b/jpeg_frame_view.h index 4c7648c..6f37bfc 100644 --- a/jpeg_frame_view.h +++ b/jpeg_frame_view.h @@ -31,6 +31,8 @@ public: update_frame(); } + unsigned get_stream_idx() const { return stream_idx; } + void setDecodedFrame(std::shared_ptr frame); protected: diff --git a/mainwindow.cpp b/mainwindow.cpp index ddb08d5..a45b12a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -8,15 +8,20 @@ #include #include +#include +#include #include using namespace std; MainWindow *global_mainwindow = nullptr; -extern int64_t current_pts; ClipList *cliplist_clips; PlayList *playlist_clips; +extern int64_t current_pts; +extern mutex frame_mu; +extern vector frames[MAX_STREAMS]; + MainWindow::MainWindow() : ui(new Ui::MainWindow) { @@ -26,6 +31,9 @@ MainWindow::MainWindow() cliplist_clips = new ClipList(); ui->clip_list->setModel(cliplist_clips); + // For scrubbing in the pts columns. + ui->clip_list->viewport()->installEventFilter(this); + playlist_clips = new PlayList(); ui->playlist->setModel(playlist_clips); @@ -152,3 +160,119 @@ void MainWindow::relayout() { ui->live_display->setMinimumHeight(ui->live_display->width() * 9 / 16); } + +bool MainWindow::eventFilter(QObject *watched, QEvent *event) +{ + constexpr int dead_zone_pixels = 3; // To avoid that simple clicks get misinterpreted. + constexpr int scrub_sensitivity = 100; // pts units per pixel. + constexpr int wheel_sensitivity = 100; // pts units per degree. + + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *mouse = (QMouseEvent *)event; + + int column = ui->clip_list->columnAt(mouse->x()); + int row = ui->clip_list->rowAt(mouse->y()); + if (column == -1 || row == -1) return false; + + if (ClipList::Column(column) == ClipList::Column::IN) { + scrubbing = true; + scrub_row = row; + scrub_column = ClipList::Column::IN; + scrub_x_origin = mouse->x(); + scrub_pts_origin = cliplist_clips->clip(scrub_row)->pts_in; + + unsigned stream_idx = ui->preview_display->get_stream_idx(); + preview_single_frame(scrub_pts_origin, stream_idx, FIRST_AT_OR_AFTER); + } else if (ClipList::Column(column) == ClipList::Column::OUT) { + scrubbing = true; + scrub_row = row; + scrub_column = ClipList::Column::OUT; + scrub_x_origin = mouse->x(); + scrub_pts_origin = cliplist_clips->clip(scrub_row)->pts_out; + + unsigned stream_idx = ui->preview_display->get_stream_idx(); + preview_single_frame(scrub_pts_origin, stream_idx, LAST_BEFORE); + } + } else if (event->type() == QEvent::MouseMove) { + if (scrubbing) { + QMouseEvent *mouse = (QMouseEvent *)event; + int offset = mouse->x() - scrub_x_origin; + int adjusted_offset; + if (offset >= dead_zone_pixels) { + adjusted_offset = offset - dead_zone_pixels; + } else if (offset < -dead_zone_pixels) { + adjusted_offset = offset + dead_zone_pixels; + } else { + adjusted_offset = 0; + } + + unsigned stream_idx = ui->preview_display->get_stream_idx(); + int64_t pts = scrub_pts_origin + adjusted_offset * scrub_sensitivity; + + if (scrub_column == ClipList::Column::IN) { + pts = std::max(pts, 0); + pts = std::min(pts, cliplist_clips->clip(scrub_row)->pts_out); + cliplist_clips->clip(scrub_row)->pts_in = pts; + preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER); + } else { + pts = std::max(pts, cliplist_clips->clip(scrub_row)->pts_in); + pts = std::min(pts, current_pts); + cliplist_clips->clip(scrub_row)->pts_out = pts; + preview_single_frame(pts, stream_idx, LAST_BEFORE); + } + + return true; // Don't use this mouse movement for selecting things. + } + } else if (event->type() == QEvent::Wheel) { + QWheelEvent *wheel = (QWheelEvent *)event; + + int column = ui->clip_list->columnAt(wheel->x()); + int row = ui->clip_list->rowAt(wheel->y()); + if (column == -1 || row == -1) return false; + + ClipProxy clip = cliplist_clips->clip(scrub_row); + unsigned stream_idx = ui->preview_display->get_stream_idx(); + + if (ClipList::Column(column) == ClipList::Column::IN) { + int64_t pts = clip->pts_in + wheel->angleDelta().y() * wheel_sensitivity; + pts = std::max(pts, 0); + pts = std::min(pts, clip->pts_out); + clip->pts_in = pts; + preview_single_frame(pts, stream_idx, FIRST_AT_OR_AFTER); + } else if (ClipList::Column(column) == ClipList::Column::OUT) { + int64_t pts = clip->pts_out + wheel->angleDelta().y() * wheel_sensitivity; + pts = std::max(pts, clip->pts_in); + pts = std::min(pts, current_pts); + clip->pts_out = pts; + preview_single_frame(pts, stream_idx, LAST_BEFORE); + } + } else if (event->type() == QEvent::MouseButtonRelease) { + scrubbing = false; + } + return false; +} + +void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWindow::Rounding rounding) +{ + if (rounding == LAST_BEFORE) { + lock_guard lock(frame_mu); + if (frames[stream_idx].empty()) return; + auto it = lower_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts); + if (it != frames[stream_idx].end()) { + pts = *it; + } + } else { + assert(rounding == FIRST_AT_OR_AFTER); + lock_guard lock(frame_mu); + if (frames[stream_idx].empty()) return; + auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts - 1); + if (it != frames[stream_idx].end()) { + pts = *it; + } + } + + Clip fake_clip; + fake_clip.pts_in = pts; + fake_clip.pts_out = pts + 1; + preview_player->play_clip(fake_clip, stream_idx); +} diff --git a/mainwindow.h b/mainwindow.h index 4d09cb3..7b294b7 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -5,6 +5,8 @@ #include #include +#include "clip_list.h" + namespace Ui { class MainWindow; } // namespace Ui @@ -24,12 +26,25 @@ public: private: Player *preview_player, *live_player; + // State when doing a scrub operation on a timestamp with the mouse. + bool scrubbing = false; + int scrub_x_origin; // In pixels on the viewport. + int64_t scrub_pts_origin; + + // Which element (e.g. pts_in on clip 4) we are scrubbing. + int scrub_row; + ClipList::Column scrub_column; + void queue_clicked(); void preview_clicked(); void play_clicked(); void live_player_clip_done(); + enum Rounding { FIRST_AT_OR_AFTER, LAST_BEFORE }; + void preview_single_frame(int64_t pts, unsigned stream_idx, Rounding rounding); + void resizeEvent(QResizeEvent *event) override; + bool eventFilter(QObject *watched, QEvent *event) override; private slots: void relayout(); diff --git a/player.cpp b/player.cpp index 5d37b28..1f2d9e5 100644 --- a/player.cpp +++ b/player.cpp @@ -38,7 +38,7 @@ void Player::thread_func() steady_clock::time_point origin = steady_clock::now(); int64_t pts_origin = clip.pts_in; - int64_t next_pts = pts_origin; + int64_t next_pts = pts_origin - 1; // Make sure we play the frame at clip.pts_in if it exists. bool aborted = false; for ( ;; ) { -- 2.39.2