#include "streamplayer_window.h" #include "videoplayer_window.h" #include "video_frame_image.h" #include "sound_manager.h" #include "../transport/stream_bridge.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include "../../../lib/debug_config.h" #include "../../../src/stream/stream.h" #include "../../../src/stream/stream_playback_clock.h" #include "../../../src/stream/stream_playback_audio.h" #ifdef HAVE_FFMPEG #include #include #endif } namespace { int64_t nowUs() { return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); } std::atomic nextId{0}; QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); } /* Обычный QSlider по клавишам/доступности: ползунок над треугольной шкалой, слева от него залит выбранный уровень. */ class StreamVolumeSlider : public QSlider { public: explicit StreamVolumeSlider(QWidget* parent) : QSlider(Qt::Horizontal, parent) { setRange(0, 100); setValue(100); setFixedSize(100, 26); setFocusPolicy(Qt::StrongFocus); } protected: void paintEvent(QPaintEvent*) override { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); const QRectF area(4, 12, width() - 8, height() - 14); const qreal position = area.left() + area.width() * (value() - minimum()) / std::max(1, maximum() - minimum()); QPolygonF triangle; triangle << area.bottomLeft() << area.topRight() << area.bottomRight(); QColor outline = palette().color(QPalette::WindowText); outline.setAlpha(100); QColor empty = outline; empty.setAlpha(35); painter.setPen(Qt::NoPen); painter.setBrush(empty); painter.drawPolygon(triangle); painter.save(); painter.setClipRect(QRectF(area.left(), area.top(), position - area.left(), area.height())); painter.setBrush(palette().highlight()); painter.drawPolygon(triangle); painter.restore(); painter.setPen(QPen(outline, 1)); painter.setBrush(Qt::NoBrush); painter.drawPolygon(triangle); painter.drawLine(QPointF(area.left(), 5), QPointF(area.right(), 5)); painter.setPen(Qt::NoPen); painter.setBrush(palette().highlight()); painter.drawRoundedRect(QRectF(position - 3, 0, 6, 10), 2, 2); if (hasFocus()) { QStyleOptionFocusRect focus; focus.initFrom(this); focus.rect = rect(); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focus, &painter, this); } } void mousePressEvent(QMouseEvent* event) override { if (event->button() != Qt::LeftButton) { QSlider::mousePressEvent(event); return; } setSliderDown(true); selectPosition(event); event->accept(); } void mouseMoveEvent(QMouseEvent* event) override { if (!isSliderDown()) { QSlider::mouseMoveEvent(event); return; } selectPosition(event); event->accept(); } void mouseReleaseEvent(QMouseEvent* event) override { if (event->button() != Qt::LeftButton || !isSliderDown()) { QSlider::mouseReleaseEvent(event); return; } selectPosition(event); setSliderDown(false); event->accept(); } private: void selectPosition(QMouseEvent* event) { setSliderPosition(QStyle::sliderValueFromPosition(minimum(), maximum(), event->pos().x() - 4, width() - 8)); } }; /* Ровно восемь Unicode code points и один компактный знак многоточия; HTML имён экранируется. */ QString shortNodeName(const char* name) { QString value = QString::fromUtf8(name); int end = 0, count = 0; while (end < value.size() && count < 8) { if (value.at(end).isHighSurrogate() && end + 1 < value.size() && value.at(end + 1).isLowSurrogate()) end++; end++; count++; } return (value.left(end) + (end < value.size() ? QStringLiteral("…") : QString())).toHtmlEscaped(); } QString bufferTime(int64_t us) { if (us < 0) return QStringLiteral("?"); QString value = QString::number(us / 1000000.0, 'f', 1); if (value.endsWith(QStringLiteral(".0"))) value.chop(2); return value + QStringLiteral("S"); } } struct StreamPlayerWindow::Playback { struct Video { QImage image; int64_t pts; }; struct AudioGap { uint64_t count = 0, silentFrames = 0, episodes = 0; int64_t at = 0; unsigned requested = 0, received = 0; unsigned pcmBefore = 0, pcmAfter = 0; int tempo = 1000; int64_t reserve = 0, feedAge = -1, workerAge = -1, audioPts = -1; int64_t deviceLead = 0, guard = 0, minimum = 0, callbackGap = 0, pendingGain = 0; size_t videoQueue = 0; } audioGap; uint64_t id = ++nextId, group = 0, source = 0, epoch = 0; int64_t createdAt = nowUs(), firstInputAt = 0; std::atomic running{true}, audioAvailable{false}; std::atomic complete{false}, failed{false}; std::atomic volume{1.0f}; float outputVolume = 0.0f; uint64_t audioGeneration = 0; std::thread worker, videoWorker; std::deque videoInput; // Audio worker -> video worker, под lock; максимум два входа. uint64_t videoGeneration = 1; bool videoBusy = false, videoInputEnded = false; std::mutex lock; std::mutex wakeLock; std::condition_variable wake; uint64_t wakeVersion = 0, dataSignals = 0; std::deque