mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-28 01:52:06 +08:00
cabana: improve playback controller (#30325)
improve playback controller old-commit-hash: 952838335d318ab3ecc2365d4f466800ad87aac4
This commit is contained in:
@@ -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) {
|
||||
|
||||
+1
-3
@@ -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);
|
||||
|
||||
+100
-57
@@ -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());
|
||||
}
|
||||
main_layout->addLayout(createPlaybackController());
|
||||
|
||||
// btn controls
|
||||
QButtonGroup *group = new QButtonGroup(this);
|
||||
group->setExclusive(true);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user