|
|
|
|
@ -71,15 +71,58 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
|
|
|
|
|
|
|
|
|
QMouseEvent* me = static_cast<QMouseEvent*>(event); |
|
|
|
|
QPoint localPos = me->pos() - option.rect.topLeft(); |
|
|
|
|
if (!L.voicePlayBtnRect.contains(localPos)) return false; |
|
|
|
|
|
|
|
|
|
bool onPlayBtn = L.voicePlayBtnRect.contains(localPos); |
|
|
|
|
bool onWaveform = L.voiceWaveformRect.contains(localPos); |
|
|
|
|
if (!onPlayBtn && !onWaveform) return false; |
|
|
|
|
|
|
|
|
|
QString filePath = index.data(MsgVoiceFileRole).toString(); |
|
|
|
|
GUI_DEBUG("editorEvent voice click: filePath=%s exists=%d", |
|
|
|
|
qPrintable(filePath), QFile::exists(filePath)); |
|
|
|
|
GUI_DEBUG("editorEvent voice click: filePath=%s exists=%d wave=%d", |
|
|
|
|
qPrintable(filePath), QFile::exists(filePath), (int)onWaveform); |
|
|
|
|
if (filePath.isEmpty()) return false; |
|
|
|
|
|
|
|
|
|
ensureVoiceTimer(); |
|
|
|
|
VoicePlayState& st = s_voiceStates[filePath]; |
|
|
|
|
|
|
|
|
|
QString durStr = index.data(MsgVoiceDurationRole).toString(); |
|
|
|
|
durStr.remove('s'); |
|
|
|
|
float dur = durStr.toFloat(); |
|
|
|
|
if (dur <= 0) dur = 1.0f; |
|
|
|
|
|
|
|
|
|
if (onWaveform) { |
|
|
|
|
float clickFrac = (float)(localPos.x() - L.voiceWaveformRect.x()) / L.voiceWaveformRect.width(); |
|
|
|
|
if (clickFrac < 0) clickFrac = 0; if (clickFrac > 1) clickFrac = 1; |
|
|
|
|
float targetSec = clickFrac * dur; |
|
|
|
|
qint64 now = QDateTime::currentMSecsSinceEpoch(); |
|
|
|
|
|
|
|
|
|
if (!st.playing && !st.paused) { |
|
|
|
|
/* Start playback then seek */ |
|
|
|
|
st.playing = true; |
|
|
|
|
st.paused = false; |
|
|
|
|
st.durationSec = dur; |
|
|
|
|
st.startMs = now - (qint64)(targetSec * 1000.0); |
|
|
|
|
st.pausedPosSec = targetSec; |
|
|
|
|
VoicePlayer::playOpusFile(filePath); |
|
|
|
|
int sr = SoundManager::instance()->currentPcmSampleRate(); |
|
|
|
|
if (sr > 0) |
|
|
|
|
SoundManager::instance()->seekCurrentPcm((unsigned long long)(targetSec * sr)); |
|
|
|
|
s_voiceTimer->start(); |
|
|
|
|
model->setData(index, 1, MsgVoicePlayStateRole); |
|
|
|
|
} else { |
|
|
|
|
/* Currently playing or paused — seek */ |
|
|
|
|
st.startMs = now - (qint64)(targetSec * 1000.0); |
|
|
|
|
if (st.paused) |
|
|
|
|
st.pausedPosSec = targetSec; |
|
|
|
|
else if (!st.playing) |
|
|
|
|
{ st.playing = true; s_voiceTimer->start(); } |
|
|
|
|
int sr = SoundManager::instance()->currentPcmSampleRate(); |
|
|
|
|
if (sr > 0) |
|
|
|
|
SoundManager::instance()->seekCurrentPcm((unsigned long long)(targetSec * sr)); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* --- Play button click (original logic) --- */ |
|
|
|
|
bool isOtherPlaying = false; |
|
|
|
|
for (auto it = s_voiceStates.begin(); it != s_voiceStates.end(); ++it) { |
|
|
|
|
if (it.key() != filePath && (it->playing || it->paused)) |
|
|
|
|
@ -117,11 +160,6 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
|
|
|
|
SoundManager::instance()->stopCurrentPcm(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QString durStr = index.data(MsgVoiceDurationRole).toString(); |
|
|
|
|
durStr.remove('s'); |
|
|
|
|
float dur = durStr.toFloat(); |
|
|
|
|
if (dur <= 0) dur = 1.0f; |
|
|
|
|
|
|
|
|
|
st.playing = true; |
|
|
|
|
st.paused = false; |
|
|
|
|
st.pausedPosSec = 0; |
|
|
|
|
@ -633,7 +671,11 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
|
|
|
|
elapsed = (now - st.startMs) / 1000.0f; |
|
|
|
|
if (elapsed > durSec) elapsed = durSec; |
|
|
|
|
} |
|
|
|
|
if (durSec > 0) frac = elapsed / durSec; |
|
|
|
|
if (durSec > 0) { |
|
|
|
|
float dispElapsed = elapsed - 0.2f; /* 200ms audio output delay */ |
|
|
|
|
if (dispElapsed < 0) dispElapsed = 0; |
|
|
|
|
frac = dispElapsed / durSec; |
|
|
|
|
} |
|
|
|
|
if (frac < 0) frac = 0; if (frac > 1) frac = 1; |
|
|
|
|
|
|
|
|
|
QRectF playR = L.voicePlayBtnRect; |
|
|
|
|
|