Makes progress display more robust in the face of concurrent edits.
}
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;
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:
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: {
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;
}
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;
}
if (!ok || !(val >= 0.001)) {
return false;
}
- clips[row].speed = val;
+ clips[row].clip.speed = val;
emit_data_changed(row);
return true;
}
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();
}
emit any_content_changed();
}
-void PlayList::set_progress(const map<size_t, double> &progress)
+void PlayList::set_progress(const map<uint64_t, double> &progress)
{
const int column = int(Column::PLAYING);
- map<size_t, double> old_progress = move(this->current_progress);
+ map<uint64_t, double> 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 {
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;
}
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:
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<size_t, double> &progress);
+ void set_progress(const std::map<uint64_t, double> &progress);
ClipListProto serialize() const;
void any_content_changed();
private:
- std::vector<Clip> clips;
+ std::vector<ClipWithID> clips;
double play_progress = 0.0;
- std::map<size_t, double> current_progress;
+ std::map<uint64_t, double> 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)
progress.setMaximum(100000);
progress.setValue(0);
- vector<Player::ClipWithRow> clips_with_row;
+ vector<ClipWithID> 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<void> done_promise;
future<void> done = done_promise.get_future();
player.set_done_callback([&done_promise] {
done_promise.set_value();
});
- player.set_progress_callback([¤t_value, &clips, total_length](const std::map<size_t, double> &player_progress, double time_remaining) {
+ player.set_progress_callback([¤t_value, &clips, total_length](const std::map<uint64_t, double> &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));
}
live_player_done();
});
});
- live_player->set_progress_callback([this](const map<size_t, double> &progress, double time_remaining) {
+ live_player->set_progress_callback([this](const map<uint64_t, double> &progress, double time_remaining) {
post_to_main_thread([this, progress, time_remaining] {
live_player_clip_progress(progress, time_remaining);
});
start_row = selected->selectedRows(0)[0].row();
}
- vector<Player::ClipWithRow> clips;
+ vector<ClipWithID> 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 } });
Clip fake_clip;
fake_clip.pts_in = 0;
fake_clip.pts_out = 0;
+ playlist_clips->set_progress({});
live_player->play(fake_clip);
}
return buf;
}
-void MainWindow::live_player_clip_progress(const map<size_t, double> &progress, double time_remaining)
+void MainWindow::live_player_clip_progress(const map<uint64_t, double> &progress, double time_remaining)
{
playlist_clips->set_progress(progress);
set_output_status(format_duration(time_remaining) + " left");
if (!any_selected) {
set_output_status("paused");
} else {
- vector<Player::ClipWithRow> clips;
+ vector<ClipWithID> 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");
void play_clicked();
void stop_clicked();
void live_player_done();
- void live_player_clip_progress(const std::map<size_t, double> &progress, double time_remaining);
+ void live_player_clip_progress(const std::map<uint64_t, double> &progress, double time_remaining);
void set_output_status(const std::string &status);
void playlist_duplicate();
void playlist_remove();
void Player::play_playlist_once()
{
- vector<ClipWithRow> clip_list;
+ vector<ClipWithID> clip_list;
bool clip_ready;
steady_clock::time_point before_sleep = steady_clock::now();
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<size_t, double> progress{ { clip_list[clip_idx].row, clip_progress } };
+ map<uint64_t, double> 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);
player_thread.join();
}
-void Player::play(const vector<Player::ClipWithRow> &clips)
+void Player::play(const vector<ClipWithID> &clips)
{
lock_guard<mutex> lock(queue_state_mu);
new_clip_ready = true;
new_clip_changed.notify_all();
}
-double compute_time_left(const vector<Player::ClipWithRow> &clips, size_t currently_playing_idx, double progress_currently_playing)
+double compute_time_left(const vector<ClipWithID> &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;
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<ClipWithRow> &clips);
+ void play(const std::vector<ClipWithID> &clips);
void override_angle(unsigned stream_idx); // Assumes one-clip playlist only.
// Not thread-safe to set concurrently with playing.
// 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<void(const std::map<size_t, double> &progress, double time_remaining)>;
+ using progress_callback_func = std::function<void(const std::map<uint64_t, double> &progress, double time_remaining)>;
void set_progress_callback(progress_callback_func cb) { progress_callback = cb; }
// QueueInterface.
std::mutex queue_state_mu;
std::condition_variable new_clip_changed;
- std::vector<ClipWithRow> queued_clip_list; // Under queue_state_mu.
+ std::vector<ClipWithID> 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.
const StreamOutput stream_output;
};
-double compute_time_left(const std::vector<Player::ClipWithRow> &clips, size_t currently_playing_idx, double progress_currently_playing);
+double compute_time_left(const std::vector<ClipWithID> &clips, size_t currently_playing_idx, double progress_currently_playing);
-static inline double compute_total_time(const std::vector<Player::ClipWithRow> &clips)
+static inline double compute_total_time(const std::vector<ClipWithID> &clips)
{
return compute_time_left(clips, 0, 0.0);
}