Введение
Виджетный QVideoWidget из предыдущих статей подходит для Qt Widgets приложений, но для QML/Qt Quick интерфейсов используется аналогичный по назначению, но QML-native компонент VideoOutput. В статье разберём интеграцию видео непосредственно в QML-сцену, включая комбинирование видео с другими QML-элементами (оверлеи, элементы управления поверх изображения).
Концепция
VideoOutput — обычный визуальный QML-элемент (Item), который, в отличие от QVideoWidget, полноценно участвует в Qt Quick Scene Graph — это означает, что поверх видео можно свободно располагать другие QML-элементы (кнопки управления, субтитры, индикаторы), применять к нему трансформации и анимации, как к любому другому визуальному компоненту QML. Связывание с источником видео (MediaPlayer или CaptureSession в QML-варианте API) происходит через свойство videoSink, концептуально аналогичное setVideoOutput() в C++ API.
Пример кода: VideoOutput и интеграция
// VideoPlayerView.qml — видео с элементами управления, расположенными прямо над ним
import QtQuick
import QtQuick.Controls
import QtMultimedia
Item {
width: 800
height: 600
MediaPlayer {
id: mediaPlayer
source: "file:///path/to/video.mp4"
videoOutput: videoOutput
audioOutput: AudioOutput {
volume: volumeSlider.value
}
}
VideoOutput {
id: videoOutput
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit
}
// Элементы управления расположены ПОВЕРХ видео — это и есть преимущество
// VideoOutput как полноценного QML-элемента по сравнению с виджетным подходом
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 60
color: "#80000000" // полупрозрачный чёрный фон панели управления
Row {
anchors.centerIn: parent
spacing: 16
Button {
text: mediaPlayer.playbackState === MediaPlayer.PlayingState ? "⏸" : "▶"
onClicked: {
if (mediaPlayer.playbackState === MediaPlayer.PlayingState) {
mediaPlayer.pause();
} else {
mediaPlayer.play();
}
}
}
Slider {
id: volumeSlider
width: 150
from: 0; to: 1; value: 0.8
}
}
}
// Прогресс-бар поверх видео, в верхней части
Slider {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
from: 0
to: mediaPlayer.duration
value: mediaPlayer.position
onMoved: mediaPlayer.position = value
}
}
// Пример живого превью с камеры в QML через CaptureSession
import QtQuick
import QtMultimedia
Item {
CaptureSession {
id: captureSession
camera: Camera {
id: camera
active: true
}
videoOutput: cameraPreview
}
VideoOutput {
id: cameraPreview
anchors.fill: parent
}
}
Пояснения к коду
VideoPlayerView.qml показывает ключевое архитектурное преимущество QML-подхода — панель управления и слайдер прогресса просто декларируются как обычные QML-элементы, расположенные через anchors поверх VideoOutput, без необходимости в отдельном механизме «оверлея» над видео-областью, который потребовался бы при работе с виджетным QVideoWidget (где совмещение видео и других виджетов поверх него требует более явных, специфичных техник). Полупрозрачный фон панели управления (color: "#80000000") достигается просто заданием альфа-канала цвета — обычный приём QML, без специальной поддержки именно для видео.
Второй пример показывает аналогичную QML-версию архитектуры захвата с камеры из статьи 252 — CaptureSession с привязанным Camera и videoOutput, указывающим на конкретный VideoOutput-элемент для отображения живого превью — структура полностью параллельна C++ API (QMediaCaptureSession/QCamera/QVideoWidget), но выражена через QML-декларативный синтаксис.
Подводные камни
- Производительность рендеринга видео через
VideoOutputзависит от того, использует ли Qt Quick Scene Graph аппаратное ускорение на конкретной платформе. На embedded-устройствах со слабым GPU (основной цикл статей про embedded, статья 242) декодирование и отображение видео через QML может оказаться заметно требовательнее к ресурсам, чем нативный виджетный путь — для таких ограниченных платформ стоит явно тестировать производительность видео именно в QML-варианте. fillModeдляVideoOutput, не соответствующий реальному соотношению сторон видеофайла, может привести к искажённому изображению (растягиванию) или появлению чёрных полос — выбор междуPreserveAspectFit(сохранение пропорций с чёрными полосами при несовпадении) иStretch(заполнение всей области с возможным искажением) должен соответствовать ожиданиям дизайна конкретного приложения, а не оставаться значением по умолчанию без осознанного выбора.- Элементы управления, расположенные поверх видео без отдельного фона, могут стать нечитаемыми на ярких или контрастных участках видео — пример с полупрозрачным фоном панели управления (
#80000000) — типичное, но не единственное решение этой проблемы; для критичных элементов интерфейса (субтитры, важные предупреждения) может требоваться более надёжный контраст, чем простая полупрозрачность. - Несинхронизированное обновление позиции прогресс-бара при перемотке. Аналогично виджетному примеру из статьи 251, слайдер прогресса в QML должен корректно различать программное обновление позиции (от
mediaPlayer.position) и пользовательское перетаскивание (onMoved) — без этого разделения слайдер может «дёргаться» между значением от плеера и значением, устанавливаемым пользователем во время перемотки.