X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=mainwindow.cpp;h=dd3c82764537bc23cb588a1dc9978e54db21b667;hb=3795723be95f2fe82f3c8b8b45b1a905b2c811fd;hp=9d0958208d2c742bc123d7f76ccf839b2244bda1;hpb=cfe82eb664cda969e745449dee37bac2e723d609;p=nageru diff --git a/mainwindow.cpp b/mainwindow.cpp index 9d09582..dd3c827 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2,39 +2,41 @@ #include "clip_list.h" #include "disk_space_estimator.h" +#include "flags.h" +#include "frame_on_disk.h" #include "player.h" #include "post_to_main_thread.h" #include "timebase.h" #include "ui_mainwindow.h" -#include -#include - #include -#include #include #include - +#include +#include #include +#include +#include using namespace std; using namespace std::placeholders; MainWindow *global_mainwindow = nullptr; -ClipList *cliplist_clips; -PlayList *playlist_clips; +static ClipList *cliplist_clips; +static PlayList *playlist_clips; extern int64_t current_pts; -extern mutex frame_mu; -extern vector frames[MAX_STREAMS]; MainWindow::MainWindow() : ui(new Ui::MainWindow), - db("futatabi.db") + db(global_flags.working_directory + "/futatabi.db") { global_mainwindow = this; ui->setupUi(this); + // The menus. + connect(ui->exit_action, &QAction::triggered, this, &MainWindow::exit_triggered); + global_disk_space_estimator = new DiskSpaceEstimator(bind(&MainWindow::report_disk_space, this, _1, _2)); disk_free_label = new QLabel(this); disk_free_label->setStyleSheet("QLabel {padding-right: 5px;}"); @@ -50,6 +52,9 @@ MainWindow::MainWindow() ui->playlist->setModel(playlist_clips); connect(playlist_clips, &PlayList::any_content_changed, this, &MainWindow::content_changed); + // For un-highlighting when we lose focus. + ui->clip_list->installEventFilter(this); + // For scrubbing in the pts columns. ui->clip_list->viewport()->installEventFilter(this); ui->playlist->viewport()->installEventFilter(this); @@ -123,15 +128,20 @@ MainWindow::MainWindow() live_player_clip_done(); }); }); - 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_next_clip_callback(bind(&MainWindow::live_player_get_next_clip, this)); + live_player->set_progress_callback([this](const map &progress) { + post_to_main_thread([this, progress] { + live_player_clip_progress(progress); }); }); + set_output_status("paused"); defer_timeout = new QTimer(this); defer_timeout->setSingleShot(true); connect(defer_timeout, &QTimer::timeout, this, &MainWindow::defer_timer_expired); + + connect(ui->clip_list->selectionModel(), &QItemSelectionModel::currentChanged, + this, &MainWindow::clip_list_selection_changed); } void MainWindow::cue_in_clicked() @@ -188,11 +198,23 @@ void MainWindow::queue_clicked() void MainWindow::preview_clicked() { - if (cliplist_clips->empty()) return; + if (ui->playlist->hasFocus()) { + // Allow the playlist as preview iff it has focus and something is selected. + QItemSelectionModel *selected = ui->playlist->selectionModel(); + if (selected->hasSelection()) { + QModelIndex index = selected->currentIndex(); + const Clip &clip = *playlist_clips->clip(index.row()); + preview_player->play_clip(clip, index.row(), clip.stream_idx); + return; + } + } + + if (cliplist_clips->empty()) + return; 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; } @@ -204,7 +226,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) @@ -304,7 +326,8 @@ void MainWindow::state_changed(const StateProto &state) void MainWindow::play_clicked() { - if (playlist_clips->empty()) return; + if (playlist_clips->empty()) + return; QItemSelectionModel *selected = ui->playlist->selectionModel(); int row; @@ -315,7 +338,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(); } @@ -323,37 +347,72 @@ void MainWindow::play_clicked() void MainWindow::live_player_clip_done() { int row = playlist_clips->get_currently_playing(); - if (row != -1 && row < int(playlist_clips->size()) - 1) { - ++row; - const Clip &clip = *playlist_clips->clip(row); - live_player->play_clip(clip, clip.stream_idx); - playlist_clips->set_currently_playing(row, 0.0f); - } else { + 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); - ui->live_label->setText("Current output (paused)"); + } else { + playlist_clips->set_progress({{ row + 1, 0.0f }}); + playlist_clips->set_currently_playing(row + 1, 0.0f); } } -void MainWindow::live_player_clip_progress(double played_this_clip, double total_length) +pair MainWindow::live_player_get_next_clip() { - playlist_clips->set_currently_playing(playlist_clips->get_currently_playing(), played_this_clip / total_length); + // 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(); + 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(make_pair(*playlist_clips->clip(row + 1), row + 1)); + } else { + clip_promise.set_value(make_pair(Clip(), 0)); + } + }); + return clip.get(); +} - double remaining = total_length - played_this_clip; - for (int row = playlist_clips->get_currently_playing() + 1; row < int(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. - } - int remaining_ms = lrint(remaining * 1e3); +static string format_duration(double t) +{ + int t_ms = lrint(t * 1e3); - int ms = remaining_ms % 1000; - remaining_ms /= 1000; - int s = remaining_ms % 60; - remaining_ms /= 60; - int m = remaining_ms; + int ms = t_ms % 1000; + t_ms /= 1000; + int s = t_ms % 60; + t_ms /= 60; + int m = t_ms; char buf[256]; - snprintf(buf, sizeof(buf), "Current output (%d:%02d.%03d left)", m, s, ms); - ui->live_label->setText(buf); + snprintf(buf, sizeof(buf), "%d:%02d.%03d", m, s, ms); + return buf; +} + +void MainWindow::live_player_clip_progress(const map &progress) +{ + 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); + 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"); } void MainWindow::resizeEvent(QResizeEvent *event) @@ -391,6 +450,13 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) unsigned stream_idx = ui->preview_display->get_stream_idx(); + if (watched == ui->clip_list) { + if (event->type() == QEvent::FocusOut) { + highlight_camera_input(-1); + } + return false; + } + if (event->type() != QEvent::Wheel) { last_mousewheel_camera_row = -1; } @@ -412,7 +478,8 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) } int column = destination->columnAt(mouse->x()); int row = destination->rowAt(mouse->y()); - if (column == -1 || row == -1) return false; + if (column == -1 || row == -1) + return false; if (type == SCRUBBING_CLIP_LIST) { if (ClipList::Column(column) == ClipList::Column::IN) { @@ -511,6 +578,12 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) int row = destination->rowAt(wheel->y()); if (column == -1 || row == -1) return false; + // Only adjust pts with the wheel if the given row is selected. + if (!destination->hasFocus() || + row != destination->selectionModel()->currentIndex().row()) { + return false; + } + currently_deferring_model_changes = true; { current_change_id = (watched == ui->clip_list->viewport()) ? "cliplist:" : "playlist:"; @@ -554,6 +627,7 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) } } currently_deferring_model_changes = false; + return true; // Don't scroll. } else if (event->type() == QEvent::MouseButtonRelease) { scrubbing = false; } @@ -564,25 +638,29 @@ void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWind { 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 (frames[stream_idx].empty()) + return; + auto it = lower_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts, + [](const FrameOnDisk &frame, int64_t pts) { return frame.pts < pts; }); if (it != frames[stream_idx].end()) { - pts = *it; + pts = it->pts; } } 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 (frames[stream_idx].empty()) + return; + auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts - 1, + [](int64_t pts, const FrameOnDisk &frame) { return pts < frame.pts; }); if (it != frames[stream_idx].end()) { - pts = *it; + pts = it->pts; } } 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() @@ -596,6 +674,27 @@ void MainWindow::playlist_selection_changed() ui->playlist_move_down_btn->setEnabled( any_selected && selected->selectedRows().back().row() < int(playlist_clips->size()) - 1); ui->play_btn->setEnabled(!playlist_clips->empty()); + + if (!any_selected) { + set_output_status("paused"); + } else { + double remaining = 0.0; + for (int row = selected->selectedRows().front().row(); row < int(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. + } + set_output_status(format_duration(remaining) + " ready"); + } +} + +void MainWindow::clip_list_selection_changed(const QModelIndex ¤t, const QModelIndex &) +{ + int camera_selected = -1; + if (current.column() >= int(ClipList::Column::CAMERA_1) && + current.column() <= int(ClipList::Column::CAMERA_4)) { + camera_selected = current.column() - int(ClipList::Column::CAMERA_1); + } + highlight_camera_input(camera_selected); } void MainWindow::report_disk_space(off_t free_bytes, double estimated_seconds_left) @@ -625,9 +724,50 @@ void MainWindow::report_disk_space(off_t free_bytes, double estimated_seconds_le std::string label = buf; - post_to_main_thread([this, label]{ - disk_free_label->setText(QString::fromStdString(label)); - ui->menuBar->setCornerWidget(disk_free_label); // Need to set this again for the sizing to get right. - }); + post_to_main_thread([this, label] { + disk_free_label->setText(QString::fromStdString(label)); + ui->menuBar->setCornerWidget(disk_free_label); // Need to set this again for the sizing to get right. + }); +} + +void MainWindow::exit_triggered() +{ + close(); } +void MainWindow::highlight_camera_input(int stream_idx) +{ + if (stream_idx == 0) { + ui->input1_frame->setStyleSheet("background: rgb(0,255,0)"); + } else { + ui->input1_frame->setStyleSheet(""); + } + if (stream_idx == 1) { + ui->input2_frame->setStyleSheet("background: rgb(0,255,0)"); + } else { + ui->input2_frame->setStyleSheet(""); + } + if (stream_idx == 2) { + ui->input3_frame->setStyleSheet("background: rgb(0,255,0)"); + } else { + ui->input3_frame->setStyleSheet(""); + } + if (stream_idx == 3) { + ui->input4_frame->setStyleSheet("background: rgb(0,255,0)"); + } else { + ui->input4_frame->setStyleSheet(""); + } +} + +void MainWindow::set_output_status(const string &status) +{ + ui->live_label->setText(QString::fromStdString("Current output (" + status + ")")); + + lock_guard lock(queue_status_mu); + queue_status = status; +} + +pair MainWindow::get_queue_status() const { + lock_guard lock(queue_status_mu); + return {queue_status, "text/plain"}; +}