|
|
|
|
@ -23,11 +23,13 @@
|
|
|
|
|
#include <QUrl> |
|
|
|
|
#include <QDebug> |
|
|
|
|
#include <QImageReader> |
|
|
|
|
#include <QPersistentModelIndex> |
|
|
|
|
|
|
|
|
|
MessageDelegate::MessageDelegate(QObject *parent) |
|
|
|
|
: QStyledItemDelegate(parent) {} |
|
|
|
|
|
|
|
|
|
struct VoicePlayState { |
|
|
|
|
bool loading = false; |
|
|
|
|
bool playing = false; |
|
|
|
|
bool paused = false; |
|
|
|
|
qint64 startMs = 0; |
|
|
|
|
@ -48,6 +50,7 @@ static void ensureVoiceTimer() {
|
|
|
|
|
bool anyActive = false; |
|
|
|
|
SoundManager* sm = SoundManager::instance(); |
|
|
|
|
for (auto it = s_voiceStates.begin(); it != s_voiceStates.end(); ++it) { |
|
|
|
|
if (it->loading) { anyActive = true; continue; } |
|
|
|
|
if (!it->playing && !it->paused) { |
|
|
|
|
if (it->displayFrac > 0.001f || it->displayElapsed > 0.001f) anyActive = true; |
|
|
|
|
continue; |
|
|
|
|
@ -161,6 +164,7 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
|
|
|
|
|
|
|
|
|
ensureVoiceTimer(); |
|
|
|
|
VoicePlayState& st = s_voiceStates[filePath]; |
|
|
|
|
if (st.loading) return true; |
|
|
|
|
|
|
|
|
|
QString durStr = index.data(MsgVoiceDurationRole).toString(); |
|
|
|
|
durStr.remove('s'); |
|
|
|
|
@ -175,21 +179,21 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
|
|
|
|
|
|
|
|
|
if (!st.playing && !st.paused) { |
|
|
|
|
/* Start playback then seek */ |
|
|
|
|
float actualDur = VoicePlayer::playOpusFile(filePath); |
|
|
|
|
if (actualDur <= 0.0f) return false; |
|
|
|
|
targetSec = clickFrac * actualDur; |
|
|
|
|
st.playing = true; |
|
|
|
|
st.paused = false; |
|
|
|
|
st.durationSec = actualDur; |
|
|
|
|
st.startMs = now - (qint64)(targetSec * 1000.0); |
|
|
|
|
st.pausedPosSec = targetSec; |
|
|
|
|
st.displayFrac = (targetSec > 0 && actualDur > 0) ? (targetSec / actualDur) : 0; |
|
|
|
|
st.displayElapsed = targetSec; |
|
|
|
|
int sr = SoundManager::instance()->currentPcmSampleRate(); |
|
|
|
|
if (sr > 0) |
|
|
|
|
SoundManager::instance()->seekCurrentPcm((unsigned long long)(targetSec * sr)); |
|
|
|
|
st.loading = true; |
|
|
|
|
QPersistentModelIndex persistent(index); |
|
|
|
|
VoicePlayer::playOpusFile(filePath, this, [filePath, clickFrac, persistent](float actualDur) { |
|
|
|
|
s_voiceStates[filePath].loading = false; |
|
|
|
|
if (actualDur <= 0) return; |
|
|
|
|
MessageDelegate::startAutoPlay(filePath, actualDur); |
|
|
|
|
float position = clickFrac * actualDur; |
|
|
|
|
auto& state = s_voiceStates[filePath]; |
|
|
|
|
state.startMs -= (qint64)(position * 1000); state.pausedPosSec = position; |
|
|
|
|
state.displayFrac = clickFrac; state.displayElapsed = position; |
|
|
|
|
int rate = SoundManager::instance()->currentPcmSampleRate(); |
|
|
|
|
if (rate > 0) SoundManager::instance()->seekCurrentPcm((unsigned long long)(position * rate)); |
|
|
|
|
if (persistent.isValid()) const_cast<QAbstractItemModel*>(persistent.model())->setData(persistent, 1, MsgVoicePlayStateRole); |
|
|
|
|
}); |
|
|
|
|
s_voiceTimer->start(); |
|
|
|
|
model->setData(index, 1, MsgVoicePlayStateRole); |
|
|
|
|
} else { |
|
|
|
|
/* Currently playing or paused — seek */ |
|
|
|
|
st.startMs = now - (qint64)(targetSec * 1000.0); |
|
|
|
|
@ -246,18 +250,15 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
|
|
|
|
SoundManager::instance()->stopCurrentPcm(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
float actualDur = VoicePlayer::playOpusFile(filePath); |
|
|
|
|
if (actualDur <= 0.0f) return false; |
|
|
|
|
st.playing = true; |
|
|
|
|
st.paused = false; |
|
|
|
|
st.pausedPosSec = 0; |
|
|
|
|
st.startMs = QDateTime::currentMSecsSinceEpoch(); |
|
|
|
|
st.durationSec = actualDur; |
|
|
|
|
st.displayFrac = 0; |
|
|
|
|
st.displayElapsed = 0; |
|
|
|
|
|
|
|
|
|
st.loading = true; |
|
|
|
|
QPersistentModelIndex persistent(index); |
|
|
|
|
VoicePlayer::playOpusFile(filePath, this, [filePath, persistent](float actualDur) { |
|
|
|
|
s_voiceStates[filePath].loading = false; |
|
|
|
|
if (actualDur <= 0) return; |
|
|
|
|
MessageDelegate::startAutoPlay(filePath, actualDur); |
|
|
|
|
if (persistent.isValid()) const_cast<QAbstractItemModel*>(persistent.model())->setData(persistent, 1, MsgVoicePlayStateRole); |
|
|
|
|
}); |
|
|
|
|
s_voiceTimer->start(); |
|
|
|
|
model->setData(index, 1, MsgVoicePlayStateRole); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -1492,6 +1493,7 @@ void MessageDelegate::drawAvatar(QPainter *p, const Layout &L,
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MessageDelegate::startAutoPlay(const QString& filePath, float actualDurationSec) { |
|
|
|
|
if (actualDurationSec <= 0) return; |
|
|
|
|
ensureVoiceTimer(); |
|
|
|
|
VoicePlayState& st = s_voiceStates[filePath]; |
|
|
|
|
st.playing = true; |
|
|
|
|
@ -1506,6 +1508,8 @@ void MessageDelegate::startAutoPlay(const QString& filePath, float actualDuratio
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MessageDelegate::stopAllAutoPlay() { |
|
|
|
|
for (auto it = s_voiceStates.begin(); it != s_voiceStates.end(); ++it) |
|
|
|
|
it->playing = false; |
|
|
|
|
VoicePlayer::cancelPending(); |
|
|
|
|
for (auto it = s_voiceStates.begin(); it != s_voiceStates.end(); ++it) { |
|
|
|
|
it->playing = false; it->paused = false; it->loading = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|