From 60232cc3b83499d67ade40cbd403e04747b64795 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 28 Oct 2018 17:17:00 +0100 Subject: [PATCH] Fix displaying of progress bars and time remaining in the presence of fades. --- clip_list.cpp | 25 ++++++++++++++++++++++-- clip_list.h | 4 ++++ mainwindow.cpp | 53 +++++++++++++++++++++++++++++++++----------------- mainwindow.h | 4 ++-- player.cpp | 26 ++++++++++++++++++++----- player.h | 8 +++++--- 6 files changed, 90 insertions(+), 30 deletions(-) diff --git a/clip_list.cpp b/clip_list.cpp index 5ee36df..14f083e 100644 --- a/clip_list.cpp +++ b/clip_list.cpp @@ -142,7 +142,10 @@ QVariant PlayList::data(const QModelIndex &parent, int role) const } if (role == Qt::BackgroundRole) { if (Column(column) == Column::PLAYING) { - if (row == currently_playing_index) { + auto it = current_progress.find(row); + if (it != current_progress.end()) { + double play_progress = it->second; + // This only really works well for the first column, for whatever odd Qt reason. QLinearGradient grad(QPointF(0, 0), QPointF(1, 0)); grad.setCoordinateMode(grad.QGradient::ObjectBoundingMode); @@ -165,7 +168,7 @@ QVariant PlayList::data(const QModelIndex &parent, int role) const switch (Column(column)) { case Column::PLAYING: - return (row == currently_playing_index) ? "→" : ""; + return current_progress.count(row) ? "→" : ""; case Column::IN: return QString::fromStdString(pts_to_string(clips[row].pts_in)); case Column::OUT: @@ -431,6 +434,24 @@ void PlayList::set_currently_playing(int index, double progress) } } +void PlayList::set_progress(const map &progress) +{ + const int column = int(Column::PLAYING); + map old_progress = move(this->current_progress); + this->current_progress = progress; + + for (auto it : old_progress) { + size_t index = it.first; + if (current_progress.count(index) == 0) { + emit dataChanged(this->index(index, column), this->index(index, column)); + } + } + for (auto it : current_progress) { + size_t index = it.first; + emit dataChanged(this->index(index, column), this->index(index, column)); + } +} + namespace { Clip deserialize_clip(const ClipProto &clip_proto) diff --git a/clip_list.h b/clip_list.h index 6644982..4da531f 100644 --- a/clip_list.h +++ b/clip_list.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -131,6 +132,8 @@ public: void set_currently_playing(int index, double progress); // -1 = none. int get_currently_playing() const { return currently_playing_index; } + void set_progress(const std::map &progress); + ClipListProto serialize() const; void emit_data_changed(size_t row) override; @@ -142,6 +145,7 @@ private: std::vector clips; int currently_playing_index = -1; double play_progress = 0.0; + std::map current_progress; }; #endif // !defined (_CLIP_LIST_H) diff --git a/mainwindow.cpp b/mainwindow.cpp index 6e999ba..ee300c5 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -130,9 +130,9 @@ MainWindow::MainWindow() }); }); live_player->set_next_clip_callback(bind(&MainWindow::live_player_get_next_clip, this)); - live_player->set_progress_callback([this](double played_this_clip, double total_length) { - post_to_main_thread([this, played_this_clip, total_length] { - live_player_clip_progress(played_this_clip, total_length); + live_player->set_progress_callback([this](const map &progress) { + post_to_main_thread([this, progress] { + live_player_clip_progress(progress); }); }); set_output_status("paused"); @@ -204,7 +204,7 @@ void MainWindow::preview_clicked() QItemSelectionModel *selected = ui->clip_list->selectionModel(); if (!selected->hasSelection()) { - preview_player->play_clip(*cliplist_clips->back(), 0); + preview_player->play_clip(*cliplist_clips->back(), cliplist_clips->size() - 1, 0); return; } @@ -216,7 +216,7 @@ void MainWindow::preview_clicked() } else { stream_idx = ui->preview_display->get_stream_idx(); } - preview_player->play_clip(*cliplist_clips->clip(index.row()), stream_idx); + preview_player->play_clip(*cliplist_clips->clip(index.row()), index.row(), stream_idx); } void MainWindow::preview_angle_clicked(unsigned stream_idx) @@ -328,7 +328,8 @@ void MainWindow::play_clicked() } const Clip &clip = *playlist_clips->clip(row); - live_player->play_clip(clip, clip.stream_idx); + live_player->play_clip(clip, row, clip.stream_idx); + playlist_clips->set_progress({{ row, 0.0f }}); playlist_clips->set_currently_playing(row, 0.0f); playlist_selection_changed(); } @@ -338,24 +339,26 @@ void MainWindow::live_player_clip_done() int row = playlist_clips->get_currently_playing(); if (row == -1 || row == int(playlist_clips->size()) - 1) { set_output_status("paused"); + playlist_clips->set_progress({}); playlist_clips->set_currently_playing(-1, 0.0f); } else { + playlist_clips->set_progress({{ row + 1, 0.0f }}); playlist_clips->set_currently_playing(row + 1, 0.0f); } } -Clip MainWindow::live_player_get_next_clip() +pair MainWindow::live_player_get_next_clip() { // playlist_clips can only be accessed on the main thread. // Hopefully, we won't have to wait too long for this to come back. - promise clip_promise; - future clip = clip_promise.get_future(); + promise> clip_promise; + future> clip = clip_promise.get_future(); post_to_main_thread([this, &clip_promise] { int row = playlist_clips->get_currently_playing(); if (row != -1 && row < int(playlist_clips->size()) - 1) { - clip_promise.set_value(*playlist_clips->clip(row + 1)); + clip_promise.set_value(make_pair(*playlist_clips->clip(row + 1), row + 1)); } else { - clip_promise.set_value(Clip()); + clip_promise.set_value(make_pair(Clip(), 0)); } }); return clip.get(); @@ -376,14 +379,28 @@ static string format_duration(double t) return buf; } -void MainWindow::live_player_clip_progress(double played_this_clip, double total_length) +void MainWindow::live_player_clip_progress(const map &progress) { - playlist_clips->set_currently_playing(playlist_clips->get_currently_playing(), played_this_clip / total_length); - - double remaining = total_length - played_this_clip; - for (int row = playlist_clips->get_currently_playing() + 1; row < int(playlist_clips->size()); ++row) { + playlist_clips->set_progress(progress); + + // Look at the last clip and then start counting from there. + assert(!progress.empty()); + auto last_it = progress.end(); + --last_it; + double remaining = 0.0; + double last_fade_time_seconds = 0.0; + for (size_t row = last_it->first; row < playlist_clips->size(); ++row) { const Clip clip = *playlist_clips->clip(row); - remaining += double(clip.pts_out - clip.pts_in) / TIMEBASE / 0.5; // FIXME: stop hardcoding speed. + double clip_length = double(clip.pts_out - clip.pts_in) / TIMEBASE / 0.5; // FIXME: stop hardcoding speed. + if (row == last_it->first) { + // A clip we're playing: Subtract the part we've already played. + remaining = clip_length * (1.0 - last_it->second); + } else { + // A clip we haven't played yet: Subtract the part that's overlapping + // with a previous clip (due to fade). + remaining += max(clip_length - last_fade_time_seconds, 0.0); + } + last_fade_time_seconds = min(clip_length, clip.fade_time_seconds); } set_output_status(format_duration(remaining) + " left"); } @@ -631,7 +648,7 @@ void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWind Clip fake_clip; fake_clip.pts_in = pts; fake_clip.pts_out = pts + 1; - preview_player->play_clip(fake_clip, stream_idx); + preview_player->play_clip(fake_clip, 0, stream_idx); } void MainWindow::playlist_selection_changed() diff --git a/mainwindow.h b/mainwindow.h index 16a4baa..7f8c57a 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -76,8 +76,8 @@ private: void preview_angle_clicked(unsigned stream_idx); void play_clicked(); void live_player_clip_done(); - Clip live_player_get_next_clip(); - void live_player_clip_progress(double played_this_clip, double total_length); + std::pair live_player_get_next_clip(); + void live_player_clip_progress(const std::map &progress); void set_output_status(const std::string &status); void playlist_duplicate(); void playlist_remove(); diff --git a/player.cpp b/player.cpp index 2fef073..1c26f60 100644 --- a/player.cpp +++ b/player.cpp @@ -50,6 +50,7 @@ void Player::thread_func(bool also_output_to_stream) constexpr double output_framerate = 60000.0 / 1001.0; // FIXME: make configurable int64_t pts = 0; Clip next_clip; + size_t next_clip_idx = size_t(-1); bool got_next_clip = false; double next_clip_fade_time = -1.0; @@ -79,10 +80,12 @@ wait_for_clip: } Clip clip; + size_t clip_idx; unsigned stream_idx; { lock_guard lock(mu); clip = current_clip; + clip_idx = current_clip_idx; stream_idx = current_stream_idx; } steady_clock::time_point origin = steady_clock::now(); // TODO: Add a 100 ms buffer for ramp-up? @@ -130,7 +133,7 @@ got_clip: double time_left_this_clip = double(clip.pts_out - in_pts) / TIMEBASE / speed; if (!got_next_clip && next_clip_callback != nullptr && time_left_this_clip <= clip.fade_time_seconds) { // Find the next clip so that we can begin a fade. - next_clip = next_clip_callback(); + tie(next_clip, next_clip_idx) = next_clip_callback(); if (next_clip.pts_in != -1) { got_next_clip = true; @@ -140,6 +143,9 @@ got_clip: } } + // pts not affected by the swapping below. + int64_t in_pts_for_progress = in_pts, in_pts_secondary_for_progress = -1; + int primary_stream_idx = stream_idx; int secondary_stream_idx = -1; int64_t secondary_pts = -1; @@ -148,6 +154,7 @@ got_clip: if (got_next_clip && time_left_this_clip <= next_clip_fade_time) { secondary_stream_idx = next_clip.stream_idx; in_pts_secondary = lrint(next_clip.pts_in + (next_clip_fade_time - time_left_this_clip) * TIMEBASE * speed); + in_pts_secondary_for_progress = in_pts_secondary; fade_alpha = 1.0f - time_left_this_clip / next_clip_fade_time; // If more than half-way through the fade, interpolate the next clip @@ -169,9 +176,16 @@ got_clip: if (progress_callback != nullptr) { // NOTE: None of this will take into account any snapping done below. - double played_this_clip = double(in_pts - clip.pts_in) / TIMEBASE / speed; + double played_this_clip = double(in_pts_for_progress - clip.pts_in) / TIMEBASE / speed; double total_length = double(clip.pts_out - clip.pts_in) / TIMEBASE / speed; - progress_callback(played_this_clip, total_length); + map progress{{ clip_idx, played_this_clip / total_length }}; + + if (got_next_clip && time_left_this_clip <= next_clip_fade_time) { + double played_next_clip = double(in_pts_secondary_for_progress - next_clip.pts_in) / TIMEBASE / speed; + double total_next_length = double(next_clip.pts_out - next_clip.pts_in) / TIMEBASE / speed; + progress[next_clip_idx] = played_next_clip / total_next_length; + } + progress_callback(progress); } int64_t in_pts_lower, in_pts_upper; @@ -285,7 +299,7 @@ got_clip: // Last-ditch effort to get the next clip (if e.g. the fade time was zero seconds). if (!got_next_clip && next_clip_callback != nullptr) { - next_clip = next_clip_callback(); + tie(next_clip, next_clip_idx) = next_clip_callback(); if (next_clip.pts_in != -1) { got_next_clip = true; in_pts_start_next_clip = next_clip.pts_in; @@ -295,6 +309,7 @@ got_clip: // Switch to next clip if we got it. if (got_next_clip) { clip = next_clip; + clip_idx = next_clip_idx; stream_idx = next_clip.stream_idx; // Override is used for previews only, and next_clip is used for live ony. if (done_callback != nullptr) { done_callback(); @@ -348,12 +363,13 @@ Player::Player(JPEGFrameView *destination, bool also_output_to_stream) thread(&Player::thread_func, this, also_output_to_stream).detach(); } -void Player::play_clip(const Clip &clip, unsigned stream_idx) +void Player::play_clip(const Clip &clip, size_t clip_idx, unsigned stream_idx) { { lock_guard lock(mu); current_clip = clip; current_stream_idx = stream_idx; + current_clip_idx = clip_idx; } { diff --git a/player.h b/player.h index fb9f4a0..aa50ade 100644 --- a/player.h +++ b/player.h @@ -21,7 +21,7 @@ class Player : public QueueInterface { public: Player(JPEGFrameView *destination, bool also_output_to_stream); - void play_clip(const Clip &clip, unsigned stream_idx); + void play_clip(const Clip &clip, size_t clip_idx, unsigned stream_idx); void override_angle(unsigned stream_idx); // For the current clip only. // Not thread-safe to set concurrently with playing. @@ -31,12 +31,13 @@ public: // Not thread-safe to set concurrently with playing. // Will be called back from the player thread. - using next_clip_callback_func = std::function; + // The second parameter is the clip's position in the play list. + using next_clip_callback_func = std::function()>; void set_next_clip_callback(next_clip_callback_func cb) { next_clip_callback = cb; } // Not thread-safe to set concurrently with playing. // Will be called back from the player thread. - using progress_callback_func = std::function; + using progress_callback_func = std::function &progress)>; void set_progress_callback(progress_callback_func cb) { progress_callback = cb; } // QueueInterface. @@ -60,6 +61,7 @@ private: std::mutex mu; Clip current_clip; // Under mu. Can have pts_in = -1 for no clip. + size_t current_clip_idx; // Under mu. unsigned current_stream_idx; // Under mu. std::mutex queue_state_mu; -- 2.39.2