Reject incomplete DB reads and clock voice progress from PCM outside paint
This commit is contained in:
1 parent
1235f770eb
commit
034df2939c
2 files changed
+84
-119
No files matched your search
@@ -97,7 +97,8 @@ QList<ChannelRow> DbManager::getChannels(ChannelSortMode sort) const {
|
||||
" FROM channels%s", orderBy);
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql);
|
||||
if (!stmt) return list;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int rc;
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
ChannelRow r;
|
||||
r.channelId = colText(stmt, 0);
|
||||
r.name = colText(stmt, 1);
|
||||
@@ -115,7 +116,7 @@ QList<ChannelRow> DbManager::getChannels(ChannelSortMode sort) const {
|
||||
r.avatarHash = colBlob(stmt, 13);
|
||||
list.append(r);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
if (!finishRead(stmt, rc)) return {};
|
||||
for (auto& ch : list) {
|
||||
ch.lastReadMsgId = getUiStateInt64(
|
||||
QStringLiteral("ch_%1_last_read").arg(ch.channelId), 0);
|
||||
@@ -165,11 +166,9 @@ bool DbManager::isChannelMember(const QString& chId, quint64 nodeId) const {
|
||||
const QByteArray sql = QStringLiteral("SELECT 1 FROM \"%1\" WHERE node_id=?").arg(peersTableName(chId)).toUtf8();
|
||||
sqlite3_stmt* statement = prepareOrNull(sql.constData());
|
||||
if (!statement) return false;
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)nodeId);
|
||||
const int result = sqlite3_step(statement);
|
||||
if (result != SQLITE_ROW && result != SQLITE_DONE) GUI_ERROR("channel membership read failed ch=%s rc=%d", qPrintable(chId), result);
|
||||
sqlite3_finalize(statement);
|
||||
return result == SQLITE_ROW;
|
||||
const int bound = sqlite3_bind_int64(statement, 1, (sqlite3_int64)nodeId);
|
||||
const int result = bound == SQLITE_OK ? sqlite3_step(statement) : bound;
|
||||
return finishRead(statement, result) && result == SQLITE_ROW;
|
||||
}
|
||||
|
||||
/* Формирует человекочитаемое тело превью последнего сообщения по content_type и "d"-полю. */
|
||||
@@ -227,7 +226,8 @@ QString DbManager::getChannelLastMessage(const QString& channelId) const {
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
|
||||
if (!stmt) return {};
|
||||
QString result;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const int rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
quint64 author = (quint64)sqlite3_column_int64(stmt, 0);
|
||||
QByteArray jdata = colBlob(stmt, 1);
|
||||
QString ct = QString::fromUtf8(parseJsonField(jdata, "ct"));
|
||||
@@ -235,8 +235,7 @@ QString DbManager::getChannelLastMessage(const QString& channelId) const {
|
||||
QString nick = getDisplayName(author);
|
||||
result = nick + QStringLiteral(": ") + mediaPreviewBody(ct, d);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
return finishRead(stmt, rc) ? result : QString();
|
||||
}
|
||||
|
||||
/* ── messages (UI) ── */
|
||||
@@ -265,7 +264,7 @@ static MessageRow readMessage(sqlite3_stmt* stmt, quint64 myId) {
|
||||
/* Окно вокруг оригинала выбирается одним SQL snapshot, включая одинаковые timestamp. */
|
||||
QList<MessageRow> DbManager::getMessages(const QString& chId, int limit, const QByteArray& around) const {
|
||||
QList<MessageRow> list;
|
||||
if (!around.isEmpty() && (around.size() != 32 || limit <= 0)) {
|
||||
if (limit <= 0 || (!around.isEmpty() && around.size() != 32)) {
|
||||
GUI_ERROR("history: invalid anchor/window bytes=%d limit=%d", int(around.size()), limit); return list;
|
||||
}
|
||||
QString sql = QStringLiteral(
|
||||
@@ -277,19 +276,24 @@ QList<MessageRow> DbManager::getMessages(const QString& chId, int limit, const Q
|
||||
"max(0,(SELECT count(*) FROM \"%1\")-?1))").arg(msgTableName(chId));
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
|
||||
if (!stmt) return list;
|
||||
sqlite3_bind_int(stmt, 1, limit);
|
||||
if (!around.isEmpty()) sqlite3_bind_blob(stmt, 2, around.constData(), around.size(), SQLITE_TRANSIENT);
|
||||
uint64_t myId = 0;
|
||||
{ sqlite3_stmt* ms = prepareOrNull("SELECT node_id FROM local_identity WHERE id=1");
|
||||
if (ms) { if (sqlite3_step(ms) == SQLITE_ROW) myId = (uint64_t)sqlite3_column_int64(ms, 0); sqlite3_finalize(ms); } }
|
||||
int rc;
|
||||
int rc = sqlite3_bind_int(stmt, 1, limit);
|
||||
if (rc == SQLITE_OK && !around.isEmpty()) rc = sqlite3_bind_blob(stmt, 2, around.constData(), around.size(), SQLITE_TRANSIENT);
|
||||
if (rc != SQLITE_OK) { finishRead(stmt, rc); return {}; }
|
||||
uint64_t myId = m_myNodeId;
|
||||
if (!myId) {
|
||||
sqlite3_stmt* identity = prepareOrNull("SELECT node_id FROM local_identity WHERE id=1");
|
||||
if (identity) {
|
||||
const int row = sqlite3_step(identity);
|
||||
if (row == SQLITE_ROW) myId = (uint64_t)sqlite3_column_int64(identity, 0);
|
||||
if (!finishRead(identity, row)) { finishRead(stmt, SQLITE_ABORT); return {}; }
|
||||
}
|
||||
}
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
list.prepend(readMessage(stmt, myId));
|
||||
}
|
||||
if (rc != SQLITE_DONE) GUI_ERROR("history: read failed ch=%s rc=%d", qPrintable(chId), rc);
|
||||
if (!finishRead(stmt, rc)) return {};
|
||||
if (!around.isEmpty()) GUI_DEBUG("history: window loaded ch=%s anchor=%s rows=%d limit=%d",
|
||||
qPrintable(chId), around.toHex().constData(), int(list.size()), limit);
|
||||
sqlite3_finalize(stmt);
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -300,13 +304,13 @@ QList<MessageRow> DbManager::getMessagesSince(const QString& chId, qint64 sinceI
|
||||
" FROM \"%1\" WHERE id > ? ORDER BY id ASC").arg(msgTableName(chId));
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
|
||||
if (!stmt) return list;
|
||||
sqlite3_bind_int64(stmt, 1, sinceId);
|
||||
int rc = sqlite3_bind_int64(stmt, 1, sinceId);
|
||||
if (rc != SQLITE_OK) { finishRead(stmt, rc); return {}; }
|
||||
uint64_t myId = m_myNodeId;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
list.append(readMessage(stmt, myId));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return list;
|
||||
return finishRead(stmt, rc) ? list : QList<MessageRow>();
|
||||
}
|
||||
|
||||
MessageRow DbManager::getMessageById(const QString& chId, qint64 msgId) const {
|
||||
@@ -316,12 +320,12 @@ MessageRow DbManager::getMessageById(const QString& chId, qint64 msgId) const {
|
||||
" FROM \"%1\" WHERE id = ?").arg(msgTableName(chId));
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
|
||||
if (!stmt) return m;
|
||||
sqlite3_bind_int64(stmt, 1, msgId);
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int rc = sqlite3_bind_int64(stmt, 1, msgId);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
m = readMessage(stmt, m_myNodeId);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return m;
|
||||
return finishRead(stmt, rc) ? m : MessageRow();
|
||||
}
|
||||
|
||||
/* ── DM-беседы (UI, read-only) ── */
|
||||
@@ -334,12 +338,10 @@ MessageRow DbManager::getMessageByKey(const QString& chId, const QByteArray& key
|
||||
"FROM \"%1\" WHERE message_id=?").arg(msgTableName(chId)).toUtf8();
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.constData());
|
||||
if (!stmt) return message;
|
||||
sqlite3_bind_blob(stmt, 1, key.constData(), key.size(), SQLITE_TRANSIENT);
|
||||
const int rc = sqlite3_step(stmt);
|
||||
int rc = sqlite3_bind_blob(stmt, 1, key.constData(), key.size(), SQLITE_TRANSIENT);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) message = readMessage(stmt, m_myNodeId);
|
||||
else if (rc != SQLITE_DONE) GUI_ERROR("reply: lookup failed ch=%s rc=%d", qPrintable(chId), rc);
|
||||
sqlite3_finalize(stmt);
|
||||
return message;
|
||||
return finishRead(stmt, rc) ? message : MessageRow();
|
||||
}
|
||||
|
||||
/* Короткий обычный текст без рекурсивных цитат и служебного описания медиа. */
|
||||
@@ -378,12 +380,7 @@ QList<DmConversation> DbManager::getDmConversations() const {
|
||||
c.generation = (quint64)sqlite3_column_int64(stmt, 6);
|
||||
list.append(c);
|
||||
}
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DM, "DM list: read failed rc=%d sql=%s", rc, sqlite3_errmsg(m_db));
|
||||
list.clear();
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return list;
|
||||
return finishRead(stmt, rc) ? list : QList<DmConversation>();
|
||||
}
|
||||
|
||||
/* ── peers (UI) ── */
|
||||
@@ -396,31 +393,21 @@ QMap<quint64, QString> DbManager::getLocalNicknames(const QString& chId) const {
|
||||
).arg(peersTableName(chId));
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
|
||||
if (!stmt) return m;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW)
|
||||
int rc;
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
|
||||
m.insert((quint64)sqlite3_column_int64(stmt, 0), colText(stmt, 1));
|
||||
sqlite3_finalize(stmt);
|
||||
return m;
|
||||
return finishRead(stmt, rc) ? m : QMap<quint64, QString>();
|
||||
}
|
||||
|
||||
QString DbManager::getDisplayName(quint64 nodeId) const {
|
||||
sqlite3_stmt* st = prepareOrNull(
|
||||
"SELECT display_name FROM accounts WHERE node_id=?");
|
||||
if (st) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
QString s = colText(st, 0);
|
||||
sqlite3_finalize(st);
|
||||
if (!s.isEmpty()) return s;
|
||||
} else sqlite3_finalize(st);
|
||||
}
|
||||
st = prepareOrNull("SELECT name FROM nodes WHERE node_id=?");
|
||||
if (st) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
QString s = colText(st, 0);
|
||||
sqlite3_finalize(st);
|
||||
if (!s.isEmpty()) return s;
|
||||
} else sqlite3_finalize(st);
|
||||
for (const char* sql : {"SELECT display_name FROM accounts WHERE node_id=?", "SELECT name FROM nodes WHERE node_id=?"}) {
|
||||
sqlite3_stmt* st = prepareOrNull(sql);
|
||||
if (!st) continue;
|
||||
int rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
||||
QString name = rc == SQLITE_ROW ? colText(st, 0) : QString();
|
||||
if (!finishRead(st, rc)) break;
|
||||
if (!name.isEmpty()) return name;
|
||||
}
|
||||
return QString("node_%1").arg(nodeId);
|
||||
}
|
||||
@@ -428,10 +415,12 @@ QString DbManager::getDisplayName(quint64 nodeId) const {
|
||||
QByteArray DbManager::getNodeEdPub(quint64 nodeId) const {
|
||||
sqlite3_stmt* stmt = prepareOrNull("SELECT ed25519_pubkey FROM nodes WHERE node_id=?");
|
||||
if (!stmt) return {};
|
||||
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
|
||||
int rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
QByteArray r;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) r = colBlob(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
if (rc == SQLITE_ROW) r = colBlob(stmt, 0);
|
||||
if (!finishRead(stmt, rc)) return {};
|
||||
if (!r.isEmpty() && r.size() != 32) { GUI_ERROR("node signing key has invalid length=%d", int(r.size())); return {}; }
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -446,7 +435,8 @@ QList<AccountRow> DbManager::getAccounts(bool contactsOnly) const {
|
||||
" FROM accounts ORDER BY display_name";
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql);
|
||||
if (!stmt) return list;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int rc;
|
||||
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
AccountRow a;
|
||||
a.nodeId = (quint64)sqlite3_column_int64(stmt, 0);
|
||||
a.displayName = colText(stmt, 1);
|
||||
@@ -455,8 +445,7 @@ QList<AccountRow> DbManager::getAccounts(bool contactsOnly) const {
|
||||
a.isContact = sqlite3_column_int(stmt, 4) != 0;
|
||||
list.append(a);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return list;
|
||||
return finishRead(stmt, rc) ? list : QList<AccountRow>();
|
||||
}
|
||||
|
||||
AccountRow DbManager::getAccount(quint64 nodeId) const {
|
||||
@@ -465,16 +454,16 @@ AccountRow DbManager::getAccount(quint64 nodeId) const {
|
||||
"SELECT node_id, display_name, avatar_color, avatar_letter, is_contact"
|
||||
" FROM accounts WHERE node_id=?");
|
||||
if (!stmt) return a;
|
||||
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
a.nodeId = (quint64)sqlite3_column_int64(stmt, 0);
|
||||
a.displayName = colText(stmt, 1);
|
||||
a.avatarColor = colText(stmt, 2);
|
||||
a.avatarLetter = colText(stmt, 3);
|
||||
a.isContact = sqlite3_column_int(stmt, 4) != 0;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return a;
|
||||
return finishRead(stmt, rc) ? a : AccountRow();
|
||||
}
|
||||
|
||||
/* ── ui_state ── */
|
||||
@@ -482,11 +471,11 @@ AccountRow DbManager::getAccount(quint64 nodeId) const {
|
||||
QString DbManager::getUiState(const QString& key) const {
|
||||
sqlite3_stmt* stmt = prepareOrNull("SELECT value FROM ui_state WHERE key=?");
|
||||
if (!stmt) return {};
|
||||
sqlite3_bind_text(stmt, 1, key.toUtf8().constData(), -1, SQLITE_TRANSIENT);
|
||||
int rc = sqlite3_bind_text(stmt, 1, key.toUtf8().constData(), -1, SQLITE_TRANSIENT);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
QString val;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) val = colText(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
return val;
|
||||
if (rc == SQLITE_ROW) val = colText(stmt, 0);
|
||||
return finishRead(stmt, rc) ? val : QString();
|
||||
}
|
||||
|
||||
int DbManager::getUiStateInt(const QString& key, int defaultVal) const {
|
||||
@@ -510,11 +499,11 @@ int DbManager::getUnreadCount(const QString& channelId, qint64 lastReadMsgId) co
|
||||
"SELECT COUNT(*) FROM \"%1\" WHERE id > ?").arg(msgTableName(channelId));
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
|
||||
if (!stmt) return 0;
|
||||
sqlite3_bind_int64(stmt, 1, lastReadMsgId);
|
||||
int rc = sqlite3_bind_int64(stmt, 1, lastReadMsgId);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
int count = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) count = sqlite3_column_int(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
return count;
|
||||
if (rc == SQLITE_ROW) count = sqlite3_column_int(stmt, 0);
|
||||
return finishRead(stmt, rc) ? count : 0;
|
||||
}
|
||||
|
||||
/* ── integrity checks ── */
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <QDebug>
|
||||
#include <QImageReader>
|
||||
#include <QPersistentModelIndex>
|
||||
#include <QPointer>
|
||||
|
||||
/* Один layout для размеров, отрисовки, выделения и hit-test ссылок. */
|
||||
static qreal layoutMessageText(QTextLayout& layout, int width, const QString& compactLink = {}) {
|
||||
@@ -92,7 +93,6 @@ struct VoicePlayState {
|
||||
bool loading = false;
|
||||
bool playing = false;
|
||||
bool paused = false;
|
||||
qint64 startMs = 0;
|
||||
float durationSec = 0.0f;
|
||||
float pausedPosSec = 0.0f;
|
||||
float displayFrac = 0.0f;
|
||||
@@ -111,19 +111,17 @@ 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;
|
||||
}
|
||||
if (it->paused) { anyActive = true; continue; }
|
||||
if (!sm->isPcmPlaying(it->playbackId)) {
|
||||
it->playing = false;
|
||||
it->paused = false;
|
||||
anyActive = true;
|
||||
} else {
|
||||
anyActive = true;
|
||||
}
|
||||
if (it->loading && !VoicePlayer::isPreparing()) it->loading = false;
|
||||
if (it->playing && !sm->isPcmPlaying(it->playbackId)) it->playing = false;
|
||||
float elapsed = it->paused ? it->pausedPosSec : 0;
|
||||
const int rate = sm->pcmSampleRate(it->playbackId);
|
||||
if (it->playing && rate > 0) elapsed = float(double(sm->pcmCursor(it->playbackId)) / rate);
|
||||
elapsed = qBound(0.0f, elapsed, it->durationSec);
|
||||
const float fraction = it->durationSec > 0 ? elapsed / it->durationSec : 0;
|
||||
const bool moving = std::fabs(fraction - it->displayFrac) > 0.001f || std::fabs(elapsed - it->displayElapsed) > 0.001f;
|
||||
it->displayFrac = moving ? it->displayFrac + (fraction - it->displayFrac) * 0.15f : fraction;
|
||||
it->displayElapsed = moving ? it->displayElapsed + (elapsed - it->displayElapsed) * 0.15f : elapsed;
|
||||
anyActive |= it->loading || it->playing || moving;
|
||||
}
|
||||
if (!anyActive) s_voiceTimer->stop();
|
||||
if (s_voiceView) s_voiceView->viewport()->update();
|
||||
@@ -288,7 +286,6 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
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 */
|
||||
@@ -300,7 +297,7 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
MessageDelegate::startAutoPlay(filePath, actualDur, playbackId);
|
||||
float position = clickFrac * actualDur;
|
||||
auto& state = s_voiceStates[filePath];
|
||||
state.startMs -= (qint64)(position * 1000); state.pausedPosSec = position;
|
||||
state.pausedPosSec = position;
|
||||
state.displayFrac = clickFrac; state.displayElapsed = position;
|
||||
int rate = SoundManager::instance()->pcmSampleRate(playbackId);
|
||||
if (rate > 0) SoundManager::instance()->seekPcm(playbackId, (unsigned long long)(position * rate));
|
||||
@@ -309,7 +306,6 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
s_voiceTimer->start();
|
||||
} else {
|
||||
/* Currently playing or paused — seek */
|
||||
st.startMs = now - (qint64)(targetSec * 1000.0);
|
||||
if (st.paused)
|
||||
st.pausedPosSec = targetSec;
|
||||
else if (!st.playing)
|
||||
@@ -332,8 +328,8 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
|
||||
if (st.playing) {
|
||||
/* Playing → Pause */
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
st.pausedPosSec = (now - st.startMs) / 1000.0f;
|
||||
const int rate = SoundManager::instance()->pcmSampleRate(st.playbackId);
|
||||
st.pausedPosSec = rate > 0 ? float(double(SoundManager::instance()->pcmCursor(st.playbackId)) / rate) : 0;
|
||||
st.playing = false;
|
||||
st.paused = true;
|
||||
SoundManager::instance()->pausePcm(st.playbackId);
|
||||
@@ -345,8 +341,6 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
/* Paused → Resume */
|
||||
st.paused = false;
|
||||
st.playing = true;
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
st.startMs = now - (qint64)(st.pausedPosSec * 1000.0);
|
||||
st.displayFrac = (dur > 0) ? (st.pausedPosSec / dur) : 0;
|
||||
st.displayElapsed = st.pausedPosSec;
|
||||
SoundManager::instance()->resumePcm(st.playbackId);
|
||||
@@ -1108,29 +1102,11 @@ void MessageDelegate::paintBody(QPainter* painter, const QStyleOptionViewItem& o
|
||||
bool isPaused = st->paused;
|
||||
|
||||
/* compute elapsed/frac for progress */
|
||||
float elapsed = 0, targetFrac = 0, durSec = st->durationSec;
|
||||
float durSec = st->durationSec;
|
||||
if (durSec <= 0) {
|
||||
QString ds = index.data(MsgVoiceDurationRole).toString();
|
||||
ds.remove('s'); durSec = ds.toFloat();
|
||||
}
|
||||
if (isPaused) {
|
||||
elapsed = st->pausedPosSec;
|
||||
} else if (isPlaying) {
|
||||
SoundManager* sm = SoundManager::instance();
|
||||
if (sm->isPcmPlaying(st->playbackId)) {
|
||||
int sr = sm->pcmSampleRate(st->playbackId);
|
||||
if (sr > 0) elapsed = (float)((double)sm->pcmCursor(st->playbackId) / sr);
|
||||
}
|
||||
if (elapsed <= 0) {
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
elapsed = (now - st->startMs) / 1000.0f;
|
||||
}
|
||||
if (elapsed > durSec) elapsed = durSec;
|
||||
}
|
||||
targetFrac = (durSec > 0) ? (elapsed / durSec) : 0;
|
||||
if (targetFrac < 0) targetFrac = 0; if (targetFrac > 1) targetFrac = 1;
|
||||
st->displayFrac += (targetFrac - st->displayFrac) * 0.15f;
|
||||
st->displayElapsed += (elapsed - st->displayElapsed) * 0.15f;
|
||||
float frac = st->displayFrac;
|
||||
|
||||
QRectF playR = L.voicePlayBtnRect;
|
||||
@@ -1714,14 +1690,14 @@ void MessageDelegate::drawAvatar(QPainter *p, const Layout &L,
|
||||
}
|
||||
|
||||
void MessageDelegate::startAutoPlay(const QString& filePath, float actualDurationSec, uint64_t playbackId) {
|
||||
if (actualDurationSec <= 0) return;
|
||||
if (actualDurationSec <= 0 || !playbackId) { GUI_WARN("voice delegate: invalid playback duration/id"); return; }
|
||||
ensureVoiceTimer();
|
||||
s_voiceStates.clear();
|
||||
VoicePlayState& st = s_voiceStates[filePath];
|
||||
st.playbackId = playbackId;
|
||||
st.playing = true;
|
||||
st.paused = false;
|
||||
st.durationSec = (actualDurationSec > 0) ? actualDurationSec : 1.0f;
|
||||
st.startMs = QDateTime::currentMSecsSinceEpoch();
|
||||
st.pausedPosSec = 0.0f;
|
||||
st.displayFrac = 0.0f;
|
||||
st.displayElapsed = 0.0f;
|
||||
|
||||
Reference in new issue
Block a user