Files
qt5-legatus-legacy/Lts/Plugins/AudioVideoManagement/Gui/AVMenuEdit/avm_playback_menu.cpp
T
2026-08-13 18:33:10 +02:00

1705 lines
51 KiB
C++
Executable File

/**
* @RDE_HEADER@
* AvmPlaybackMenu
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, SLSE, 2017-06-26
*/
#include "avm_playback_menu.h"
#include "TopicEditor/avm_sequence_model.h"
#include <AudioVideoManagement/av_helper.h>
#include <AudioVideoManagement/ava_comm.h>
#include <AudioVideoManagement/ava_control.h>
#include <AudioVideoManagement/ava_session.h>
#include <AudioVideoManagement/stream_name_mapping.h>
#include <utility>
#include <QVector>
#include <QModelIndex>
#include <QStringList>
#include <QSignalBlocker>
#include <QIcon>
#include <QtGlobal>
#include <QInputDialog>
#include <QMenu>
#include <QAction>
#include <ngltools/ngltools_dtg.h>
#include <PluginInterfaces/gui_plugin_interface.h>
#include <LtsSession/lts_session_data.h>
#include <LtsWidgets/lts_message_dialog.h>
#include <LtsWidgets/lts_msec_date_time_edit.h>
#include <MarkerManagement/Gui/Common/mrk_dialog_model.h>
#include <MarkerManagement/mrk_data_plugin_interface.h>
#include <OperatorManagement/opm_data_plugin_interface.h>
#include <LtsUtilsGuiDds/request_helper.h>
#include <LtsUtilsSettings/settings.h>
namespace
{
// stolen from ngltools_dtg.cpp
bool isTimezoneValid(char tz)
{
tz = toupper(tz);
return !(tz < 'A' || tz == 'J' || tz > 'Z');
}
int timezone2Hours(char tz, bool *bOk)
{
if (!isTimezoneValid(tz))
{
if (bOk)
*bOk = false;
return 0;
}
if (bOk)
*bOk = true;
if (tz < 'J')
return tz - 'A' + 1;
if (tz < 'N')
return tz - 'A';
if (tz < 'Z')
return 'Z' - tz - 13;
return 0;
}
const QString &ALL_STREAMS()
{
static const QString ALL_STREAMS_ = QStringLiteral("*");
return ALL_STREAMS_;
}
} // anonymous namespace
namespace AudioVideoManagement
{
/**
* Small helper class to build the Timeline for a set of streams.
*/
class AvmPlaybackMenu::TimelineBuilder : public QObject
{
Q_OBJECT
public:
explicit TimelineBuilder(AvaControl *avaCtrl)
: m_avaCtrl(avaCtrl)
{
Q_ASSERT(avaCtrl);
}
/**
* Can be called even if the previous queryTimeline() is not yet complete.
* The previous call will be cancelled then: there will only be a timelineComplete() signal
* for the last issued queryTimeline() call.
*/
void queryTimeline(const QVector<AvaStream> &streams)
{
m_timeline.clear();
m_ignoreTimelineCount += m_awaitedTimelineCount;
m_awaitedTimelineCount = streams.size();
for (const auto &stream : streams)
m_avaCtrl->queryTimeline(stream.toString(), this, &TimelineBuilder::onTimelineReceived);
}
const AvaTimeline &timeline() const
{
// cfroebel 2018-04-19: Must be activated again, once onStreamSelectionChanged() will not
// be called from third parties anymore. It was supposed to be private.
// Q_ASSERT(m_awaitedTimelineCount == 0);
return m_timeline;
}
/**
* Must be called when the connection to AVS_Workstation breaks in case we
* have some outstanding queries for which we will never receive the response then.
*/
void reset()
{
m_ignoreTimelineCount = 0;
m_awaitedTimelineCount = 0;
}
signals:
void timelineComplete(const AvaTimeline &timeline);
private slots:
void onTimelineReceived(const AvaTimeline &timeline)
{
Q_ASSERT(m_awaitedTimelineCount > 0);
if (m_ignoreTimelineCount > 0)
{
--m_ignoreTimelineCount;
return;
}
m_timeline.merge(timeline);
if (--m_awaitedTimelineCount > 0)
return;
emit timelineComplete(m_timeline);
}
private:
AvaControl *m_avaCtrl;
AvaTimeline m_timeline;
int m_ignoreTimelineCount{0};
int m_awaitedTimelineCount{0};
};
/**
* Helper class to control the interaction between the DateTimeEdit and
* the slider.
*/
class AvmPlaybackMenu::TimeControl : public QObject
{
Q_OBJECT
public:
TimeControl(LtsWidgets::LtsMSecDateTimeEdit *dt, QSlider *slider, QLabel *lblStart, QLabel *lblEnd)
: m_dt(dt)
, m_slider(slider)
, m_lblStart(lblStart)
, m_lblEnd(lblEnd)
{
Q_ASSERT(dt);
Q_ASSERT(slider);
Q_ASSERT(lblStart);
Q_ASSERT(lblEnd);
connect(m_dt, &LtsWidgets::LtsMSecDateTimeEdit::dateTimeChanged, this, &TimeControl::onDtChanged);
connect(m_slider, &QSlider::valueChanged, this, &TimeControl::onSliderChanged);
}
QString dateTimeToString(const QDateTime &dt)
{
return dt.toString(QStringLiteral("dd.MM.yy HH:mm:ss.zzz"));
}
QString msecsSinceEpochToString(qint64 msecs)
{
return dateTimeToString(QDateTime::fromMSecsSinceEpoch(msecs, Qt::TimeSpec::OffsetFromUTC, m_timezoneHoursOffset * 3600));
}
void setTimezoneHoursOffset(int timezoneHoursOffset)
{
m_timezoneHoursOffset = timezoneHoursOffset;
if (!m_interval.is_initialized())
return;
// update start & end time labels
// NOTE: dt's timezone is set outside of TimeControl
m_lblStart->setText(msecsSinceEpochToString(toMSecSinceEpoch(m_interval->first)));
m_lblEnd->setText(msecsSinceEpochToString(toMSecSinceEpoch(m_interval->second)));
}
void setTime(AvaTimePoint t)
{
QSignalBlocker b1(m_dt);
if (!m_interval.is_initialized())
{
m_dt->setMsecsSinceEpoch(AudioVideoManagement::toMSecSinceEpoch(t));
return;
}
t = timePointInInterval(t);
m_dt->setMsecsSinceEpoch(AudioVideoManagement::toMSecSinceEpoch(t));
// Avoid that changing the slider in return changes the DTE as the slider has only seconds-accuracy.
QSignalBlocker blocker(m_slider);
AvaDuration delta = t - m_interval->first;
std::chrono::seconds deltaS = std::chrono::duration_cast<std::chrono::seconds>(delta + std::chrono::milliseconds(500)); // +0.5s for rounding
m_slider->setValue(deltaS.count());
}
void setInterval(AvaTimelinePart interval)
{
std::chrono::seconds intervalLength = std::chrono::duration_cast<std::chrono::seconds>(interval.second - interval.first);
QSignalBlocker b1(m_slider);
m_slider->setMinimum(0);
m_slider->setMaximum(intervalLength.count());
m_lblStart->setText(msecsSinceEpochToString(toMSecSinceEpoch(interval.first)));
m_lblEnd->setText(msecsSinceEpochToString(toMSecSinceEpoch(interval.second)));
m_interval = interval;
}
AvaTimePoint timePointInInterval(AvaTimePoint t)
{
Q_ASSERT(m_interval.is_initialized());
if (!m_interval.is_initialized())
return {};
if (t < m_interval->first)
return m_interval->first;
if (t > m_interval->second)
return m_interval->second;
return t;
}
AvaTimePoint time() const { return toTimePoint(m_dt->msecsSinceEpoch()); }
qint64 timeMSec() const { return m_dt->msecsSinceEpoch(); }
signals:
void timeChanged(AvaTimePoint currentTime);
private slots:
void onDtChanged()
{
emit timeChanged(toTimePoint(m_dt->msecsSinceEpoch()));
}
void onSliderChanged(int seconds)
{
QSignalBlocker b1(m_dt);
AvaTimePoint t = m_interval->first + std::chrono::seconds(seconds);
m_dt->setMsecsSinceEpoch(toMSecSinceEpoch(t));
emit timeChanged(toTimePoint(m_dt->msecsSinceEpoch()));
}
private:
LtsWidgets::LtsMSecDateTimeEdit *m_dt;
QSlider *m_slider;
QLabel *m_lblStart;
QLabel *m_lblEnd;
boost::optional<AvaTimelinePart> m_interval;
int m_timezoneHoursOffset = 0;
};
// QueryTimeline
QueryTimeline::QueryTimeline(const AvaStream &stream, const AvaTimePoint &start, const AvaTimePoint &end, AvaControl *control)
: QObject()
{
connect(control, &AvaControl::queryTimelineResponse,
this, &QueryTimeline::signalTimelineComplete);
connect(control, &AvaControl::queryTimelineResponse,
this, &QObject::deleteLater);
control->queryTimeline(stream.toString(), start, end);
}
// AvmPlaybackMenu
AvmPlaybackMenu::AvmPlaybackMenu(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::Playback)
, m_requestHelper(new LtsUtils::RequestHelper(this))
, m_logger(getLogger("AvmPlaybackMenu"))
{}
AvmPlaybackMenu::~AvmPlaybackMenu()
{
if (m_selectedStreams)
m_avaCtrl->releaseStream(m_playbackApp.dialogId(), ALL_STREAMS());
m_playbackApp.hide(true /* and close */);
}
QueryTimeline *AvmPlaybackMenu::queryTimeline(const AvaTimePoint &start, const AvaTimePoint &end) // .. of current stream
{
if (!m_currentStream)
return nullptr;
if (m_queryTimelineActive)
return nullptr;
QueryTimeline *qt = new QueryTimeline(m_currentStream.get(), start, end, m_avaCtrl); // don't parent, probably uses dumb connections
// if (!qt->init())
// {
// qt->deleteLater();
// return nullptr;
// }
m_queryTimelineActive = true;
emit signalSetOverlay(new QWidget()); // NOTE: doesn't overlay if not its own dialog (e.g. inside topic editor)
connect(qt, &QueryTimeline::signalTimelineComplete,
this, [this]()
{
emit signalUnsetOverlay(); // s.a.a.
m_queryTimelineActive = false;
});
return qt;
}
void AvmPlaybackMenu::setPlaybackPreset(const QString &streamName, const AvaTimePoint &startTime, bool autostartPresetAfterInit)
{
m_presetStreamName = streamName;
m_presetStartTime = startTime;
m_autostartOnce = autostartPresetAfterInit;
if (m_timelineIsComplete) // dialog was already open and has a selected stream - set time, stop and start playback
{
onStopClicked();
processPresets();
}
}
void AvmPlaybackMenu::setMarkerPreset(int markerId, bool autostartPresetAfterInit)
{
m_markerIdPreset = markerId;
m_autostartOnce = autostartPresetAfterInit;
m_mrkDataIfc->getDtc()->waitForHistoricalDataMarker(8000);
m_ui->tabWidget->setCurrentIndex(1); // markers tab
auto idx = m_markerModel->findIndexForId(*m_markerIdPreset);
auto mappedIdx = m_markerProxy->mapFromSource(idx);
m_ui->tvMarker->selectRow(mappedIdx);
m_ui->tvMarker->scrollTo(mappedIdx);
selectMarker(markerId);
qWarning() << m_ui->tvMarker->selectionModel()->selectedRows();
}
bool AvmPlaybackMenu::init(LtsSession::LtsSession *session, AvaSession *avaSession, PlaybackAppHandle &&appHandle,
StreamNameMapping *streamNameMapping, bool isAudio, bool insideTopicEditor,
const boost::optional<QString> &streamName, const boost::optional<AvaTimePoint> &startTime,
bool setInitialSearchContentMarkers)
{
Q_ASSERT(avaSession);
// todo: At the moment we do not allow to instantiate this dialog when we don't have access to the PlaybackApp.
// We could instead just disable the dialog and maybe show a message indicating a reason...
Q_ASSERT(appHandle);
m_isAudio = isAudio;
m_streamNameMapping = streamNameMapping;
m_presetStreamName = streamName;
if (m_presetStreamName)
DEBUG_LOG(m_logger) << "init stream preset: " << m_presetStreamName.get();
m_presetStartTime = startTime;
if (m_presetStartTime)
DEBUG_LOG(m_logger) << "init time preset: " << std::chrono::milliseconds(m_presetStartTime.get().time_since_epoch()).count();
m_session = session;
m_playbackApp = std::move(appHandle);
m_avaCtrl = avaSession->control();
m_timelineBuilder.reset(new TimelineBuilder(avaSession->control()));
m_ui->setupUi(this);
m_ui->pbDebug->hide();
#ifndef LTS_DEBUG
m_ui->lbAvsConnectionStatus->hide();
m_ui->lblAvsConnectionStatus->hide();
#endif
m_ui->tabWidget->setCurrentIndex(0); // make sure clips is always the default selected tab
if (!insideTopicEditor)
m_ui->topicEditorEssentialsWidget->hide();
else
{
setContentsMargins(QMargins());
m_ui->tabWidget->setTabText(0, m_isAudio ? tr("Audio clips") : tr("Video clips"));
m_ui->multiStreamWidget->hide();
m_ui->pbAddStreamClip->hide();
m_ui->pbAddStreamMarker->hide();
// action for the interval view to set start & end time
m_ui->tavIntervals->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui->tavIntervals, &QTableView::customContextMenuRequested,
this, [this](const QPoint & pos)
{
QMenu *m = new QMenu(this);
auto a1 = new QAction(tr("Set start and end time"), this);
connect(a1, &QAction::triggered,
this, [this]()
{
auto idxs = m_ui->tavIntervals->selectionModel()->selectedRows();
if (idxs.isEmpty())
return;
AvaTimelinePart interval = m_intervalModel.getRowData(idxs.at(0).row());
emit signalSetStart(toMSecSinceEpoch(interval.first));
emit signalSetEnd(toMSecSinceEpoch(interval.second));
});
m->addAction(a1);
m->popup(m_ui->tavIntervals->viewport()->mapToGlobal(pos));
});
}
m_timeControl.reset(new TimeControl(m_ui->dtCurrentTime, m_ui->slSequenceSlider, m_ui->lblStartSeq, m_ui->lblEndSeq));
if (m_isAudio)
m_maxSelectedStreams = 8;
m_ui->tavIntervals->setModel(&m_intervalModel);
m_ui->tavIntervals->setSelectionMode(QAbstractItemView::SingleSelection);
m_ui->tavIntervals->setSelectionBehavior(QAbstractItemView::SelectRows);
m_ui->tavIntervals->resizeColumnsToContents();
// optional
if (m_session->getForeignDataInterface<MarkerManagement::DataInterface>(m_mrkDataIfc))
{
m_markerModel.reset(new MarkerManagement::MarkerManagementDialogModel(this));
m_markerProxy.reset(new MarkerManagement::MarkerSortFilterProxyModel(this));
m_markerProxy->setSourceModel(m_markerModel.get());
std::shared_ptr<OperatorManagement::Interface::Dtc> opmDtc;
m_session->getDtc<OperatorManagement::DataInterface>(opmDtc);
std::shared_ptr<FileManagement::ForeignGuiInterface> flmGui;
m_session->getForeignGuiInterface<FileManagement::ForeignGuiInterface>(flmGui);
m_markerModel->initModel(m_mrkDataIfc->getDtc(), opmDtc, flmGui, true /* we decide which timezone the marker time is shown in! */);
m_ui->tvMarker->setModel(m_markerProxy.get());
m_ui->tvMarker->hideColumn(MarkerManagement::MarkerManagementDialogModel::ColImage);
m_ui->tvMarker->hideColumn(MarkerManagement::MarkerManagementDialogModel::ColFileId);
m_ui->tvMarker->hideColumn(MarkerManagement::MarkerManagementDialogModel::ColOffset);
#ifndef LTS_DEBUG
m_ui->tvMarker->hideColumn(MarkerManagement::MarkerManagementDialogModel::ColStreams);
#endif
if (setInitialSearchContentMarkers)
m_ui->tvMarker->setInitialSearchContent(MarkerManagement::MarkerManagementDialogModel::ColOperator,
LtsSessionData::LtsSessionData::getInstance()->getOperatorId());
connect(m_ui->tvMarker, &LtsDataView::LtsDataView::activated, this, &AvmPlaybackMenu::onMarkerClicked);
connect(m_ui->pbSetMarker, &QPushButton::clicked, this, &AvmPlaybackMenu::onSetMarkerClicked);
}
else
{
m_ui->tabWidget->setTabEnabled(1, false); // disable marker tab completely
}
QSignalBlocker blocker1(m_ui->slSpeed);
m_ui->slSpeed->setMinimum(0);
m_ui->slSpeed->setMaximum(AvaSpeed::allowedValues().size() - 1);
m_ui->slSpeed->setSingleStep(1);
m_ui->slSpeed->setPageStep(1);
m_ui->slSpeed->setValue(AvaSpeed::allowedValues().indexOf(AvaSpeed(100)));
m_ui->lbSpeedValue->setText(QString::number(100));
//hide things
m_ui->slSpeed->setVisible(!m_isAudio);
m_ui->lbSpeedValue->setVisible(!m_isAudio);
m_ui->lbSpeed->setVisible(!m_isAudio);
if (m_isAudio)
m_ui->spacerSpeedVolume->changeSize(0, 0);
QSignalBlocker blocker2(m_ui->slVolume);
m_ui->slVolume->setMinimum(AvaVolume::min());
m_ui->slVolume->setMaximum(AvaVolume::max());
m_ui->slVolume->setSingleStep(1);
m_ui->slVolume->setPageStep(10);
m_ui->slVolume->setValue(0);
m_ui->lbVolumeValue->setText(QString::number(0));
m_ui->pbSkipPause->setIcon(QIcon(QStringLiteral("icon:media-vcr-nopause.svg")));
connect(avaSession, &AvaSession::sessionEstablished, this, &AvmPlaybackMenu::onSessionEstablished);
connect(avaSession, &AvaSession::disconnected, this, &AvmPlaybackMenu::onDisconnected);
connect(m_timelineBuilder.get(), &TimelineBuilder::timelineComplete, this, &AvmPlaybackMenu::onTimelineComplete);
connect(m_timeControl.get(), &TimeControl::timeChanged, this, &AvmPlaybackMenu::onUiTimeChanged);
connect(m_avaCtrl->comm(), &AvaComm::streamPositionReceived, this, &AvmPlaybackMenu::onStreamPositionReceived);
connect(m_avaCtrl->comm(), &AvaComm::volumeReceived, this, &AvmPlaybackMenu::onVolumeReceived);
connect(m_avaCtrl, &AvaControl::commandQueueEmpty, this, &AvmPlaybackMenu::onCommandQueueEmpty);
connect(m_ui->cbStreams, &QComboBox::currentTextChanged, this, &AvmPlaybackMenu::onStreamSelected);
connect(m_ui->cbMarkerStreams, &QComboBox::currentTextChanged, this, &AvmPlaybackMenu::onMarkerStreamSelected);
connect(&m_intervalModel, &IntervalModel::currentIntervalChanged, this, &AvmPlaybackMenu::onCurrentIntervalChanged);
connect(m_ui->pbStart, &QToolButton::clicked, this, &AvmPlaybackMenu::onStartClicked);
connect(m_ui->pbPause, &QToolButton::clicked, this, &AvmPlaybackMenu::onPauseClicked);
connect(m_ui->pbStop, &QToolButton::clicked, this, &AvmPlaybackMenu::onStopClicked);
connect(m_ui->pbIntervalBackward, &QToolButton::clicked, this, &AvmPlaybackMenu::onIntervalBackwardClicked);
connect(m_ui->pbIntervalForward, &QToolButton::clicked, this, &AvmPlaybackMenu::onIntervalForwardClicked);
connect(m_ui->slSpeed, &QSlider::valueChanged, this, &AvmPlaybackMenu::onSpeedChanged);
connect(m_ui->slVolume, &QSlider::valueChanged, this, &AvmPlaybackMenu::onVolumeChanged);
connect(m_ui->tavIntervals, &QTableView::activated, this, &AvmPlaybackMenu::onIntervalActivated);
connect(m_ui->pbSkipPause, &QToolButton::clicked, this, &AvmPlaybackMenu::onSkipPauseClicked);
connect(m_ui->pbAllStreams, &QPushButton::clicked, this, &AvmPlaybackMenu::onAllStreams);
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &AvmPlaybackMenu::onTabChanged);
connect(m_streamNameMapping, &StreamNameMapping::changed,
this, &AvmPlaybackMenu::onStreamNameMappingChanged);
connect(m_ui->pbAddStreamClip, &QPushButton::clicked, this, &AvmPlaybackMenu::onAddStreamClip);
connect(m_ui->pbAddStreamMarker, &QPushButton::clicked, this, &AvmPlaybackMenu::onAddStreamMarker);
connect(m_ui->pbRemoveSelectedStream, &QPushButton::clicked, this, &AvmPlaybackMenu::onRemoveSelectedStreams);
connect(m_ui->pbAsStart, &QPushButton::clicked, this, [this]()
{
emit signalSetStart(m_ui->dtCurrentTime->msecsSinceEpoch());
});
connect(m_ui->pbAsEnd, &QPushButton::clicked, this, [this]()
{
emit signalSetEnd(m_ui->dtCurrentTime->msecsSinceEpoch());
});
//m_ui->dteCurrentTime->setRetrieveCurrentTimeCallback([this]() { return m_session->partitionTime(); });
#ifndef LTS_DEBUG
if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("AVM.Debug")) == QVariant(true))
#endif
{
m_ui->tavIntervals->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui->tavIntervals, &QTableView::customContextMenuRequested,
this, [this](const QPoint & pos)
{
QMenu *menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction *a = menu->addAction(QStringLiteral("[debug]"));
a->setEnabled(false);
auto sim = [this]()
{
AvaTimelinePart interval = m_intervalModel.getRowData(m_ui->tavIntervals->selectionModel()->currentIndex().row());
new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("start: %1 - end: %2 - duration: %3").arg(toMSecSinceEpoch(interval.first)).arg(toMSecSinceEpoch(interval.second)).arg(toMSecSinceEpoch(interval.second) - toMSecSinceEpoch(interval.first)));
};
menu->addAction(QStringLiteral("show in msecs"), sim);
menu->popup(m_ui->tavIntervals->mapToGlobal(pos));
});
QMenu *menu = new QMenu(this);
menu->addAction(QStringLiteral("add test stream entry"), this, [this]()
{
qint64 sm = QDateTime::currentMSecsSinceEpoch();
qint64 em = sm + 48163;
AvaTimePoint s(std::chrono::duration_cast<AvaDuration>(std::chrono::milliseconds(sm)));
AvaTimePoint e(std::chrono::duration_cast<AvaDuration>(std::chrono::milliseconds(em)));
m_intervalModel.insertRow({s, e});
});
menu->addAction(QStringLiteral("enable view"), this, [this]()
{
m_ui->tavIntervals->setEnabled(true);
});
m_ui->pbDebug->setMenu(menu);
m_ui->pbDebug->show();
}
onDisconnected();
if (avaSession->isEstablished())
onSessionEstablished();
// stolen from operator_settings_dialog.cpp
for (int tz = 'Y'; tz <= 'Z'; ++tz)
m_ui->cbTimeZone->addItem(QString(QChar(tz)), tz);
m_ui->cbTimeZone->addItem(tr("Exercise default (%1)").arg(QLatin1Char(DTG::timezoneExerciseDefault())), 0);
for (int tz = 'A'; tz <= 'F'; ++tz)
m_ui->cbTimeZone->addItem(QString(QChar(tz)), tz);
const QChar tz = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("TimeZone"), QChar(QLatin1Char('Z'))).toChar();
int idx = m_ui->cbTimeZone->findData(tz.isNull() ? 0 : tz);
//qWarning() << "tz?" << tz << "is null?" << tz.isNull() << "idx" << idx << "raw" << LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("TimeZone"), QChar(QLatin1Char('-')));
if (idx >= 0)
m_ui->cbTimeZone->setCurrentIndex(idx);
else
m_ui->cbTimeZone->setCurrentIndex(m_ui->cbTimeZone->findData(0));
// timezone stuff
bool k = false;
m_ui->dtCurrentTime->setTimezoneHoursOffset(timezone2Hours(tz.toLatin1(), &k));
auto timezoneChanged = [this](int)
{
char tz = m_ui->cbTimeZone->currentData().toInt(); // careful
if (tz == 0) // 'exercise default' entry
tz = DTG::timezoneExerciseDefault();
bool k = false;
int tzh = timezone2Hours(tz, &k);
if (!k)
return;
m_currentOffset = tzh;
emit signalTimezoneChanged(tzh);
QSignalBlocker b1(m_ui->dtCurrentTime);
m_ui->dtCurrentTime->setTimezoneHoursOffset(tzh);
m_timeControl->setTimezoneHoursOffset(tzh);
m_intervalModel.setTimezoneHoursOffset(tzh);
m_markerModel->setTimezoneHoursOffset(tzh);
};
connect(m_ui->cbTimeZone, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [timezoneChanged](int)
{
timezoneChanged(0);
});
timezoneChanged(0); // initial setting
connect(m_playbackApp.objectWrapper(), &ObjectWrapper::signalVisible,
this, [this]()
{
m_videoScreenSelectionDone = true;
if (!m_timelineIsComplete)
return;
if (!m_autostartOnce)
return;
m_autostartOnce = false;
startPlayback();
});
connect(m_playbackApp.objectWrapper(), &ObjectWrapper::signalPause,
this, &AvmPlaybackMenu::onPauseFromPlaybackApp);
connect(m_playbackApp.objectWrapper(), &ObjectWrapper::signalResume,
this, &AvmPlaybackMenu::onResumeFromPlaybackApp);
return true;
}
void AvmPlaybackMenu::onTabChanged(int index)
{
switch (index)
{
case 0: // clips
{
onStreamSelected();
break;
}
case 1: // markers
{
m_ui->pbIntervalBackward->setEnabled(false);
m_ui->pbIntervalForward->setEnabled(false);
onMarkerStreamSelected();
break;
}
default:
{
Q_ASSERT(index > 1);
break;
}
}
}
void AvmPlaybackMenu::onPauseFromPlaybackApp()
{
m_buttonStateBeforePlaybackAppPause = m_currentButtonState;
switch (m_currentButtonState)
{
case ButtonStates::AllOff:
return;
case ButtonStates::Started:
onPauseClicked();
Q_FALLTHROUGH();
case ButtonStates::Paused:
Q_FALLTHROUGH();
case ButtonStates::Stopped:
Q_FALLTHROUGH();
default:
setButtonStates(ButtonStates::AllOff);
disableAll();
emit setTopicButtonStates(false);
return;
}
}
void AvmPlaybackMenu::onResumeFromPlaybackApp()
{
qWarning() << "onResumeFromPlaybackApp";
switch (m_buttonStateBeforePlaybackAppPause)
{
case ButtonStates::AllOff:
return;
case ButtonStates::Started:
enableAll();
onStartClicked();
emit setTopicButtonStates(true);
return;
case ButtonStates::Paused:
Q_FALLTHROUGH();
case ButtonStates::Stopped:
Q_FALLTHROUGH();
default:
enableAll();
setButtonStates(m_buttonStateBeforePlaybackAppPause);
emit setTopicButtonStates(true);
return;
}
}
void AvmPlaybackMenu::addStream(QComboBox *cb)
{
const auto currentStream(cb->currentData().value<AvaStream>());
if (currentStream.internalName.isEmpty())
return;
const int streamCount = m_ui->lwStreams->count();
if (streamCount >= m_maxSelectedStreams)
{
new LtsWidgets::LtsMessageDialogNew(this, tr("Cannot add more than %1 streams").arg(m_maxSelectedStreams));
return;
}
for (int i = 0; i < m_ui->lwStreams->count(); i++)
{
const QListWidgetItem *item = m_ui->lwStreams->item(i);
if (item->data(Qt::UserRole).value<AvaStream>() == currentStream)
return;
}
auto item = new QListWidgetItem(currentStream.internalName);
item->setData(Qt::UserRole, QVariant::fromValue(currentStream));
m_ui->lwStreams->addItem(item);
trySelectedStreams();
}
void AvmPlaybackMenu::onAddStreamClip()
{
addStream(m_ui->cbStreams);
}
void AvmPlaybackMenu::onAddStreamMarker()
{
addStream(m_ui->cbMarkerStreams);
}
void AvmPlaybackMenu::onRemoveSelectedStreams()
{
const QModelIndexList indexList = m_ui->lwStreams->selectionModel()->selectedRows();
for (const QModelIndex &idx : indexList)
delete m_ui->lwStreams->item(idx.row());
}
void AvmPlaybackMenu::onStreamNameMappingChanged(/*int32_t \*mountingPointId*\, const QString &internalName, const QString &displayName*/)
{
QSignalBlocker block1(m_ui->cbStreams);
for (int i = 0; i < m_ui->cbStreams->count(); i++)
m_ui->cbStreams->setItemText(i, m_streamNameMapping->displayName(m_ui->cbStreams->itemData(i).value<AvaStream>().internalName));
QSignalBlocker block2(m_ui->cbMarkerStreams);
for (int i = 0; i < m_ui->cbMarkerStreams->count(); i++)
m_ui->cbMarkerStreams->setItemText(i, m_streamNameMapping->displayName(m_ui->cbMarkerStreams->itemData(i).value<AvaStream>().internalName));
for (int i = 0; i < m_ui->lwStreams->count(); i++)
{
QListWidgetItem *item = m_ui->lwStreams->item(i);
item->setData(Qt::UserRole, m_streamNameMapping->displayName(item->data(Qt::UserRole).value<AvaStream>().internalName));
}
}
bool AvmPlaybackMenu::trySelectedStreams()
{
if (!m_ui->lwStreams->count())
return false;
QVector<AvaStream> vec;
for (int i = 0; i < m_ui->lwStreams->count(); i++)
vec.append(m_ui->lwStreams->item(i)->data(Qt::UserRole).value<AvaStream>());
onStreamSelectionChanged(vec);
return true;
}
void AvmPlaybackMenu::onStreamSelected()
{
if (trySelectedStreams())
return;
if (m_ui->cbStreams->currentIndex() < 0)
return;
QVector<AvaStream> vec;
AvaStream stream = m_ui->cbStreams->currentData().value<AvaStream>();
vec.append(stream);
onStreamSelectionChanged(vec);
m_currentStream = stream;
emit signalCurrentStreamChanged(m_streamNameMapping->displayName(stream.internalName));
}
void AvmPlaybackMenu::onMarkerStreamSelected()
{
if (trySelectedStreams())
return;
if (m_ui->cbMarkerStreams->currentIndex() < 0)
return;
const QVariant data(m_ui->cbMarkerStreams->currentData());
if (data.isValid() && data.canConvert<AvaStream>())
{
const AvaStream stream(data.value<AvaStream>());
onStreamSelectionChanged({stream});
m_currentStream = stream;
emit signalCurrentStreamChanged(m_streamNameMapping->displayName(stream.internalName));
}
else
{
m_currentStream = boost::none;
emit signalCurrentStreamChanged({});
}
}
void AvmPlaybackMenu::setStreamByName(const QString &name)
{
using AudioVideoManagement::AvaStream;
for (int i = 0; i < m_ui->cbStreams->count(); ++i)
{
QVariant data = m_ui->cbStreams->itemData(i);
Q_ASSERT(data.canConvert<AvaStream>());
AvaStream stream = m_ui->cbStreams->itemData(i).value<AvaStream>();
if (stream.internalName == name)
{
m_ui->cbStreams->setCurrentIndex(i);
return;
}
}
m_ui->cbStreams->setCurrentIndex(0);
}
void AvmPlaybackMenu::onMarkerModifyReply(const Core::FunctionReplyObject &replyObject)
{
m_requestHelper->requestReply(replyObject);
}
void AvmPlaybackMenu::retranslate()
{
m_ui->retranslateUi(this);
m_ui->tabWidget->setTabText(0, m_isAudio ? tr("Audio clips") : tr("Video clips"));
}
void AvmPlaybackMenu::setStreamTime(qint64 time)
{
m_ui->dtCurrentTime->setMsecsSinceEpoch(time);
}
//void AvmPlaybackMenu::setStreamTime(AvaTimePoint t)
//{
// m_savedPos = t;
// m_intervalModel.setCurrentPosition(t);
// m_timeControl->setTime(t);
//}
void AvmPlaybackMenu::onSessionEstablished()
{
m_ui->lblAvsConnectionStatus->setText(QStringLiteral("<font color='green'>%1</font>").arg(tr("established")));
enableAll();
if (!m_intervalModel.currentPosition())
{
m_ui->pbIntervalBackward->setEnabled(false);
m_ui->pbIntervalForward->setEnabled(false);
}
else
{
bool forceSignal = true;
m_intervalModel.setCurrentPosition(*m_intervalModel.currentPosition(), forceSignal);
}
m_avaCtrl->setVolume(m_ui->slVolume->value());
if (!m_selectedStreams)
{
// This is the first connection. Or a reconnect and no streams were available before.
// Query the list of available streams in order to initially fill the ComboBox.
AvaStreamType type = AvaStreamType::Video;
if (m_isAudio)
type = AvaStreamType::Audio;
m_avaCtrl->getStreamList(type, AvaStreamSource::Server, this, &AvmPlaybackMenu::onStreamListReceived);
return;
}
else
{
// A re-connect happend.
// Then, we just want to restore the previously selected streams.
onStreamSelectionChanged(*m_selectedStreams);
}
}
void AvmPlaybackMenu::onDisconnected()
{
m_ui->lblAvsConnectionStatus->setText(QStringLiteral("<font color='orange'>%1</font>").arg(tr("connecting...")));
setButtonStates(ButtonStates::AllOff);
emit setTopicButtonStates(false);
disableAll();
m_timelineBuilder->reset();
}
void AvmPlaybackMenu::preselectStream(const QString &fallbackInternalStreamName)
{
if (m_markerIdPreset)
{
m_ui->tabWidget->setCurrentIndex(1); // marker tab
m_ui->tvMarker->selectRow(m_markerModel->findIndexForId(*m_markerIdPreset));
selectMarker(*m_markerIdPreset);
m_markerIdPreset.reset();
return;
}
{
const QString currentStreamName(m_presetStreamName.is_initialized() ? *m_presetStreamName : QString());
const QString currentInternalName(currentStreamName.isEmpty() ? fallbackInternalStreamName : QString());
DEBUG_LOG(m_logger) << "currentStreamName: " << currentStreamName;
DEBUG_LOG(m_logger) << "currentInternalName: " << currentInternalName;
m_presetStreamName.reset();
if (!currentInternalName.isEmpty() || !currentStreamName.isEmpty())
{
DEBUG_LOG(m_logger) << "stream preset set - searching for '" << currentStreamName << "'..";
bool found = false;
for (int i = 0; i < m_ui->cbStreams->count(); i++)
{
const AvaStream &curStream = m_ui->cbStreams->itemData(i).value<AvaStream>();
DEBUG_LOG(m_logger) << "stream " << i << ": " << curStream.displayName << " (" << curStream.internalName << ")";
if ((!currentInternalName.isEmpty() && curStream.displayName == currentInternalName) ||
(!currentStreamName.isEmpty() && curStream.internalName == currentStreamName))
{
found = true;
DEBUG_LOG(m_logger) << "stream preset set, found & selected";
m_ui->cbStreams->setCurrentIndex(i);
break;
}
}
if (!found)
WARNING_LOG(m_logger) << "new stream list does not contain old selected stream (" << currentInternalName << ") !";
}
else
DEBUG_LOG(m_logger) << "no stream preset set";
}
onStreamSelected();
}
void AvmPlaybackMenu::processPresets()
{
if (m_presetStartTime)
m_savedPos = *m_presetStartTime;
m_presetStartTime.reset();
preselectStream(m_ui->cbStreams->currentData().value<AvaStream>().internalName /* <= fallback stream */);
}
void AvmPlaybackMenu::onStreamListReceived(const QVector<AvaStream> &streams)
{
m_streams = streams;
QSignalBlocker block1(m_ui->cbStreams);
m_ui->cbStreams->clear();
for (const AvaStream &s : streams)
{
QString displayName = m_streamNameMapping->displayName(s.internalName);
m_ui->cbStreams->addItem(displayName, QVariant::fromValue(s));
}
processPresets();
}
void AvmPlaybackMenu::startPlayback(bool usePresets)
{
Q_ASSERT(m_currentButtonState != ButtonStates::AllOff);
if (m_currentButtonState == ButtonStates::Paused || m_currentButtonState == ButtonStates::Started)
onStopClicked();
if (usePresets && m_presetStartTime)
{
preselectStream(m_ui->cbStreams->currentData().value<AvaStream>().internalName);
m_savedPos = m_presetStartTime.get();
m_presetStartTime.reset();
}
else
onStartClicked();
}
void AvmPlaybackMenu::setSavedPos(qint64 msecs)
{
m_savedPos = AvaTimePoint(std::chrono::duration_cast<AvaDuration>(std::chrono::milliseconds(msecs)));
}
void AvmPlaybackMenu::onTimelineComplete(const AvaTimeline &timeline)
{
DEBUG_LOG(m_logger) << "onTimelineComplete";
m_intervalModel.setData(timeline.parts());
m_ui->tavIntervals->resizeColumnsToContents();
setButtonStates(ButtonStates::Stopped);
emit setTopicButtonStates(true);
if (timeline.isEmpty())
return;
AvaTimePoint newPos = timeline.startTime();
if (m_savedPos)
{
newPos = *m_savedPos;
m_savedPos.reset();
}
DEBUG_LOG(m_logger) << "onTimelineComplete " << std::chrono::milliseconds(newPos.time_since_epoch()).count();
m_avaCtrl->setAbsPosition(m_playbackApp.dialogId(), ALL_STREAMS(), newPos);
m_timelineIsComplete = true;
if (m_autostartOnce)
{
/* audio clips can be played as soon as it can, but as videos need a screen selection
* first, we need to wait for the selection first
* but if the selection was done faster than the timeline query, then it can already
* be played. otherwise the playback will start after the user selected the video screen
*/
if (m_isAudio || m_videoScreenSelectionDone)
{
m_autostartOnce = false;
startPlayback();
}
}
}
/**
* When we receive a stream position from AVS we only update the UI when command queue is empty.
* Otherwise we just save the position for later. Then, when the command queue is empty again,
* we apply the last saved position.
*/
void AvmPlaybackMenu::onStreamPositionReceived(const QString &dialogId, const QString &streamName, AvaTimePoint pos)
{
Q_UNUSED(streamName);
if (dialogId != m_playbackApp.dialogId())
return;
m_lastAvsPos = pos;
DEBUG_LOG(m_logger) << "onStreamPositionReceived: last avs pos now: " << std::chrono::milliseconds((*m_lastAvsPos).time_since_epoch()).count() << " - direct apply? " << m_avaCtrl->isEmpty();
if (m_avaCtrl->isEmpty())
applyAvsPos();
}
void AvmPlaybackMenu::onVolumeReceived(AvaVolume db)
{
m_lastAvsVolume = db;
if (m_avaCtrl->isEmpty())
applyAvsVolume();
}
void AvmPlaybackMenu::onCommandQueueEmpty()
{
// >> is needed to make the clip selection work, but ... why?? that's weird, because a stream selection does an applyAvsPos and then this method does it again....
if (m_lastAvsPos)
applyAvsPos();
if (m_lastAvsVolume)
applyAvsVolume();
}
void AvmPlaybackMenu::applyAvsPos()
{
Q_ASSERT(m_lastAvsPos);
// cfroebel 2018-02-28: What is this good for?
// int difTimes = AudioVideoManagement::toDateTime(*m_lastAvsPos).toMSecsSinceEpoch() - m_ui->dteCurrentTime->dateTime().toMSecsSinceEpoch();
// if (difTimes > 0 && difTimes < 750)
// return;
//qint64 y = std::chrono::milliseconds((*m_lastAvsPos).time_since_epoch()).count();
//DEBUG_LOG(m_logger) << "applyAvsPos: " << y << " (" << msecsSinceEpochToString(y) << ")";
m_intervalModel.setCurrentPosition(*m_lastAvsPos);
m_timeControl->setTime(*m_lastAvsPos);
m_lastAvsPos.reset();
}
void AvmPlaybackMenu::applyAvsVolume()
{
Q_ASSERT(m_lastAvsVolume);
m_ui->slVolume->setValue(*m_lastAvsVolume);
}
void AvmPlaybackMenu::onUiTimeChanged(AvaTimePoint t)
{
if (m_currentButtonState != ButtonStates::Paused &&
m_currentButtonState != ButtonStates::Stopped)
return;
DEBUG_LOG(m_logger) << "onUiTimeChanged " << std::chrono::milliseconds(t.time_since_epoch()).count();
m_avaCtrl->setAbsPosition(m_playbackApp.dialogId(), ALL_STREAMS(), t);
}
void AvmPlaybackMenu::onStreamSelectionChanged(const QVector<AvaStream> &selectedStreams)
{
DEBUG_LOG(m_logger) << "onStreamSelectionChanged count = " << selectedStreams.count();
Q_ASSERT(!selectedStreams.isEmpty());
// Save the currentPosition and apply it again after we received all queryTimeline-Responses.
if (!m_savedPos)
m_savedPos = m_intervalModel.currentPosition();
m_selectedStreams = selectedStreams;
onStopClicked();
m_intervalModel.reset();
setButtonStates(ButtonStates::AllOff);
emit setTopicButtonStates(false);
m_timelineBuilder->queryTimeline(selectedStreams); // => onTimelineReceived
// use chooseStreamOverview even when there's only one stream
m_avaCtrl->chooseStreamOverview(m_playbackApp.dialogId(), AvaStream::toStringList(selectedStreams));
m_avaCtrl->setPint(m_playbackApp.dialogId(), ALL_STREAMS(), m_skipEmpty);
}
/**<
* Called when a stream interval was activated in the TableView.
*/
void AvmPlaybackMenu::onIntervalActivated(const QModelIndex &index)
{
AvaTimelinePart interval = m_intervalModel.getRowData(index.row());
DEBUG_LOG(m_logger) << "start = " << std::chrono::milliseconds(interval.first.time_since_epoch()).count();
m_avaCtrl->setAbsPosition(m_playbackApp.dialogId(), ALL_STREAMS(), interval.first); // => onStreamPositionm_savedPosReceived
/* NOTE: above method's slot 'onStreamPositionReceived' does:
* m_intervalModel.setCurrentPosition(...);
* m_timeControl->setTime(...);
*/
//clear marker selection
m_ui->tvMarker->selectionModel()->clear();
m_ui->tavIntervals->selectionModel()->clear();
//emit signalSetStart(toMSecSinceEpoch(interval.first));
//emit signalSetEnd(toMSecSinceEpoch(interval.second));
}
void AvmPlaybackMenu::setStreamName(const QString &internalStreamName)
{
int idx = -1;
for (int i = 0; i < m_ui->cbStreams->count(); i++)
{
if (m_ui->cbStreams->itemData(i).value<AvaStream>().internalName == internalStreamName)
{
idx = i;
break;
}
}
if (idx == -1)
{
new LtsWidgets::LtsMessageDialogNew(this, tr("The sequence uses a stream that is not available anymore"), tr("Stream from sequence not found"));
m_ui->cbStreams->setCurrentText(QString());
return;
}
// select stream
QVector<AvaStream> vec;
AvaStream stream = m_ui->cbStreams->itemData(idx).value<AvaStream>();
vec.append(stream);
onStreamSelectionChanged(vec);
m_ui->cbStreams->setCurrentIndex(idx);
}
bool AvmPlaybackMenu::getSkipPause() const
{
return m_skipEmpty;
}
void AvmPlaybackMenu::setSkipPause(bool bSkip)
{
if (m_skipEmpty == bSkip)
return;
m_skipEmpty = !bSkip; // onSkipPauseClicked toggles the state
onSkipPauseClicked();
}
QString AvmPlaybackMenu::getInternalStreamName() const
{
if (m_currentStream)
return m_currentStream.get().internalName;
return {};
}
void AvmPlaybackMenu::selectMarker(const MarkerManagement::Marker &marker)
{
if (marker.id() == 0) // invalid?
return;
QSignalBlocker blocker(m_ui->cbMarkerStreams);
(void)blocker;
const auto &rowData = marker;//m_markerModel->getRowData(index.row());
DEBUG_LOG(m_logger) << "AvmPlaybackMenu::onMarkerClicked name = " << QString::fromStdString(rowData.name())
<< " note = " << QString::fromStdString(rowData.note());
bool selectMarkerStream = false;
for (const MarkerManagement::MarkerDescription &desc : rowData.descriptions())
{
if ((desc.markerType() == MarkerManagement::MarkerType::Video && m_isAudio == false) ||
(desc.markerType() == MarkerManagement::MarkerType::Audio && m_isAudio == true))
{
m_ui->cbMarkerStreams->clear();
break;
}
}
for (const MarkerManagement::MarkerDescription &desc : rowData.descriptions())
{
DEBUG_LOG(m_logger) << " description = " << QString::fromStdString(desc.content())
<< " desc.markerType()=" << desc.markerType();
if ((desc.markerType() == MarkerManagement::MarkerType::Video && m_isAudio == false) ||
(desc.markerType() == MarkerManagement::MarkerType::Audio && m_isAudio == true))
{
AvaStream avaStream;
avaStream.type = m_isAudio ? AvaStreamType::Audio : AvaStreamType::Video;
avaStream.source = AvaStreamSource::Server;
AvaStream selectedStream;
const QStringList streams(QString::fromStdString(desc.content()).split(QLatin1Char(',')));
for (const QString &sr : streams)
{
bool bIsSelected = false;
avaStream.internalName = sr.simplified();
if (sr.endsWith(QLatin1Char('*')))
{
QString &in = avaStream.internalName;
in = in.left(in.size() - 1);
bIsSelected = true;
}
avaStream.displayName = m_streamNameMapping->displayName(avaStream.internalName);
m_ui->cbMarkerStreams->addItem(avaStream.displayName, QVariant::fromValue(avaStream));
DEBUG_LOG(m_logger) << "streams[" << avaStream.internalName << "]"
<< " internaName=" << avaStream.internalName
<< " displayName=" << avaStream.displayName;
if (bIsSelected && selectedStream.internalName.isEmpty())
selectedStream = avaStream;
}
if (!selectedStream.displayName.isEmpty())
{
const QVariant data(QVariant::fromValue(selectedStream));
m_ui->cbMarkerStreams->setCurrentIndex(m_ui->cbMarkerStreams->findData(data));
selectMarkerStream = true; // don't select hundreds of streams, only do it once
}
}
}
qint64 time = rowData.timeStamp() - rowData.timeStampOffset();
DEBUG_LOG(m_logger) << "marker time: " << time << " (" << QDateTime::fromMSecsSinceEpoch(time).toString(QStringLiteral("dd.MM.yy HH:mm:ss.zzz")) << ")";
std::chrono::milliseconds t(time);
AvaTimePoint p(std::chrono::duration_cast<AvaDuration>(t));
//QSignalBlocker blocker(m_timeControl.get());
//onStopClicked();
m_savedPos = p;
// m_timeControl->setTime(p);
// m_avaCtrl->setAbsPosition(m_playbackApp.dialogId(), ALL_STREAMS(), p);
if (selectMarkerStream)
onMarkerStreamSelected();
emit markerSelected(QString::fromStdString(rowData.note()));
//emit signalSetStart(time);
m_ui->tavIntervals->selectionModel()->clear();
}
void AvmPlaybackMenu::selectMarker(int markerId)
{
selectMarker(m_mrkDataIfc->getDtc()->marker(markerId));
}
void AvmPlaybackMenu::onMarkerClicked(const QModelIndex &index)
{
if (!index.isValid())
return;
selectMarker(m_markerModel->getRowData(index.row()));
}
void AvmPlaybackMenu::onSetMarkerClicked()
{
if (!m_selectedStreams || m_selectedStreams.get().isEmpty())
{
new LtsWidgets::LtsMessageDialogNew(this, tr("No stream selected to create a marker from."));
WARNING_LOG(m_logger) << "onSetMarkerClicked called, but no selected streams available";
return;
}
bool ok = false;
QString note = QInputDialog::getText(this, tr("Enter Note"),
tr("Note:"), QLineEdit::Normal, QString(), &ok);
if (!ok)
return;
MarkerManagement::Marker mrk;
MarkerManagement::MarkerDescription ma;
ma.markerType(m_isAudio ? MarkerManagement::MarkerType::Audio : MarkerManagement::MarkerType::Video);
auto &descriptions = mrk.descriptions();
QStringList streamList;
streamList.reserve(m_selectedStreams->size());
const QLatin1Char star = QLatin1Char('*');
const auto &x = *m_selectedStreams;
for (const AvaStream &stream : x)
{
streamList += stream.internalName;
streamList.last().append(star);
}
ma.content() = streamList.join(QStringLiteral(", ")).toStdString();
descriptions.resize(descriptions.size() + 1, ma);
mrk.note(note.toStdString());
mrk.timeStamp(m_timeControl->timeMSec());
mrk.timeStampOffset(0);
mrk.operatorId(LtsSessionData::LtsSessionData::getInstance()->getOperatorId());
m_requestHelper->startSingleRequest([mrk, this]()
{
return m_mrkDataIfc->setMarker(mrk, this);
});
}
void AvmPlaybackMenu::onStartClicked()
{
// QString displayName = m_ui->cbMarkerStreams->currentText();
// QString streamName = m_streamNameMapping->internalName(displayName);
DEBUG_LOG(m_logger) << "onStartClicked"
<< " dialogId=" << m_playbackApp.dialogId()
<< " currentSpeed=" << currentSpeed()
// << " displayName=" << displayName
// << " streamName=" << streamName
;
if (!m_selectedStreams || m_selectedStreams.get().isEmpty())
{
if (qobject_cast<QAbstractButton *>(sender()) != nullptr) // called by pressing a button => pressing a button should never do nothing on an error, but show an error message
{
new LtsWidgets::LtsMessageDialogNew(this, tr("No stream selected to start a replay from."));
return;
}
WARNING_LOG(m_logger) << "onStartClicked called, but no selected streams available";
return;
}
m_avaCtrl->startStream(m_playbackApp.dialogId(), ALL_STREAMS(), currentSpeed());
m_avaCtrl->positionUpdate(m_playbackApp.dialogId(), m_selectedStreams->first().toString(), std::chrono::seconds(1));
setButtonStates(ButtonStates::Started);
}
void AvmPlaybackMenu::onPauseClicked()
{
m_avaCtrl->pauseStream(m_playbackApp.dialogId(), ALL_STREAMS());
setButtonStates(ButtonStates::Paused);
}
void AvmPlaybackMenu::onStopClicked()
{
m_avaCtrl->stopStream(m_playbackApp.dialogId(), ALL_STREAMS());
setButtonStates(ButtonStates::Stopped);
}
void AvmPlaybackMenu::onSkipPauseClicked()
{
m_skipEmpty = !m_skipEmpty;
if (m_skipEmpty)
m_ui->pbSkipPause->setIcon(QIcon(QStringLiteral("icon:media-vcr-nopause.svg")));
else
m_ui->pbSkipPause->setIcon(QIcon(QStringLiteral("icon:media-vcr-withpause.svg")));
m_avaCtrl->setPint(m_playbackApp.dialogId(), ALL_STREAMS(), m_skipEmpty);
}
void AvmPlaybackMenu::onAllStreams()
{
QSignalBlocker block2(m_ui->cbMarkerStreams);
m_ui->cbMarkerStreams->clear();
for (int i = 0; i < m_ui->cbStreams->count(); i++)
m_ui->cbMarkerStreams->addItem(m_ui->cbStreams->itemText(i), m_ui->cbStreams->itemData(i));
}
void AvmPlaybackMenu::onIntervalBackwardClicked()
{
const AvaTimelinePart &targetInterval = m_intervalModel.previousInterval();
DEBUG_LOG(m_logger) << "onIntervalBackwardClicked " << std::chrono::milliseconds(targetInterval.first.time_since_epoch()).count();
m_avaCtrl->setAbsPosition(m_playbackApp.dialogId(), ALL_STREAMS(), targetInterval.first);
}
void AvmPlaybackMenu::onIntervalForwardClicked()
{
const AvaTimelinePart &targetInterval = m_intervalModel.nextInterval();
DEBUG_LOG(m_logger) << "onIntervalForwardClicked " << std::chrono::milliseconds(targetInterval.first.time_since_epoch()).count();
m_avaCtrl->setAbsPosition(m_playbackApp.dialogId(), ALL_STREAMS(), targetInterval.first);
}
/**
* Called when the value of the speed slider changed.
*/
void AvmPlaybackMenu::onSpeedChanged()
{
m_ui->lbSpeedValue->setText(QString::number(currentSpeed()));
m_avaCtrl->setStreamSpeed(m_playbackApp.dialogId(), ALL_STREAMS(), currentSpeed());
if ( m_currentButtonState == ButtonStates::Paused)
m_avaCtrl->pauseStream(m_playbackApp.dialogId(), ALL_STREAMS());
else if ( m_currentButtonState == ButtonStates::Stopped)
m_avaCtrl->stopStream(m_playbackApp.dialogId(), ALL_STREAMS());
}
void AvmPlaybackMenu::onVolumeChanged(int percent)
{
m_ui->lbVolumeValue->setText(QString::number(percent));
m_avaCtrl->setVolume(percent);
}
/**
* Called when the current interval changed as a result of
* a position update or when the IntervalModel is reset.
*/
void AvmPlaybackMenu::onCurrentIntervalChanged(bool hasPreviousInterval, bool hasNextInterval, const QModelIndex &rowIndex)
{
if (m_ui->tabWidget->currentIndex() == 0)
{
m_ui->pbIntervalBackward->setEnabled(hasPreviousInterval);
m_ui->pbIntervalForward->setEnabled(hasNextInterval);
}
else
{
m_ui->pbIntervalBackward->setEnabled(false);
m_ui->pbIntervalForward->setEnabled(false);
}
if (rowIndex.isValid())
{
m_ui->tavIntervals->scrollTo(rowIndex);
m_timeControl->setInterval(m_intervalModel.getRowData(rowIndex.row()));
return;
}
if (m_timelineBuilder->timeline().isEmpty())
return;
AvaTimelinePart interval{m_timelineBuilder->timeline().startTime(), m_timelineBuilder->timeline().endTime()};
m_timeControl->setInterval(interval);
}
AvaSpeed AvmPlaybackMenu::currentSpeed() const
{
int uiValue = m_ui->slSpeed->value();
Q_ASSERT(uiValue < AvaSpeed::allowedValues().size());
AvaSpeed speed = AvaSpeed::allowedValues().at(uiValue);
return speed;
}
/**
* For now deals only with the start, pause, stop buttons.
*/
void AvmPlaybackMenu::setButtonStates(ButtonStates s)
{
switch (s)
{
case ButtonStates::AllOff:
m_ui->pbStart->setEnabled(false);
m_ui->pbStart->show();
m_ui->pbPause->setEnabled(false);
m_ui->pbPause->hide();
m_ui->pbStop->setEnabled(false);
m_ui->tvMarker->setEnabled(false);
m_ui->dtCurrentTime->setEnabled(false);
m_ui->slSequenceSlider->setEnabled(false);
m_ui->pbSetMarker->setEnabled(false);
break;
case ButtonStates::Started:
m_ui->pbStart->setEnabled(false);
m_ui->pbStart->hide();
m_ui->pbPause->setEnabled(true);
m_ui->pbPause->show();
m_ui->pbStop->setEnabled(true);
m_ui->tvMarker->setEnabled(false);
m_ui->dtCurrentTime->setEnabled(false);
m_ui->slSequenceSlider->setEnabled(false);
m_ui->pbSetMarker->setEnabled(false);
break;
case ButtonStates::Paused:
m_ui->pbStart->setEnabled(true);
m_ui->pbStart->show();
m_ui->pbPause->setEnabled(false);
m_ui->pbPause->hide();
m_ui->pbStop->setEnabled(true);
m_ui->tvMarker->setEnabled(true);
m_ui->dtCurrentTime->setEnabled(true);
m_ui->slSequenceSlider->setEnabled(true);
m_ui->pbSetMarker->setEnabled(true);
break;
case ButtonStates::Stopped:
m_ui->pbStart->setEnabled(true);
m_ui->pbStart->show();
m_ui->pbPause->setEnabled(false);
m_ui->pbPause->hide();
m_ui->pbStop->setEnabled(false);
m_ui->tvMarker->setEnabled(true);
m_ui->dtCurrentTime->setEnabled(true);
m_ui->slSequenceSlider->setEnabled(true);
m_ui->pbSetMarker->setEnabled(true);
break;
}
m_currentButtonState = s;
}
void AvmPlaybackMenu::enableAll(bool enabled)
{
m_ui->pbIntervalBackward->setEnabled(enabled);
m_ui->pbIntervalForward->setEnabled(enabled);
m_ui->pbSkipPause->setEnabled(enabled);
m_ui->slSpeed->setEnabled(enabled);
m_ui->slVolume->setEnabled(enabled);
m_ui->tavIntervals->setEnabled(enabled);
m_ui->tvMarker->setEnabled(enabled);
m_ui->cbStreams->setEnabled(enabled);
}
void AvmPlaybackMenu::hideStreamSelection()
{
// m_ui->wStreams->hide();
// m_ui->layoutWStream->removeItem(m_ui->vSpace);
m_ui->lblCbStreams->hide();
m_ui->cbStreams->hide();
}
qint64 AvmPlaybackMenu::getCurrentTime() const
{
return m_timeControl->timeMSec();
}
AvaSpeed AvmPlaybackMenu::getSpeed() const
{
return currentSpeed();
}
void AvmPlaybackMenu::setSpeed(AvaSpeed newSpeed)
{
m_ui->slSpeed->setValue(AvaSpeed::allowedValues().indexOf(newSpeed));
}
int AvmPlaybackMenu::getVolume() const
{
return m_ui->slVolume->value();
}
void AvmPlaybackMenu::setVolume(int newVolume)
{
m_ui->slVolume->setValue(newVolume);
}
} // namespace AudioVideoManagement
#include "avm_playback_menu.moc"
//