From c6fb9649d9f9c2e2cf3d1ac16c6c359630fe72bf Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Fri, 28 Dec 2018 18:09:35 +0100 Subject: [PATCH] Give each clip in the playlist a stable ID. Makes progress display more robust in the face of concurrent edits. --- futatabi/clip_list.cpp | 54 +++++++++++++++++++---------------------- futatabi/clip_list.h | 16 ++++++++---- futatabi/export.cpp | 10 ++++---- futatabi/mainwindow.cpp | 13 +++++----- futatabi/mainwindow.h | 2 +- futatabi/player.cpp | 10 ++++---- futatabi/player.h | 16 +++++------- 7 files changed, 60 insertions(+), 61 deletions(-) diff --git a/futatabi/clip_list.cpp b/futatabi/clip_list.cpp index 0bb73ae..7e554b0 100644 --- a/futatabi/clip_list.cpp +++ b/futatabi/clip_list.cpp @@ -141,7 +141,7 @@ QVariant PlayList::data(const QModelIndex &parent, int role) const } if (role == Qt::BackgroundRole) { if (Column(column) == Column::PLAYING) { - auto it = current_progress.find(row); + auto it = current_progress.find(clips[row].id); if (it != current_progress.end()) { double play_progress = it->second; @@ -167,37 +167,37 @@ QVariant PlayList::data(const QModelIndex &parent, int role) const switch (Column(column)) { case Column::PLAYING: - return current_progress.count(row) ? "→" : ""; + return current_progress.count(clips[row].id) ? "→" : ""; case Column::IN: - return QString::fromStdString(pts_to_string(clips[row].pts_in)); + return QString::fromStdString(pts_to_string(clips[row].clip.pts_in)); case Column::OUT: - if (clips[row].pts_out >= 0) { - return QString::fromStdString(pts_to_string(clips[row].pts_out)); + if (clips[row].clip.pts_out >= 0) { + return QString::fromStdString(pts_to_string(clips[row].clip.pts_out)); } else { return QVariant(); } case Column::DURATION: - if (clips[row].pts_out >= 0) { - return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in)); + if (clips[row].clip.pts_out >= 0) { + return QString::fromStdString(duration_to_string(clips[row].clip.pts_out - clips[row].clip.pts_in)); } else { return QVariant(); } case Column::CAMERA: - return qlonglong(clips[row].stream_idx + 1); + return qlonglong(clips[row].clip.stream_idx + 1); case Column::DESCRIPTION: - return QString::fromStdString(clips[row].descriptions[clips[row].stream_idx]); + return QString::fromStdString(clips[row].clip.descriptions[clips[row].clip.stream_idx]); case Column::FADE_TIME: { stringstream ss; ss.imbue(locale("C")); ss.precision(3); - ss << fixed << clips[row].fade_time_seconds; + ss << fixed << clips[row].clip.fade_time_seconds; return QString::fromStdString(ss.str()); } case Column::SPEED: { stringstream ss; ss.imbue(locale("C")); ss.precision(3); - ss << fixed << clips[row].speed; + ss << fixed << clips[row].clip.speed; return QString::fromStdString(ss.str()); } default: @@ -323,7 +323,7 @@ bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role switch (Column(column)) { case Column::DESCRIPTION: - clips[row].descriptions[clips[row].stream_idx] = value.toString().toStdString(); + clips[row].clip.descriptions[clips[row].clip.stream_idx] = value.toString().toStdString(); emit_data_changed(row); return true; case Column::CAMERA: { @@ -332,7 +332,7 @@ bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role if (!ok || camera_idx < 1 || camera_idx > int(num_cameras)) { return false; } - clips[row].stream_idx = camera_idx - 1; + clips[row].clip.stream_idx = camera_idx - 1; emit_data_changed(row); return true; } @@ -342,7 +342,7 @@ bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role if (!ok || !(val >= 0.0)) { return false; } - clips[row].fade_time_seconds = val; + clips[row].clip.fade_time_seconds = val; emit_data_changed(row); return true; } @@ -352,7 +352,7 @@ bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role if (!ok || !(val >= 0.001)) { return false; } - clips[row].speed = val; + clips[row].clip.speed = val; emit_data_changed(row); return true; } @@ -372,7 +372,7 @@ void ClipList::add_clip(const Clip &clip) void PlayList::add_clip(const Clip &clip) { beginInsertRows(QModelIndex(), clips.size(), clips.size()); - clips.push_back(clip); + clips.emplace_back(ClipWithID{ clip, clip_counter++ }); endInsertRows(); emit any_content_changed(); } @@ -433,22 +433,18 @@ void ClipList::change_num_cameras(size_t num_cameras) emit any_content_changed(); } -void PlayList::set_progress(const map &progress) +void PlayList::set_progress(const map &progress) { const int column = int(Column::PLAYING); - map old_progress = move(this->current_progress); + 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 (size_t row = 0; row < clips.size(); ++row) { + uint64_t id = clips[row].id; + if (current_progress.count(id) || old_progress.count(id)) { + emit dataChanged(this->index(row, column), this->index(row, column)); } } - for (auto it : current_progress) { - size_t index = it.first; - emit dataChanged(this->index(index, column), this->index(index, column)); - } } namespace { @@ -504,15 +500,15 @@ ClipListProto ClipList::serialize() const PlayList::PlayList(const ClipListProto &serialized) { for (const ClipProto &clip_proto : serialized.clip()) { - clips.push_back(deserialize_clip(clip_proto)); + clips.emplace_back(ClipWithID{ deserialize_clip(clip_proto), clip_counter++ }); } } ClipListProto PlayList::serialize() const { ClipListProto ret; - for (const Clip &clip : clips) { - serialize_clip(clip, ret.add_clip()); + for (const ClipWithID &clip : clips) { + serialize_clip(clip.clip, ret.add_clip()); } return ret; } diff --git a/futatabi/clip_list.h b/futatabi/clip_list.h index 99fdf53..5373ce0 100644 --- a/futatabi/clip_list.h +++ b/futatabi/clip_list.h @@ -19,6 +19,10 @@ struct Clip { double fade_time_seconds = 0.5; double speed = 0.5; }; +struct ClipWithID { + Clip clip; + uint64_t id; // Used for progress callback only. Immutable. +}; class DataChangedReceiver { public: @@ -131,13 +135,14 @@ public: size_t size() const { return clips.size(); } bool empty() const { return clips.empty(); } - ClipProxy mutable_clip(size_t index) { return ClipProxy(clips[index], this, index); } - const Clip *clip(size_t index) const { return &clips[index]; } + ClipProxy mutable_clip(size_t index) { return ClipProxy(clips[index].clip, this, index); } + const Clip *clip(size_t index) const { return &clips[index].clip; } + const ClipWithID *clip_with_id(size_t index) const { return &clips[index]; } ClipProxy mutable_back() { return mutable_clip(size() - 1); } const Clip *back() const { return clip(size() - 1); } - void set_progress(const std::map &progress); + void set_progress(const std::map &progress); ClipListProto serialize() const; @@ -152,10 +157,11 @@ signals: void any_content_changed(); private: - std::vector clips; + std::vector clips; double play_progress = 0.0; - std::map current_progress; + std::map current_progress; size_t num_cameras = 2; + uint64_t clip_counter = 1000000; // Used for generating IDs. Starting at a high number to avoid any kind of bugs treating IDs as rows. }; #endif // !defined (_CLIP_LIST_H) diff --git a/futatabi/export.cpp b/futatabi/export.cpp index 5481007..50fc950 100644 --- a/futatabi/export.cpp +++ b/futatabi/export.cpp @@ -223,11 +223,11 @@ void export_interpolated_clip(const string &filename, const vector &clips) progress.setMaximum(100000); progress.setValue(0); - vector clips_with_row; + vector clips_with_id; for (const Clip &clip : clips) { - clips_with_row.emplace_back(Player::ClipWithRow{ clip, 0 }); + clips_with_id.emplace_back(ClipWithID{ clip, 0 }); } - double total_length = compute_total_time(clips_with_row); + double total_length = compute_total_time(clips_with_id); promise done_promise; future done = done_promise.get_future(); @@ -237,10 +237,10 @@ void export_interpolated_clip(const string &filename, const vector &clips) player.set_done_callback([&done_promise] { done_promise.set_value(); }); - player.set_progress_callback([¤t_value, &clips, total_length](const std::map &player_progress, double time_remaining) { + player.set_progress_callback([¤t_value, &clips, total_length](const std::map &player_progress, double time_remaining) { current_value = 1.0 - time_remaining / total_length; }); - player.play(clips_with_row); + player.play(clips_with_id); while (done.wait_for(std::chrono::milliseconds(100)) != future_status::ready && !progress.wasCanceled()) { progress.setValue(lrint(100000.0 * current_value)); } diff --git a/futatabi/mainwindow.cpp b/futatabi/mainwindow.cpp index 3d3d8c1..346c6f6 100644 --- a/futatabi/mainwindow.cpp +++ b/futatabi/mainwindow.cpp @@ -189,7 +189,7 @@ MainWindow::MainWindow() live_player_done(); }); }); - live_player->set_progress_callback([this](const map &progress, double time_remaining) { + live_player->set_progress_callback([this](const map &progress, double time_remaining) { post_to_main_thread([this, progress, time_remaining] { live_player_clip_progress(progress, time_remaining); }); @@ -487,9 +487,9 @@ void MainWindow::play_clicked() start_row = selected->selectedRows(0)[0].row(); } - vector clips; + vector clips; for (unsigned row = start_row; row < playlist_clips->size(); ++row) { - clips.emplace_back(Player::ClipWithRow{ *playlist_clips->clip(row), row }); + clips.emplace_back(*playlist_clips->clip_with_id(row)); } live_player->play(clips); playlist_clips->set_progress({ { start_row, 0.0f } }); @@ -503,6 +503,7 @@ void MainWindow::stop_clicked() Clip fake_clip; fake_clip.pts_in = 0; fake_clip.pts_out = 0; + playlist_clips->set_progress({}); live_player->play(fake_clip); } @@ -528,7 +529,7 @@ static string format_duration(double t) return buf; } -void MainWindow::live_player_clip_progress(const map &progress, double time_remaining) +void MainWindow::live_player_clip_progress(const map &progress, double time_remaining) { playlist_clips->set_progress(progress); set_output_status(format_duration(time_remaining) + " left"); @@ -813,9 +814,9 @@ void MainWindow::playlist_selection_changed() if (!any_selected) { set_output_status("paused"); } else { - vector clips; + vector clips; for (size_t row = selected->selectedRows().front().row(); row < playlist_clips->size(); ++row) { - clips.emplace_back(Player::ClipWithRow{ *playlist_clips->clip(row), row }); + clips.emplace_back(*playlist_clips->clip_with_id(row)); } double remaining = compute_total_time(clips); set_output_status(format_duration(remaining) + " ready"); diff --git a/futatabi/mainwindow.h b/futatabi/mainwindow.h index f93da89..8d28611 100644 --- a/futatabi/mainwindow.h +++ b/futatabi/mainwindow.h @@ -103,7 +103,7 @@ private: void play_clicked(); void stop_clicked(); void live_player_done(); - void live_player_clip_progress(const std::map &progress, double time_remaining); + void live_player_clip_progress(const std::map &progress, double time_remaining); void set_output_status(const std::string &status); void playlist_duplicate(); void playlist_remove(); diff --git a/futatabi/player.cpp b/futatabi/player.cpp index 0d9e092..263edbf 100644 --- a/futatabi/player.cpp +++ b/futatabi/player.cpp @@ -67,7 +67,7 @@ double calc_progress(const Clip &clip, int64_t pts) void Player::play_playlist_once() { - vector clip_list; + vector clip_list; bool clip_ready; steady_clock::time_point before_sleep = steady_clock::now(); @@ -183,11 +183,11 @@ void Player::play_playlist_once() if (progress_callback != nullptr) { // NOTE: None of this will take into account any snapping done below. double clip_progress = calc_progress(clip, in_pts_for_progress); - map progress{ { clip_list[clip_idx].row, clip_progress } }; + map progress{ { clip_list[clip_idx].id, clip_progress } }; double time_remaining; if (next_clip != nullptr && time_left_this_clip <= next_clip_fade_time) { double next_clip_progress = calc_progress(*next_clip, in_pts_secondary_for_progress); - progress[clip_list[clip_idx + 1].row] = next_clip_progress; + progress[clip_list[clip_idx + 1].id] = next_clip_progress; time_remaining = compute_time_left(clip_list, clip_idx + 1, next_clip_progress); } else { time_remaining = compute_time_left(clip_list, clip_idx, clip_progress); @@ -421,7 +421,7 @@ Player::~Player() player_thread.join(); } -void Player::play(const vector &clips) +void Player::play(const vector &clips) { lock_guard lock(queue_state_mu); new_clip_ready = true; @@ -482,7 +482,7 @@ void Player::release_queue_spot() new_clip_changed.notify_all(); } -double compute_time_left(const vector &clips, size_t currently_playing_idx, double progress_currently_playing) +double compute_time_left(const vector &clips, size_t currently_playing_idx, double progress_currently_playing) { // Look at the last clip and then start counting from there. double remaining = 0.0; diff --git a/futatabi/player.h b/futatabi/player.h index c8ed083..d2e861e 100644 --- a/futatabi/player.h +++ b/futatabi/player.h @@ -30,15 +30,11 @@ public: Player(JPEGFrameView *destination, StreamOutput stream_output, AVFormatContext *file_avctx = nullptr); ~Player(); - struct ClipWithRow { - Clip clip; - size_t row; // Used for progress callback only. - }; void play(const Clip &clip) { - play({ ClipWithRow{ clip, 0 } }); + play({ ClipWithID{ clip, 0 } }); } - void play(const std::vector &clips); + void play(const std::vector &clips); void override_angle(unsigned stream_idx); // Assumes one-clip playlist only. // Not thread-safe to set concurrently with playing. @@ -49,7 +45,7 @@ public: // Not thread-safe to set concurrently with playing. // Will be called back from the player thread. // The keys in the given map are row members in the vector given to play(). - using progress_callback_func = std::function &progress, double time_remaining)>; + using progress_callback_func = std::function &progress, double time_remaining)>; void set_progress_callback(progress_callback_func cb) { progress_callback = cb; } // QueueInterface. @@ -77,7 +73,7 @@ private: std::mutex queue_state_mu; std::condition_variable new_clip_changed; - std::vector queued_clip_list; // Under queue_state_mu. + std::vector queued_clip_list; // Under queue_state_mu. bool new_clip_ready = false; // Under queue_state_mu. bool playing = false; // Under queue_state_mu. int override_stream_idx = -1; // Under queue_state_mu. @@ -105,9 +101,9 @@ private: const StreamOutput stream_output; }; -double compute_time_left(const std::vector &clips, size_t currently_playing_idx, double progress_currently_playing); +double compute_time_left(const std::vector &clips, size_t currently_playing_idx, double progress_currently_playing); -static inline double compute_total_time(const std::vector &clips) +static inline double compute_total_time(const std::vector &clips) { return compute_time_left(clips, 0, 0.0); } -- 2.39.2