cabana: improve playback controller (#30325)

improve playback controller
old-commit-hash: 952838335d
testing-closet
Dean Lee 2 years ago committed by GitHub
parent 83678f7a87
commit 920bc9acea
  1. 2
      tools/cabana/chart/chartswidget.cc
  2. 16
      tools/cabana/detailwidget.cc
  3. 2
      tools/cabana/detailwidget.h
  4. 2
      tools/cabana/settings.cc
  5. 1
      tools/cabana/settings.h
  6. 2
      tools/cabana/streams/abstractstream.h
  7. 1
      tools/cabana/streams/livestream.cc
  8. 2
      tools/cabana/streams/livestream.h
  9. 2
      tools/cabana/streams/replaystream.h
  10. 7
      tools/cabana/util.cc
  11. 4
      tools/cabana/util.h
  12. 157
      tools/cabana/videowidget.cc
  13. 23
      tools/cabana/videowidget.h

@ -190,7 +190,7 @@ void ChartsWidget::updateState() {
if (pos < 0 || pos > 0.8) {
display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1);
}
double max_sec = std::min(std::floor(display_range.first + max_chart_range), can->totalSeconds());
double max_sec = std::min(display_range.first + max_chart_range, can->totalSeconds());
display_range.first = std::max(0.0, max_sec - max_chart_range);
display_range.second = display_range.first + max_chart_range;
} else if (cur_sec < (zoomed_range.first - 0.1) || cur_sec >= zoomed_range.second) {

@ -2,6 +2,7 @@
#include <QFormLayout>
#include <QMenu>
#include <QSpacerItem>
#include "tools/cabana/commands.h"
#include "tools/cabana/mainwin.h"
@ -22,19 +23,15 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
// message title
QHBoxLayout *title_layout = new QHBoxLayout();
title_layout->setContentsMargins(3, 6, 3, 0);
time_label = new QLabel(this);
time_label->setToolTip(tr("Current time"));
time_label->setStyleSheet("QLabel{font-weight:bold;}");
title_layout->addWidget(time_label);
name_label = new ElidedLabel(this);
auto spacer = new QSpacerItem(0, 1);
title_layout->addItem(spacer);
title_layout->addWidget(name_label = new ElidedLabel(this), 1);
name_label->setStyleSheet("QLabel{font-weight:bold;}");
name_label->setAlignment(Qt::AlignCenter);
name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
title_layout->addWidget(name_label);
auto edit_btn = new ToolButton("pencil", tr("Edit Message"));
title_layout->addWidget(edit_btn);
remove_btn = new ToolButton("x-lg", tr("Remove Message"));
title_layout->addWidget(remove_btn);
title_layout->addWidget(remove_btn = new ToolButton("x-lg", tr("Remove Message")));
spacer->changeSize(edit_btn->sizeHint().width() * 2 + 9, 1);
main_layout->addLayout(title_layout);
// warning
@ -151,7 +148,6 @@ void DetailWidget::refresh() {
}
void DetailWidget::updateState(const QHash<MessageId, CanData> *msgs) {
time_label->setText(QString::number(can->currentSec(), 'f', 3));
if ((msgs && !msgs->contains(msg_id)))
return;

@ -42,7 +42,7 @@ private:
void updateState(const QHash<MessageId, CanData> * msgs = nullptr);
MessageId msg_id;
QLabel *time_label, *warning_icon, *warning_label;
QLabel *warning_icon, *warning_label;
ElidedLabel *name_label;
QWidget *warning_widget;
TabBar *tabbar;

@ -14,6 +14,7 @@ Settings settings;
QSettings::Status Settings::save() {
QSettings s(filePath(), QSettings::IniFormat);
s.setValue("absolute_time", absolute_time);
s.setValue("fps", fps);
s.setValue("max_cached_minutes", max_cached_minutes);
s.setValue("chart_height", chart_height);
@ -40,6 +41,7 @@ QSettings::Status Settings::save() {
void Settings::load() {
QSettings s(filePath(), QSettings::IniFormat);
absolute_time = s.value("absolute_time", false).toBool();
fps = s.value("fps", 10).toInt();
max_cached_minutes = s.value("max_cached_minutes", 30).toInt();
chart_height = s.value("chart_height", 200).toInt();

@ -29,6 +29,7 @@ public:
void load();
inline static QString filePath() { return QApplication::applicationDirPath() + "/settings"; }
bool absolute_time = false;
int fps = 10;
int max_cached_minutes = 30;
int chart_height = 200;

@ -8,6 +8,7 @@
#include <vector>
#include <QColor>
#include <QDateTime>
#include <QHash>
#include "common/timing.h"
@ -64,6 +65,7 @@ public:
virtual void seekTo(double ts) {}
virtual QString routeName() const = 0;
virtual QString carFingerprint() const { return ""; }
virtual QDateTime beginDateTime() const { return {}; }
virtual double routeStartTime() const { return 0; }
virtual double currentSec() const = 0;
virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); }

@ -46,6 +46,7 @@ void LiveStream::start() {
emit streamStarted();
stream_thread->start();
startUpdateTimer();
begin_date_time = QDateTime::currentDateTime();
}
LiveStream::~LiveStream() {

@ -15,6 +15,7 @@ public:
LiveStream(QObject *parent);
virtual ~LiveStream();
void start() override;
inline QDateTime beginDateTime() const { return begin_date_time; }
inline double routeStartTime() const override { return begin_event_ts / 1e9; }
inline double currentSec() const override { return (current_event_ts - begin_event_ts) / 1e9; }
void setSpeed(float speed) override { speed_ = speed; }
@ -49,6 +50,7 @@ private:
int timer_id;
QBasicTimer update_timer;
QDateTime begin_date_time;
uint64_t begin_event_ts = 0;
uint64_t current_event_ts = 0;
uint64_t first_event_ts = 0;

@ -22,11 +22,13 @@ public:
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
double totalSeconds() const override { return replay->totalSeconds(); }
inline VisionStreamType visionStreamType() const override { return replay->hasFlag(REPLAY_FLAG_ECAM) ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD; }
inline QDateTime beginDateTime() const { return replay->route()->datetime(); }
inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; }
inline double currentSec() const override { return replay->currentSeconds(); }
inline const Route *route() const override { return replay->route(); }
inline void setSpeed(float speed) override { replay->setSpeed(speed); }
inline float getSpeed() const { return replay->getSpeed(); }
inline Replay *getReplay() const { return replay.get(); }
inline bool isPaused() const override { return replay->isPaused(); }
void pause(bool pause) override;
inline const std::vector<std::tuple<double, double, TimelineType>> getTimeline() override { return replay->getTimeline(); }

@ -242,6 +242,13 @@ void setTheme(int theme) {
}
}
QString formatSeconds(double sec, bool include_milliseconds, bool absolute_time) {
QString format = absolute_time ? "yyyy-MM-dd hh:mm:ss"
: (sec > 60 * 60 ? "hh:mm:ss" : "mm:ss");
if (include_milliseconds) format += ".zzz";
return QDateTime::fromMSecsSinceEpoch(sec * 1000).toString(format);
}
} // namespace utils
QString toHex(uint8_t byte) {

@ -103,9 +103,7 @@ public:
namespace utils {
QPixmap icon(const QString &id);
void setTheme(int theme);
inline QString formatSeconds(int seconds) {
return QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
}
QString formatSeconds(double sec, bool include_milliseconds = false, bool absolute_time = false);
inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text) {
auto size = (r.size() - text.size()) / 2;
p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text);

@ -3,8 +3,9 @@
#include <algorithm>
#include <utility>
#include <QButtonGroup>
#include <QHBoxLayout>
#include <QAction>
#include <QActionGroup>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QStackedLayout>
@ -27,50 +28,18 @@ static const QColor timeline_colors[] = {
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
auto main_layout = new QVBoxLayout(this);
if (!can->liveStreaming()) {
if (!can->liveStreaming())
main_layout->addWidget(createCameraWidget());
}
// btn controls
QButtonGroup *group = new QButtonGroup(this);
group->setExclusive(true);
main_layout->addLayout(createPlaybackController());
QHBoxLayout *control_layout = new QHBoxLayout();
play_btn = new QToolButton();
play_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
control_layout->addWidget(play_btn);
if (can->liveStreaming()) {
control_layout->addWidget(skip_to_end_btn = new QToolButton(this));
skip_to_end_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
skip_to_end_btn->setIcon(utils::icon("skip-end-fill"));
skip_to_end_btn->setToolTip(tr("Skip to the end"));
QObject::connect(skip_to_end_btn, &QToolButton::clicked, [group]() {
// set speed to 1.0
group->buttons()[2]->click();
can->pause(false);
can->seekTo(can->totalSeconds() + 1);
});
}
for (float speed : {0.1, 0.5, 1., 2.}) {
QToolButton *btn = new QToolButton(this);
btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
btn->setText(QString("%1x").arg(speed));
btn->setCheckable(true);
QObject::connect(btn, &QToolButton::clicked, [speed]() { can->setSpeed(speed); });
control_layout->addWidget(btn);
group->addButton(btn);
if (speed == 1.0) btn->setChecked(true);
}
main_layout->addLayout(control_layout);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QObject::connect(play_btn, &QToolButton::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
QObject::connect(&settings, &Settings::changed, this, &VideoWidget::updatePlayBtnState);
updatePlayBtnState();
updatePlayBtnState();
setWhatsThis(tr(R"(
<b>Video</b><br />
<!-- TODO: add descprition here -->
@ -93,6 +62,71 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
timeline_colors[(int)TimelineType::AlertCritical].name()));
}
QHBoxLayout *VideoWidget::createPlaybackController() {
QHBoxLayout *layout = new QHBoxLayout();
layout->addWidget(seek_backward_btn = new ToolButton("rewind", tr("Seek backward")));
layout->addWidget(play_btn = new ToolButton("play", tr("Play")));
layout->addWidget(seek_forward_btn = new ToolButton("fast-forward", tr("Seek forward")));
if (can->liveStreaming()) {
layout->addWidget(skip_to_end_btn = new ToolButton("skip-end", tr("Skip to the end"), this));
QObject::connect(skip_to_end_btn, &QToolButton::clicked, [this]() {
// set speed to 1.0
speed_btn->menu()->actions()[7]->setChecked(true);
can->pause(false);
can->seekTo(can->totalSeconds() + 1);
});
}
layout->addWidget(time_btn = new QToolButton);
time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time"));
time_btn->setAutoRaise(true);
layout->addStretch(0);
if (!can->liveStreaming()) {
layout->addWidget(loop_btn = new ToolButton("repeat", tr("Loop playback")));
QObject::connect(loop_btn, &QToolButton::clicked, this, &VideoWidget::loopPlaybackClicked);
}
// speed selector
layout->addWidget(speed_btn = new QToolButton(this));
speed_btn->setAutoRaise(true);
speed_btn->setMenu(new QMenu(speed_btn));
speed_btn->setPopupMode(QToolButton::InstantPopup);
QActionGroup *speed_group = new QActionGroup(this);
speed_group->setExclusive(true);
int max_width = 0;
QFont font = speed_btn->font();
font.setBold(true);
speed_btn->setFont(font);
QFontMetrics fm(font);
for (float speed : {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 0.8, 1., 2., 3., 5.}) {
QString name = QString("%1x").arg(speed);
max_width = std::max(max_width, fm.width(name) + fm.horizontalAdvance(QLatin1Char(' ')) * 2);
QAction *act = new QAction(name, speed_group);
act->setCheckable(true);
QObject::connect(act, &QAction::toggled, [this, speed]() {
can->setSpeed(speed);
speed_btn->setText(QString("%1x ").arg(speed));
});
speed_btn->menu()->addAction(act);
if (speed == 1.0)act->setChecked(true);
}
speed_btn->setMinimumWidth(max_width + style()->pixelMetric(QStyle::PM_MenuButtonIndicator));
QObject::connect(play_btn, &QToolButton::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(seek_backward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() - 1); });
QObject::connect(seek_forward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() + 1); });
QObject::connect(time_btn, &QToolButton::clicked, [this]() {
settings.absolute_time = !settings.absolute_time;
time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time"));
updateState();
});
return layout;
}
QWidget *VideoWidget::createCameraWidget() {
QWidget *w = new QWidget(this);
QVBoxLayout *l = new QVBoxLayout(w);
@ -106,30 +140,30 @@ QWidget *VideoWidget::createCameraWidget() {
stacked->addWidget(alert_label = new InfoLabel(this));
l->addLayout(stacked);
// slider controls
auto slider_layout = new QHBoxLayout();
slider_layout->addWidget(time_label = new QLabel("00:00"));
slider = new Slider(this);
l->addWidget(slider = new Slider(this));
slider->setSingleStep(0);
slider_layout->addWidget(slider);
slider_layout->addWidget(end_time_label = new QLabel(this));
l->addLayout(slider_layout);
setMaximumTime(can->totalSeconds());
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); });
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(slider->currentSecond())); });
QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection);
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(static_cast<ReplayStream*>(can), &ReplayStream::qLogLoaded, slider, &Slider::parseQLog);
QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
return w;
}
void VideoWidget::loopPlaybackClicked() {
auto replay = qobject_cast<ReplayStream *>(can)->getReplay();
if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) {
replay->removeFlag(REPLAY_FLAG_NO_LOOP);
loop_btn->setIcon("repeat");
} else {
replay->addFlag(REPLAY_FLAG_NO_LOOP);
loop_btn->setIcon("repeat-1");
}
}
void VideoWidget::setMaximumTime(double sec) {
maximum_time = sec;
end_time_label->setText(utils::formatSeconds(sec));
slider->setTimeRange(0, sec);
}
@ -143,19 +177,29 @@ void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) {
min = 0;
max = maximum_time;
}
end_time_label->setText(utils::formatSeconds(max));
slider->setTimeRange(min, max);
}
QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
if (settings.absolute_time)
sec = can->beginDateTime().addMSecs(sec * 1000).toMSecsSinceEpoch() / 1000.0;
return utils::formatSeconds(sec, include_milliseconds, settings.absolute_time);
}
void VideoWidget::updateState() {
if (!slider->isSliderDown()) {
slider->setCurrentSecond(can->currentSec());
if (slider) {
if (!slider->isSliderDown())
slider->setCurrentSecond(can->currentSec());
alert_label->showAlert(slider->alertInfo(can->currentSec()));
time_btn->setText(QString("%1 / %2").arg(formatTime(can->currentSec(), true),
formatTime(slider->maximum() / slider->factor)));
} else {
time_btn->setText(formatTime(can->currentSec(), true));
}
alert_label->showAlert(slider->alertInfo(can->currentSec()));
}
void VideoWidget::updatePlayBtnState() {
play_btn->setIcon(utils::icon(can->isPaused() ? "play" : "pause"));
play_btn->setIcon(can->isPaused() ? "play" : "pause");
play_btn->setToolTip(can->isPaused() ? tr("Play") : tr("Pause"));
}
@ -284,8 +328,7 @@ void InfoLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap &
second = sec;
pixmap = pm;
alert_info = alert;
resize(pm.size());
move(pt);
setGeometry(QRect(pt, pm.size()));
setVisible(true);
update();
}

