|
|
|
@ -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(); |
|
|
|
|
} |
|
|
|
|