Мультимедиа

VideoOutput и интеграция видео в QML-интерфейс

Введение

Виджетный 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) — без этого разделения слайдер может «дёргаться» между значением от плеера и значением, устанавливаемым пользователем во время перемотки.