@ -3,7 +3,7 @@
#include <map>
#include <memory>
#include <QLabel>
#include <QHBoxLayout>
#include <QSlider>
#include <QToolButton>
@ -39,6 +39,8 @@ public:
QPixmap thumbnail(double sec);
void parseQLog(int segnum, std::shared_ptr<LogReader> qlog);
const double factor = 1000.0;
signals:
void updateMaximumTime(double);
@ -48,7 +50,6 @@ private:
bool event(QEvent *event) override;
void paintEvent(QPaintEvent *ev) override;
const double factor = 1000.0;
QMap<uint64_t, QPixmap> thumbnails;
std::map<uint64_t, AlertInfo> alerts;
InfoLabel thumbnail_label;
@ -63,16 +64,22 @@ public:
void setMaximumTime(double sec);
protected:
QString formatTime(double sec, bool include_milliseconds = false);
void updateState();
void updatePlayBtnState();
QWidget *createCameraWidget();
QHBoxLayout *createPlaybackController();
void loopPlaybackClicked();
CameraWidget *cam_widget;
double maximum_time = 0;
QLabel *end_time_label;
QLabel *time_label;
QToolButton *play_btn;
QToolButton *skip_to_end_btn = nullptr;
InfoLabel *alert_label;
Slider *slider;
QToolButton *time_btn = nullptr;
ToolButton *seek_backward_btn = nullptr;
ToolButton *play_btn = nullptr;
ToolButton *seek_forward_btn = nullptr;
ToolButton *loop_btn = nullptr;
QToolButton *speed_btn = nullptr;
ToolButton *skip_to_end_btn = nullptr;
InfoLabel *alert_label = nullptr;
Slider *slider = nullptr;
};

Loading…
Cancel
Save