int ClipList::columnCount(const QModelIndex &parent) const {
if (parent.isValid()) return 0;
- return Column::NUM_COLUMNS;
+ if (display_type == ListDisplay::CLIP_LIST) {
+ return int(ClipListColumn::NUM_COLUMNS);
+ } else {
+ return int(PlayListColumn::NUM_COLUMNS);
+ }
}
QVariant ClipList::data(const QModelIndex &parent, int role) const {
if (orientation != Qt::Horizontal)
return QVariant();
- switch (section) {
- case Column::IN:
- return "In";
- case Column::OUT:
- return "Out";
- case Column::DURATION:
- return "Duration";
- case Column::CAMERA_1:
- return "Camera 1";
- case Column::CAMERA_2:
- return "Camera 2";
- case Column::CAMERA_3:
- return "Camera 3";
- case Column::CAMERA_4:
- return "Camera 4";
- default:
- return "";
+ if (display_type == ListDisplay::CLIP_LIST) {
+ switch (ClipListColumn(section)) {
+ case ClipListColumn::IN:
+ return "In";
+ case ClipListColumn::OUT:
+ return "Out";
+ case ClipListColumn::DURATION:
+ return "Duration";
+ case ClipListColumn::CAMERA_1:
+ return "Camera 1";
+ case ClipListColumn::CAMERA_2:
+ return "Camera 2";
+ case ClipListColumn::CAMERA_3:
+ return "Camera 3";
+ case ClipListColumn::CAMERA_4:
+ return "Camera 4";
+ default:
+ return "";
+ }
+ } else {
+ switch (PlayListColumn(section)) {
+ case PlayListColumn::IN:
+ return "In";
+ case PlayListColumn::OUT:
+ return "Out";
+ case PlayListColumn::DURATION:
+ return "Duration";
+ case PlayListColumn::CAMERA:
+ return "Camera";
+ case PlayListColumn::DESCRIPTION:
+ return "Description";
+ default:
+ return "";
+ }
}
}
void ClipList::emit_data_changed(size_t row)
{
- emit dataChanged(index(row, 0), index(row, 6));
+ if (display_type == ListDisplay::CLIP_LIST) {
+ emit dataChanged(index(row, 0), index(row, int(ClipListColumn::NUM_COLUMNS)));
+ } else {
+ emit dataChanged(index(row, 0), index(row, int(PlayListColumn::NUM_COLUMNS)));
+ }
}
struct Clip {
int64_t pts_in = -1, pts_out = -1;
std::vector<std::string> descriptions; // One per camera.
+ unsigned stream_idx = 0; // For the playlist only.
};
+// FIXME: This should be split into a separate clip list and play list model.
class ClipList : public QAbstractTableModel {
Q_OBJECT
public:
- enum Column {
+ enum class ListDisplay {
+ CLIP_LIST,
+ PLAY_LIST
+ };
+ ClipList(ListDisplay display_type) : display_type(display_type) {}
+
+ enum class ClipListColumn {
IN,
OUT,
DURATION,
CAMERA_4,
NUM_COLUMNS
};
+ enum class PlayListColumn {
+ IN,
+ OUT,
+ DURATION,
+ CAMERA,
+ DESCRIPTION,
+ NUM_COLUMNS
+ };
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
private:
std::vector<Clip> clips;
+ ListDisplay display_type;
};
#endif // !defined (_CLIP_LIST_H)
mainWindow.show();
thread(record_thread_func).detach();
- start_player_thread();
return app.exec();
}
MainWindow *global_mainwindow = nullptr;
extern int64_t current_pts;
-ClipList *clips;
+ClipList *cliplist_clips, *playlist_clips;
MainWindow::MainWindow()
: ui(new Ui::MainWindow)
global_mainwindow = this;
ui->setupUi(this);
- clips = new ClipList;
- ui->clip_list->setModel(clips);
+ cliplist_clips = new ClipList(ClipList::ListDisplay::CLIP_LIST);
+ ui->clip_list->setModel(cliplist_clips);
+
+ playlist_clips = new ClipList(ClipList::ListDisplay::PLAY_LIST);
+ ui->playlist->setModel(playlist_clips);
- // TODO: Make these into buttons.
// TODO: These are too big for lambdas.
QShortcut *cue_in = new QShortcut(QKeySequence(Qt::Key_A), this);
- connect(cue_in, &QShortcut::activated, []{
- if (!clips->empty() && clips->back()->pts_out < 0) {
- clips->back()->pts_in = current_pts;
+ connect(cue_in, &QShortcut::activated, ui->cue_in_btn, &QPushButton::click);
+ connect(ui->cue_in_btn, &QPushButton::clicked, []{
+ if (!cliplist_clips->empty() && cliplist_clips->back()->pts_out < 0) {
+ cliplist_clips->back()->pts_in = current_pts;
return;
}
Clip clip;
clip.pts_in = current_pts;
- clips->add_clip(clip);
+ cliplist_clips->add_clip(clip);
});
QShortcut *cue_out = new QShortcut(QKeySequence(Qt::Key_S), this);
- connect(cue_out, &QShortcut::activated, []{
- if (!clips->empty()) {
- clips->back()->pts_out = current_pts;
+ connect(cue_out, &QShortcut::activated, ui->cue_out_btn, &QPushButton::click);
+ connect(ui->cue_out_btn, &QPushButton::clicked, []{
+ if (!cliplist_clips->empty()) {
+ cliplist_clips->back()->pts_out = current_pts;
// TODO: select the row in the clip list?
}
});
- QShortcut *preview_shortcut = new QShortcut(QKeySequence(Qt::Key_W), this);
- connect(preview_shortcut, &QShortcut::activated, this, &MainWindow::preview_clicked);
+ QShortcut *queue = new QShortcut(QKeySequence(Qt::Key_Q), this);
+ connect(queue, &QShortcut::activated, ui->queue_btn, &QPushButton::click);
+ connect(ui->queue_btn, &QPushButton::clicked, this, &MainWindow::queue_clicked);
+
+ QShortcut *preview = new QShortcut(QKeySequence(Qt::Key_W), this);
+ connect(preview, &QShortcut::activated, ui->preview_btn, &QPushButton::click);
+ connect(ui->preview_btn, &QPushButton::clicked, this, &MainWindow::preview_clicked);
+
+ QShortcut *play = new QShortcut(QKeySequence(Qt::Key_Space), this);
+ connect(play, &QShortcut::activated, ui->play_btn, &QPushButton::click);
+ connect(ui->play_btn, &QPushButton::clicked, this, &MainWindow::play_clicked);
+
+ preview_player = new Player(ui->preview_display);
+}
+
+void MainWindow::queue_clicked()
+{
+ QItemSelectionModel *selected = ui->clip_list->selectionModel();
+ if (!selected->hasSelection()) {
+ Clip clip = *cliplist_clips->back();
+ clip.stream_idx = 0;
+ playlist_clips->add_clip(clip);
+ return;
+ }
+
+ QModelIndex index = selected->currentIndex();
+ if (index.column() >= int(ClipList::ClipListColumn::CAMERA_1) &&
+ index.column() <= int(ClipList::ClipListColumn::CAMERA_4)) {
+ Clip clip = *cliplist_clips->clip(index.row());
+ clip.stream_idx = index.column() - int(ClipList::ClipListColumn::CAMERA_1);
+ playlist_clips->add_clip(clip);
+ }
}
void MainWindow::preview_clicked()
{
QItemSelectionModel *selected = ui->clip_list->selectionModel();
if (!selected->hasSelection()) {
- play_clip(*clips->back(), 0);
+ preview_player->play_clip(*cliplist_clips->back(), 0);
return;
}
QModelIndex index = selected->currentIndex();
- if (index.column() >= ClipList::Column::CAMERA_1 &&
- index.column() <= ClipList::Column::CAMERA_4) {
- unsigned stream_idx = index.column() - ClipList::Column::CAMERA_1;
- play_clip(*clips->clip(index.row()), stream_idx);
+ if (index.column() >= int(ClipList::ClipListColumn::CAMERA_1) &&
+ index.column() <= int(ClipList::ClipListColumn::CAMERA_4)) {
+ unsigned stream_idx = index.column() - int(ClipList::ClipListColumn::CAMERA_1);
+ preview_player->play_clip(*cliplist_clips->clip(index.row()), stream_idx);
+ }
+}
+
+void MainWindow::play_clicked()
+{
+ QItemSelectionModel *selected = ui->playlist->selectionModel();
+ if (!selected->hasSelection()) {
+ ui->playlist->selectRow(0);
+ return;
}
}
class MainWindow;
} // namespace Ui
+class Player;
+
class MainWindow : public QMainWindow
{
Q_OBJECT
Ui::MainWindow *ui;
private:
+ Player *preview_player;
+
+ void queue_clicked();
void preview_clicked();
+ void play_clicked();
};
extern MainWindow *global_mainwindow;
#include "clip_list.h"
#include "defs.h"
-#include "mainwindow.h"
-#include "ffmpeg_raii.h"
-#include "post_to_main_thread.h"
-#include "ui_mainwindow.h"
+#include "jpeg_frame_view.h"
+#include "player.h"
using namespace std;
using namespace std::chrono;
extern mutex frame_mu;
extern vector<int64_t> frames[MAX_STREAMS];
-struct PlaylistClip {
- Clip clip;
- unsigned stream_idx;
-};
-vector<PlaylistClip> current_cue_playlist;
-mutex playlist_mu;
-
-enum { PAUSED, PLAYING } cue_state = PAUSED;
-mutex cue_state_mu;
-condition_variable cue_is_playing;
-//int cue_playlist_index = -1;
-//int64_t cue_playlist_pos = 0;
-
-int preview_thread_func()
+void Player::thread_func()
{
for ( ;; ) {
// Wait until we're supposed to play something.
{
unique_lock<mutex> lock(cue_state_mu);
- cue_is_playing.wait(lock, []{
+ cue_is_playing.wait(lock, [this]{
return cue_state == PLAYING;
//return current_cue_status.origin != steady_clock::time_point::max();
});
}
- PlaylistClip clip;
+ Clip clip;
+ unsigned stream_idx;
{
- lock_guard<mutex> lock2(playlist_mu);
- clip = current_cue_playlist[0];
+ lock_guard<mutex> lock2(mu);
+ clip = current_clip;
+ stream_idx = current_stream_idx;
}
steady_clock::time_point origin = steady_clock::now();
- int64_t pts_origin = clip.clip.pts_in;
+ int64_t pts_origin = clip.pts_in;
int64_t next_pts = pts_origin;
steady_clock::time_point next_frame_start =
origin + microseconds((next_pts - pts_origin) * int(1000000 / speed) / 12800);
this_thread::sleep_until(next_frame_start);
- global_mainwindow->ui->preview_display->setFrame(clip.stream_idx, next_pts);
+ destination->setFrame(stream_idx, next_pts);
// Find the next frame.
{
lock_guard<mutex> lock2(frame_mu);
- auto it = upper_bound(frames[clip.stream_idx].begin(),
- frames[clip.stream_idx].end(),
+ auto it = upper_bound(frames[stream_idx].begin(),
+ frames[stream_idx].end(),
next_pts);
- if (it == frames[clip.stream_idx].end()) {
+ if (it == frames[stream_idx].end()) {
eof = true;
} else {
next_pts = *it;
- if (next_pts >= clip.clip.pts_out) {
+ if (next_pts >= clip.pts_out) {
eof = true;
}
}
if (eof) break;
}
- // TODO: advance the playlist and look for the next element.
+ // TODO: callback so that the next playlist item can be cued.
{
unique_lock<mutex> lock(cue_state_mu);
cue_state = PAUSED;
}
}
-void start_player_thread()
+Player::Player(JPEGFrameView *destination)
+ : destination(destination)
{
- thread(preview_thread_func).detach();
+ thread(&Player::thread_func, this).detach();
}
-void play_clip(const Clip &clip, unsigned stream_idx)
+void Player::play_clip(const Clip &clip, unsigned stream_idx)
{
{
- lock_guard<mutex> lock(playlist_mu);
- current_cue_playlist.clear();
- current_cue_playlist.push_back(PlaylistClip{ clip, stream_idx });
+ lock_guard<mutex> lock(mu);
+ current_clip = clip;
+ current_stream_idx = stream_idx;
}
{
#include "clip_list.h"
-void start_player_thread();
-void play_clip(const Clip &clip, unsigned stream_idx);
+#include <condition_variable>
+#include <mutex>
+
+class JPEGFrameView;
+
+class Player {
+public:
+ Player(JPEGFrameView *destination);
+
+ void play_clip(const Clip &clip, unsigned stream_idx);
+
+private:
+ void thread_func();
+
+ JPEGFrameView *destination;
+
+ std::mutex mu;
+ Clip current_clip; // Under mu.
+ unsigned current_stream_idx; // Under mu.
+
+ enum { PAUSED, PLAYING } cue_state = PAUSED; // Under cue_state_mu.
+ std::mutex cue_state_mu;
+ std::condition_variable cue_is_playing;
+};
#endif // !defined(_PLAYER_H)
</widget>
<widget class="QWidget" name="verticalLayoutWidget_4">
<layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="queue_btn">
+ <property name="text">
+ <string>Queue (&Q)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="preview_btn">
+ <property name="text">
+ <string>Preview (&W)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="cue_in_btn">
+ <property name="text">
+ <string>Cue in (&A)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="cue_out_btn">
+ <property name="text">
+ <string>Cue out (&S)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="play_btn">
+ <property name="text">
+ <string>Play (space)</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
<item>
<widget class="QTableView" name="clip_list"/>
</item>
<item>
- <widget class="QTableWidget" name="playlist">
- <column>
- <property name="text">
- <string>In</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Out</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Duration</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Camera</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Description</string>
- </property>
- </column>
+ <widget class="QTableView" name="playlist">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
</widget>
</item>
</layout>