Введение
QMediaPlayer — основной класс для воспроизведения медиафайлов и потоков в Qt: локальных видео/аудио файлов, сетевых потоков, ресурсов из Qt Resource System. В статье подробно разберём управление воспроизведением — старт, пауза, перемотка, контроль громкости, и реакцию на изменения состояния плеера через сигналы.
Концепция
QMediaPlayer асинхронен — установка источника через setSource() не означает немедленную готовность к воспроизведению, поскольку требуется время на открытие файла и его первичный разбор. Состояние плеера отражается через несколько связанных, но различных понятий: playbackState() (играет/на паузе/остановлен), mediaStatus() (загружается/буферизуется/готов/закончился) и error() для диагностики проблем. Управление позицией воспроизведения происходит через position()/setPosition() в миллисекундах, а громкость — через связанный объект QAudioOutput, а не сам плеер.
Пример кода: QMediaPlayer
#include <QMediaPlayer>
#include <QAudioOutput>
#include <QVideoWidget>
#include <QPushButton>
#include <QSlider>
#include <QVBoxLayout>
#include <QLabel>
#include <QWidget>
class VideoPlayerWidget : public QWidget
{
Q_OBJECT
public:
VideoPlayerWidget(QWidget *parent = nullptr) : QWidget(parent)
{
m_videoWidget = new QVideoWidget(this);
m_player = new QMediaPlayer(this);
m_audioOutput = new QAudioOutput(this);
m_player->setVideoOutput(m_videoWidget);
m_player->setAudioOutput(m_audioOutput);
m_positionSlider = new QSlider(Qt::Horizontal, this);
m_statusLabel = new QLabel("Готов к воспроизведению", this);
auto *playButton = new QPushButton("Воспроизвести/Пауза", this);
auto *volumeSlider = new QSlider(Qt::Horizontal, this);
volumeSlider->setRange(0, 100);
volumeSlider->setValue(80);
auto *layout = new QVBoxLayout(this);
layout->addWidget(m_videoWidget);
layout->addWidget(m_positionSlider);
layout->addWidget(playButton);
layout->addWidget(volumeSlider);
layout->addWidget(m_statusLabel);
connect(playButton, &QPushButton::clicked, this, &VideoPlayerWidget::togglePlayPause);
connect(volumeSlider, &QSlider::valueChanged, this, [this](int value) {
m_audioOutput->setVolume(value / 100.0); // громкость в диапазоне 0.0–1.0
});
// Обновление слайдера позиции при воспроизведении
connect(m_player, &QMediaPlayer::positionChanged, this, [this](qint64 position) {
if (!m_positionSlider->isSliderDown()) { // не сбиваем пользователя, если он тащит слайдер сам
m_positionSlider->setValue(static_cast<int>(position));
}
});
connect(m_player, &QMediaPlayer::durationChanged, this, [this](qint64 duration) {
m_positionSlider->setRange(0, static_cast<int>(duration));
});
connect(m_positionSlider, &QSlider::sliderMoved, this, [this](int value) {
m_player->setPosition(value); // перемотка по перетаскиванию слайдера
});
// Реакция на изменение статуса медиа — важно для информирования пользователя
connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &VideoPlayerWidget::onMediaStatusChanged);
connect(m_player, &QMediaPlayer::errorOccurred, this, &VideoPlayerWidget::onErrorOccurred);
}
void openFile(const QString &path)
{
m_player->setSource(QUrl::fromLocalFile(path));
}
private slots:
void togglePlayPause()
{
if (m_player->playbackState() == QMediaPlayer::PlayingState) {
m_player->pause();
} else {
m_player->play();
}
}
void onMediaStatusChanged(QMediaPlayer::MediaStatus status)
{
switch (status) {
case QMediaPlayer::LoadingMedia:
m_statusLabel->setText("Загрузка...");
break;
case QMediaPlayer::BufferingMedia:
m_statusLabel->setText("Буферизация...");
break;
case QMediaPlayer::LoadedMedia:
m_statusLabel->setText("Загружено, готово к воспроизведению");
break;
case QMediaPlayer::EndOfMedia:
m_statusLabel->setText("Воспроизведение завершено");
break;
default:
break;
}
}
void onErrorOccurred(QMediaPlayer::Error error, const QString &errorString)
{
Q_UNUSED(error);
m_statusLabel->setText("Ошибка: " + errorString);
}
private:
QMediaPlayer *m_player;
QAudioOutput *m_audioOutput;
QVideoWidget *m_videoWidget;
QSlider *m_positionSlider;
QLabel *m_statusLabel;
};
Пояснения к коду
togglePlayPause() показывает корректный способ переключения воспроизведения/паузы — проверка playbackState() перед вызовом play()/pause(), а не слепое чередование, что важно, если пауза/воспроизведение может быть инициировано не только этой кнопкой (например, после автоматической остановки при ошибке). Связка слайдера позиции с positionChanged/durationChanged сигналами и обратная связь через sliderMoved — стандартный паттерн UI медиаплеера, где важна проверка isSliderDown() для избежания «дрожания» слайдера, когда пользователь сам тащит его, пока плеер одновременно пытается обновить позицию.
onMediaStatusChanged() обрабатывает именно mediaStatus, а не playbackState — это разные, не взаимоисключающие понятия: плеер может быть в состоянии «Playing» по playbackState, но одновременно находиться в BufferingMedia по mediaStatus, если сетевой поток временно не успевает подгружать данные. Обработка errorOccurred обязательна для информирования пользователя о реальных проблемах — без неё неудачная попытка открыть несуществующий или повреждённый файл может остаться незамеченной.
Подводные камни
- Вызов
play()сразу послеsetSource()без ожидания готовности медиа. ХотяQMediaPlayerобычно сам справляется с очередью команд (воспроизведение начнётся, как только источник будет готов), для некоторых сетевых источников с заметной задержкой инициализации лучше дождатьсяLoadedMediaвmediaStatusChangedперед тем, как полагаться на немедленный старт воспроизведения, особенно если нужна точная синхронизация с другими событиями интерфейса. - Управление громкостью через
QAudioOutput, а не через самQMediaPlayer— в Qt6 громкость, в отличие от Qt5, перенесена с плеера на объект аудиовывода; код, скопированный из старых примеров для Qt5 (player->setVolume()), просто не скомпилируется в Qt6, и это частая причина путаницы при миграции старого кода. - Забытая проверка
isSliderDown()при обновлении позиции слайдера во время воспроизведения — без этой проверки слайдер позиции будет «выпрыгивать» из рук пользователя, который пытается перетащить его для перемотки, посколькуpositionChangedпродолжает поступать и перезаписывать значение слайдера каждую долю секунды во время воспроизведения. - Несогласованность единиц измерения позиции/длительности.
position()/duration()возвращают значения в миллисекундах (qint64) — при отображении пользователю их нужно преобразовывать в более понятный формат «минуты:секунды», и забытое преобразование (прямое отображение миллисекунд как «времени») создаёт нечитаемый для пользователя интерфейс.