summaryrefslogtreecommitdiffstats
path: root/src/multimediaquick
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimediaquick')
-rw-r--r--src/multimediaquick/CMakeLists.txt44
-rw-r--r--src/multimediaquick/Video.qml371
-rw-r--r--src/multimediaquick/multimedia_plugin.cpp42
-rw-r--r--src/multimediaquick/qquickimagecapture.cpp189
-rw-r--r--src/multimediaquick/qquickimagecapture_p.h57
-rw-r--r--src/multimediaquick/qquickimagepreviewprovider.cpp59
-rw-r--r--src/multimediaquick/qquickimagepreviewprovider_p.h35
-rw-r--r--src/multimediaquick/qquickmediaplayer.cpp95
-rw-r--r--src/multimediaquick/qquickmediaplayer_p.h75
-rw-r--r--src/multimediaquick/qquickplaylist.cpp594
-rw-r--r--src/multimediaquick/qquickplaylist_p.h171
-rw-r--r--src/multimediaquick/qquickscreencapture.cpp27
-rw-r--r--src/multimediaquick/qquickscreencapture_p.h44
-rw-r--r--src/multimediaquick/qquicksoundeffect.cpp29
-rw-r--r--src/multimediaquick/qquicksoundeffect_p.h47
-rw-r--r--src/multimediaquick/qquickvideooutput.cpp566
-rw-r--r--src/multimediaquick/qquickvideooutput_p.h128
-rw-r--r--src/multimediaquick/qsgvideonode_p.cpp378
-rw-r--r--src/multimediaquick/qsgvideonode_p.h65
-rw-r--r--src/multimediaquick/qsgvideotexture.cpp77
-rw-r--r--src/multimediaquick/qsgvideotexture_p.h47
-rw-r--r--src/multimediaquick/qtmultimediaquickglobal_p.h28
-rw-r--r--src/multimediaquick/qtmultimediaquicktypes.cpp12
-rw-r--r--src/multimediaquick/qtmultimediaquicktypes_p.h201
24 files changed, 3381 insertions, 0 deletions
diff --git a/src/multimediaquick/CMakeLists.txt b/src/multimediaquick/CMakeLists.txt
new file mode 100644
index 000000000..1376f9274
--- /dev/null
+++ b/src/multimediaquick/CMakeLists.txt
@@ -0,0 +1,44 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## MultimediaQuickPrivate Module:
+#####################################################################
+
+set(qml_files
+ "Video.qml"
+)
+
+qt_internal_add_qml_module(MultimediaQuickPrivate
+ URI "QtMultimedia"
+ VERSION "${PROJECT_VERSION}"
+ PAST_MAJOR_VERSIONS 5
+ CLASS_NAME QMultimediaQuickModule
+ PLUGIN_TARGET quickmultimedia
+ NO_GENERATE_PLUGIN_SOURCE
+ NO_PLUGIN_OPTIONAL
+ DEPENDENCIES QtQuick
+ CONFIG_MODULE_NAME multimediaquick
+ INTERNAL_MODULE
+ SOURCES
+ qquickimagecapture.cpp qquickimagecapture_p.h
+ qquickimagepreviewprovider.cpp qquickimagepreviewprovider_p.h
+# qquickplaylist.cpp qquickplaylist_p.h
+ qquickmediaplayer.cpp qquickmediaplayer_p.h
+ qquickscreencapture.cpp qquickscreencapture_p.h
+ qquicksoundeffect.cpp qquicksoundeffect_p.h
+ qquickvideooutput.cpp qquickvideooutput_p.h
+ qsgvideonode_p.cpp qsgvideonode_p.h
+ qsgvideotexture.cpp qsgvideotexture_p.h
+ qtmultimediaquickglobal_p.h
+ qtmultimediaquicktypes.cpp qtmultimediaquicktypes_p.h
+ QML_FILES
+ ${qml_files}
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::MultimediaPrivate
+ Qt::Quick
+ Qt::QuickPrivate
+)
+
+target_sources(quickmultimedia PRIVATE multimedia_plugin.cpp)
diff --git a/src/multimediaquick/Video.qml b/src/multimediaquick/Video.qml
new file mode 100644
index 000000000..80d28c805
--- /dev/null
+++ b/src/multimediaquick/Video.qml
@@ -0,0 +1,371 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtMultimedia
+
+/*!
+ \qmltype Video
+ \inherits Item
+ \ingroup multimedia_qml
+ \ingroup multimedia_video_qml
+ \inqmlmodule QtMultimedia
+ \brief A convenience type for showing a specified video.
+
+ \c Video is a convenience type combining the functionality
+ of a \l MediaPlayer and a \l VideoOutput into one. It provides
+ simple video playback functionality without having to declare multiple
+ types.
+
+ The following is sample code to implement video playback in a scene.
+
+ \qml
+ Video {
+ id: video
+ width : 800
+ height : 600
+ source: "video.avi"
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ video.play()
+ }
+ }
+
+ focus: true
+ Keys.onSpacePressed: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()
+ Keys.onLeftPressed: video.position = video.position - 5000
+ Keys.onRightPressed: video.position = video.position + 5000
+ }
+ \endqml
+
+ The source file, \c video.avi, plays when you click the parent
+ of MouseArea. The video plays in an area of 800 by 600 pixels, and its \c id
+ property has the value \b{video}.
+
+ Notice that because signals for the \l Keys have been defined pressing the:
+ \list
+ \li \uicontrol Spacebar toggles the pause button.
+ \li \uicontrol{Left Arrow} moves the current position in the video to 5 seconds
+ previously.
+ \li \uicontrol{Right Arrow} advances the current position in the video by 5 seconds.
+ \endlist
+
+ Video supports un-transformed, stretched, and uniformly scaled
+ video presentation. For a description of stretched uniformly scaled
+ presentation, see the \l fillMode property description.
+
+ \sa MediaPlayer, VideoOutput
+
+\omit
+ \section1 Screen Saver
+
+ If it is likely that an application will be playing video for an extended
+ period of time without user interaction, it may be necessary to disable
+ the platform's screen saver. The \l ScreenSaver (from \l QtSystemInfo)
+ may be used to disable the screensaver in this fashion:
+
+ \qml
+ import QtSystemInfo 5.0
+
+ ScreenSaver { screenSaverEnabled: false }
+ \endqml
+\endomit
+*/
+
+// TODO: Restore Qt System Info docs when the module is released
+
+Item {
+ id: video
+ implicitWidth: videoOut.implicitWidth
+ implicitHeight: videoOut.implicitHeight
+
+ /*** Properties of VideoOutput ***/
+ /*!
+ \qmlproperty enumeration Video::fillMode
+
+ Set this property to define how the video is scaled to fit the target
+ area.
+
+ \list
+ \li VideoOutput.Stretch - the video is scaled to fit
+ \li VideoOutput.PreserveAspectFit - the video is scaled uniformly to fit without
+ cropping
+ \li VideoOutput.PreserveAspectCrop - the video is scaled uniformly to fill, cropping
+ if necessary
+ \endlist
+
+ Because this type is for convenience in QML, it does not
+ support enumerations directly, so enumerations from \c VideoOutput are
+ used to access the available fill modes.
+
+ The default fill mode is preserveAspectFit.
+ */
+ property alias fillMode: videoOut.fillMode
+
+ /*!
+ \qmlproperty int Video::orientation
+
+ The orientation of the \c Video in degrees. Only multiples of 90
+ degrees is supported, that is 0, 90, 180, 270, 360, etc.
+ */
+ property alias orientation: videoOut.orientation
+
+
+ /*** Properties of MediaPlayer ***/
+
+ /*!
+ \qmlproperty enumeration Video::playbackState
+
+ This read only property indicates the playback state of the media.
+
+ \list
+ \li MediaPlayer.PlayingState - the media is playing
+ \li MediaPlayer.PausedState - the media is paused
+ \li MediaPlayer.StoppedState - the media is stopped
+ \endlist
+
+ The default state is MediaPlayer.StoppedState.
+ */
+ property alias playbackState: player.playbackState
+
+ /*!
+ \qmlproperty real Video::bufferProgress
+
+ This property holds how much of the data buffer is currently filled,
+ from 0.0 (empty) to 1.0
+ (full).
+ */
+ property alias bufferProgress: player.bufferProgress
+
+ /*!
+ \qmlproperty int Video::duration
+
+ This property holds the duration of the media in milliseconds.
+
+ If the media doesn't have a fixed duration (a live stream for example)
+ this will be 0.
+ */
+ property alias duration: player.duration
+
+ /*!
+ \qmlproperty int Video::loops
+
+ Determines how often the media is played before stopping.
+ Set to MediaPlayer.Infinite to loop the current media file forever.
+
+ The default value is \c 1. Setting this property to \c 0 has no effect.
+ */
+ property alias loops: player.loops
+
+ /*!
+ \qmlproperty enumeration Video::error
+
+ This property holds the error state of the video. It can be one of:
+
+ \list
+ \li MediaPlayer.NoError - there is no current error.
+ \li MediaPlayer.ResourceError - the video cannot be played due to a problem
+ allocating resources.
+ \li MediaPlayer.FormatError - the video format is not supported.
+ \li MediaPlayer.NetworkError - the video cannot be played due to network issues.
+ \li MediaPlayer.AccessDenied - the video cannot be played due to insufficient
+ permissions.
+ \li MediaPlayer.ServiceMissing - the video cannot be played because the media
+ service could not be
+ instantiated.
+ \endlist
+ */
+ property alias error: player.error
+
+ /*!
+ \qmlproperty string Video::errorString
+
+ This property holds a string describing the current error condition in more detail.
+ */
+ property alias errorString: player.errorString
+
+ /*!
+ \qmlproperty bool Video::hasAudio
+
+ This property holds whether the current media has audio content.
+ */
+ property alias hasAudio: player.hasAudio
+
+ /*!
+ \qmlproperty bool Video::hasVideo
+
+ This property holds whether the current media has video content.
+ */
+ property alias hasVideo: player.hasVideo
+
+ /*!
+ \qmlproperty mediaMetaData Video::metaData
+
+ This property holds the meta data for the current media.
+
+ See \l{MediaPlayer::metaData}{MediaPlayer.metaData} for details about each meta data key.
+
+ \sa {mediaMetaData}
+ */
+ property alias metaData: player.metaData
+
+ /*!
+ \qmlproperty bool Video::muted
+
+ This property holds whether the audio output is muted.
+ */
+ property alias muted: audioOutput.muted
+
+ /*!
+ \qmlproperty real Video::playbackRate
+
+ This property holds the rate at which video is played at as a multiple
+ of the normal rate.
+ */
+ property alias playbackRate: player.playbackRate
+
+ /*!
+ \qmlproperty int Video::position
+
+ This property holds the current playback position in milliseconds.
+ */
+ property alias position: player.position
+
+ /*!
+ \qmlproperty bool Video::seekable
+
+ This property holds whether the playback position of the video can be
+ changed.
+
+ If true, calling the \l seek() method or changing the \l position property
+ will cause playback to seek to the new position.
+ */
+ property alias seekable: player.seekable
+
+ /*!
+ \qmlproperty url Video::source
+
+ This property holds the source URL of the media.
+ */
+ property alias source: player.source
+
+ /*!
+ \since 6.7
+ \qmlproperty bool Video::autoPlay
+
+ This property controls whether the media begins to play automatically after it gets loaded.
+ Defaults to \c false.
+ */
+ property alias autoPlay: player.autoPlay
+
+ /*!
+ \qmlproperty real Video::volume
+
+ This property holds the audio volume.
+
+ The volume is scaled linearly from \c 0.0 (silence) to \c 1.0
+ (full volume). Values outside this range will be clamped.
+
+ The default volume is \c 1.0.
+
+ UI volume controls should usually be scaled nonlinearly. For example,
+ using a logarithmic scale will produce linear changes in perceived
+ loudness, which is what a user would normally expect from a volume
+ control. See \l {QtAudio::convertVolume()} for more details.
+ */
+ property alias volume: audioOutput.volume
+
+ /*!
+ \qmlsignal Video::paused()
+
+ This signal is emitted when playback is paused.
+ */
+ signal paused
+
+ /*!
+ \qmlsignal Video::stopped()
+
+ This signal is emitted when playback is stopped.
+ */
+ signal stopped
+
+ /*!
+ \qmlsignal Video::playing()
+
+ This signal is emitted when playback is started or continued.
+ */
+ signal playing
+
+ /*!
+ \qmlsignal Video::errorOccurred(error, errorString)
+
+ This signal is emitted when an \a error has occurred. The \a errorString
+ parameter may contain more detailed information about the error.
+ */
+ signal errorOccurred(int error, string errorString)
+
+ VideoOutput {
+ id: videoOut
+ anchors.fill: video
+ }
+
+ MediaPlayer {
+ id: player
+ onPlaybackStateChanged: function(newState) {
+ if (newState === MediaPlayer.PausedState)
+ video.paused();
+ else if (newState === MediaPlayer.StoppedState)
+ video.stopped();
+ else
+ video.playing();
+ }
+ onErrorOccurred: function(error, errorString) {
+ video.errorOccurred(error, errorString);
+ }
+ videoOutput: videoOut
+ audioOutput: AudioOutput {
+ id: audioOutput
+ }
+ }
+
+ /*!
+ \qmlmethod Video::play()
+
+ Starts playback of the media.
+ */
+ function play() {
+ player.play();
+ }
+
+ /*!
+ \qmlmethod Video::pause()
+
+ Pauses playback of the media.
+ */
+ function pause() {
+ player.pause();
+ }
+
+ /*!
+ \qmlmethod Video::stop()
+
+ Stops playback of the media.
+ */
+ function stop() {
+ player.stop();
+ }
+
+ /*!
+ \qmlmethod Video::seek(offset)
+
+ If the \l seekable property is true, seeks the current
+ playback position to \a offset.
+
+ \sa seekable, position
+ */
+ function seek(offset) {
+ player.position = offset;
+ }
+}
diff --git a/src/multimediaquick/multimedia_plugin.cpp b/src/multimediaquick/multimedia_plugin.cpp
new file mode 100644
index 000000000..9509b4b5f
--- /dev/null
+++ b/src/multimediaquick/multimedia_plugin.cpp
@@ -0,0 +1,42 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtQml/qqmlextensionplugin.h>
+#include <QtQml/qqml.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include "qsoundeffect.h"
+#include "qmediaplayer.h"
+#include "qmediametadata.h"
+#include "qcamera.h"
+#include "qmediacapturesession.h"
+#include "qmediarecorder.h"
+
+#include <private/qquickimagepreviewprovider_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QMultimediaQuickModule : public QQmlEngineExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
+
+public:
+ QMultimediaQuickModule(QObject *parent = nullptr)
+ : QQmlEngineExtensionPlugin(parent)
+ {
+ volatile auto registration = qml_register_types_QtMultimedia;
+ Q_UNUSED(registration);
+ }
+
+ void initializeEngine(QQmlEngine *engine, const char *uri) override
+ {
+ Q_UNUSED(uri);
+ engine->addImageProvider("camera", new QQuickImagePreviewProvider);
+ }
+};
+
+QT_END_NAMESPACE
+
+#include "multimedia_plugin.moc"
+
diff --git a/src/multimediaquick/qquickimagecapture.cpp b/src/multimediaquick/qquickimagecapture.cpp
new file mode 100644
index 000000000..b7e56d18d
--- /dev/null
+++ b/src/multimediaquick/qquickimagecapture.cpp
@@ -0,0 +1,189 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickimagecapture_p.h"
+#include "qquickimagepreviewprovider_p.h"
+
+#include <QtCore/qurl.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype ImageCapture
+ \instantiates QQuickImageCapture
+ \brief An interface for capturing camera images.
+ \ingroup multimedia_qml
+ \inqmlmodule QtMultimedia
+ \ingroup camera_qml
+
+ This type allows you to capture still images and be notified when they
+ are available or saved to disk.
+
+ \qml
+ Item {
+ width: 640
+ height: 360
+
+ CaptureSession {
+ imageCapture : ImageCapture {
+ id: imageCapture
+ }
+ camera: Camera {
+ id: camera
+ }
+
+ videoOutput: videoOutput
+ }
+ VideoOutput {
+ id: videoOutput
+ anchors.fill: parent
+
+ MouseArea {
+ anchors.fill: parent;
+ onClicked: imageCapture.capture();
+ }
+ }
+
+ Image {
+ id: photoPreview
+ source: imageCapture.preview // always shows the last captured image
+ }
+ }
+ \endqml
+
+*/
+
+QQuickImageCapture::QQuickImageCapture(QObject *parent)
+ : QImageCapture(parent)
+{
+ connect(this, &QImageCapture::imageCaptured, this, &QQuickImageCapture::_q_imageCaptured);
+}
+
+QQuickImageCapture::~QQuickImageCapture() = default;
+
+/*!
+ \qmlproperty bool QtMultimedia::ImageCapture::readyForCapture
+
+ This property holds a bool value indicating whether the camera
+ is ready to capture photos or not.
+
+ Calling capture() or captureToFile() while \e ready is \c false is not permitted and
+ results in an error.
+*/
+
+/*!
+ \qmlproperty string QtMultimedia::ImageCapture::preview
+
+ This property holds a url to the latest captured image. It can be connected to the
+ source property of an \l Image element to show the last captured image.
+
+ \qml
+ CaptureSession {
+ camera: Camera {}
+ imageCapture: ImageCapture {
+ id: capture
+ }
+ }
+ Image {
+ source: capture.preview
+ }
+ \endqml
+
+ \sa saveToFile
+*/
+
+/*!
+ \qmlmethod QtMultimedia::ImageCapture::capture()
+
+ Start image capture. The \l imageCaptured and \l imageSaved signals will
+ be emitted when the capture is complete.
+
+ The captured image will be available through the preview property that can be
+ used as the source for a QML Image item. The saveToFile() method can then be used
+ save the image.
+
+ Camera saves all the capture parameters like exposure settings or
+ image processing parameters, so changes to camera parameters after
+ capture() is called do not affect previous capture requests.
+
+ capture() returns the capture requestId parameter, used with
+ imageExposed(), imageCaptured(), imageMetadataAvailable() and imageSaved() signals.
+
+ \sa readyForCapture, preview
+*/
+
+/*!
+ \qmlmethod QtMultimedia::ImageCapture::captureToFile(location)
+
+ Does the same as capture() but additionally automatically saves the captured image to the specified
+ \a location.
+
+ \sa capture
+*/
+
+QString QQuickImageCapture::preview() const
+{
+ return m_capturedImagePath;
+}
+
+/*!
+ \qmlmethod QtMultimedia::ImageCapture::saveToFile(location)
+
+ Saves the last captured image to \a location.
+
+ \sa capture, preview
+*/
+void QQuickImageCapture::saveToFile(const QUrl &location) const
+{
+ m_lastImage.save(location.toLocalFile());
+}
+
+void QQuickImageCapture::_q_imageCaptured(int id, const QImage &preview)
+{
+ QString previewId = QStringLiteral("preview_%1").arg(id);
+ QQuickImagePreviewProvider::registerPreview(previewId, preview);
+ m_capturedImagePath = QStringLiteral("image://camera/%2").arg(previewId);
+ m_lastImage = preview;
+ emit previewChanged();
+}
+
+/*!
+ \qmlsignal QtMultimedia::ImageCapture::errorOccurred(id, error, errorString)
+
+ This signal is emitted when an error occurs during capture with requested \a id.
+ \a error is an enumeration of type ImageCapture::Error.
+ A descriptive message is available in \a errorString.
+*/
+
+/*!
+ \qmlsignal QtMultimedia::ImageCapture::imageCaptured(requestId, preview)
+
+ This signal is emitted when an image with requested id \a requestId has been captured
+ but not yet saved to the filesystem. The \a preview
+ parameter is the captured image.
+
+ \sa imageSaved, preview
+*/
+
+/*!
+ \qmlsignal QtMultimedia::ImageCapture::imageSaved(id, fileName)
+
+ This signal is emitted after the image with requested \a id has been written to the filesystem.
+ The \a fileName is a local file path, not a URL.
+
+ \sa imageCaptured
+*/
+
+
+/*!
+ \qmlsignal QtMultimedia::ImageCapture::imageMetadataAvailable(id, metaData)
+
+ This signal is emitted when the image with requested \a id has new \a metaData.
+
+ \sa imageCaptured
+*/
+
+
+QT_END_NAMESPACE
+
+#include "moc_qquickimagecapture_p.cpp"
diff --git a/src/multimediaquick/qquickimagecapture_p.h b/src/multimediaquick/qquickimagecapture_p.h
new file mode 100644
index 000000000..01f21af75
--- /dev/null
+++ b/src/multimediaquick/qquickimagecapture_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKIMAGECAPTURE_H
+#define QQUICKIMAGECAPTURE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qcamera.h>
+#include <qimagecapture.h>
+#include <qmediametadata.h>
+
+#include <QtQml/qqml.h>
+#include <QtCore/private/qglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QUrl;
+
+class QQuickImageCapture : public QImageCapture
+{
+ Q_OBJECT
+ Q_PROPERTY(QString preview READ preview NOTIFY previewChanged)
+ QML_NAMED_ELEMENT(ImageCapture)
+
+public:
+ QQuickImageCapture(QObject *parent = nullptr);
+ ~QQuickImageCapture();
+
+ QString preview() const;
+
+public Q_SLOTS:
+ void saveToFile(const QUrl &location) const;
+
+Q_SIGNALS:
+ void previewChanged();
+
+private Q_SLOTS:
+ void _q_imageCaptured(int, const QImage&);
+
+private:
+ QImage m_lastImage;
+ QString m_capturedImagePath;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimediaquick/qquickimagepreviewprovider.cpp b/src/multimediaquick/qquickimagepreviewprovider.cpp
new file mode 100644
index 000000000..3b40c1c88
--- /dev/null
+++ b/src/multimediaquick/qquickimagepreviewprovider.cpp
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickimagepreviewprovider_p.h"
+#include <QtCore/qmutex.h>
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QQuickImagePreviewProviderPrivate
+{
+ QString id;
+ QImage image;
+ QMutex mutex;
+};
+
+Q_GLOBAL_STATIC(QQuickImagePreviewProviderPrivate, priv)
+
+QQuickImagePreviewProvider::QQuickImagePreviewProvider()
+: QQuickImageProvider(QQuickImageProvider::Image)
+{
+}
+
+QQuickImagePreviewProvider::~QQuickImagePreviewProvider()
+{
+ QQuickImagePreviewProviderPrivate *d = priv();
+ QMutexLocker lock(&d->mutex);
+ d->id.clear();
+ d->image = QImage();
+}
+
+QImage QQuickImagePreviewProvider::requestImage(const QString &id, QSize *size, const QSize& requestedSize)
+{
+ QQuickImagePreviewProviderPrivate *d = priv();
+ QMutexLocker lock(&d->mutex);
+
+ if (d->id != id)
+ return QImage();
+
+ QImage res = d->image;
+ if (!requestedSize.isEmpty())
+ res = res.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+
+ if (size)
+ *size = res.size();
+
+ return res;
+}
+
+void QQuickImagePreviewProvider::registerPreview(const QString &id, const QImage &preview)
+{
+ //only the last preview is kept
+ QQuickImagePreviewProviderPrivate *d = priv();
+ QMutexLocker lock(&d->mutex);
+ d->id = id;
+ d->image = preview;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimediaquick/qquickimagepreviewprovider_p.h b/src/multimediaquick/qquickimagepreviewprovider_p.h
new file mode 100644
index 000000000..3ffd55eff
--- /dev/null
+++ b/src/multimediaquick/qquickimagepreviewprovider_p.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKIMAGEPREVIEWPROVIDER_H
+#define QQUICKIMAGEPREVIEWPROVIDER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaquickglobal_p.h>
+#include <QtQuick/qquickimageprovider.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIAQUICK_EXPORT QQuickImagePreviewProvider : public QQuickImageProvider
+{
+public:
+ QQuickImagePreviewProvider();
+ ~QQuickImagePreviewProvider();
+
+ QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize) override;
+ static void registerPreview(const QString &id, const QImage &preview);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimediaquick/qquickmediaplayer.cpp b/src/multimediaquick/qquickmediaplayer.cpp
new file mode 100644
index 000000000..6b49c827e
--- /dev/null
+++ b/src/multimediaquick/qquickmediaplayer.cpp
@@ -0,0 +1,95 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickmediaplayer_p.h"
+#include <QtQml/qqmlcontext.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickMediaPlayer::QQuickMediaPlayer(QObject *parent) : QMediaPlayer(parent)
+{
+ connect(this, &QMediaPlayer::positionChanged, this, &QQuickMediaPlayer::onPositionChanged);
+ connect(this, &QMediaPlayer::durationChanged, this, &QQuickMediaPlayer::onDurationChanged);
+ connect(this, &QMediaPlayer::mediaStatusChanged, this,
+ &QQuickMediaPlayer::onMediaStatusChanged);
+}
+
+void QQuickMediaPlayer::qmlSetSource(const QUrl &source)
+{
+ if (m_source == source)
+ return;
+ m_source = source;
+ m_wasMediaLoaded = false;
+ const QQmlContext *context = qmlContext(this);
+ setSource(context ? context->resolvedUrl(source) : source);
+ emit qmlSourceChanged(source);
+}
+
+QUrl QQuickMediaPlayer::qmlSource() const
+{
+ return m_source;
+}
+
+void QQuickMediaPlayer::setQmlPosition(int position)
+{
+ setPosition(static_cast<qint64>(position));
+}
+
+int QQuickMediaPlayer::qmlPosition() const
+{
+ return static_cast<int>(position());
+}
+
+int QQuickMediaPlayer::qmlDuration() const
+{
+ return static_cast<int>(duration());
+}
+
+void QQuickMediaPlayer::onPositionChanged(qint64 position)
+{
+ emit qmlPositionChanged(static_cast<int>(position));
+}
+
+void QQuickMediaPlayer::onDurationChanged(qint64 duration)
+{
+ emit qmlDurationChanged(static_cast<int>(duration));
+}
+
+void QQuickMediaPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus status)
+{
+ if (status != QMediaPlayer::LoadedMedia || std::exchange(m_wasMediaLoaded, true))
+ return;
+
+ // run with QueuedConnection to make the user able to handle the media status change
+ // by themselves, otherwise play() might change the status in the handler.
+ auto tryAutoPlay = [this]() {
+ if (m_autoPlay && mediaStatus() == QMediaPlayer::LoadedMedia)
+ play();
+ };
+
+ if (m_autoPlay)
+ QMetaObject::invokeMethod(this, tryAutoPlay, Qt::QueuedConnection);
+}
+
+/*!
+ \since 6.7
+ \qmlproperty bool QtMultimedia::MediaPlayer::autoPlay
+
+ This property controls whether the media begins to play automatically after it gets loaded.
+ Defaults to \c false.
+*/
+
+bool QQuickMediaPlayer::autoPlay() const
+{
+ return m_autoPlay;
+}
+
+void QQuickMediaPlayer::setAutoPlay(bool autoPlay)
+{
+ if (std::exchange(m_autoPlay, autoPlay) != autoPlay)
+ emit autoPlayChanged(autoPlay);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquickmediaplayer_p.cpp"
diff --git a/src/multimediaquick/qquickmediaplayer_p.h b/src/multimediaquick/qquickmediaplayer_p.h
new file mode 100644
index 000000000..be2ad6b12
--- /dev/null
+++ b/src/multimediaquick/qquickmediaplayer_p.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKMEDIAPLAYER_H
+#define QQUICKMEDIAPLAYER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QMediaPlayer>
+#include <QtQml/qqml.h>
+#include <qtmultimediaquickexports.h>
+#include <qurl.h>
+#include <private/qglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIAQUICK_EXPORT QQuickMediaPlayer : public QMediaPlayer
+{
+ Q_OBJECT
+ Q_PROPERTY(QUrl source READ qmlSource WRITE qmlSetSource NOTIFY qmlSourceChanged FINAL)
+
+ // qml doesn't support qint64, so we have to convert to the supported type.
+ // Int is expected to be enough for actual purposes.
+ Q_PROPERTY(int duration READ qmlDuration NOTIFY qmlDurationChanged FINAL)
+ Q_PROPERTY(int position READ qmlPosition WRITE setQmlPosition NOTIFY qmlPositionChanged FINAL)
+ Q_PROPERTY(bool autoPlay READ autoPlay WRITE setAutoPlay NOTIFY autoPlayChanged FINAL)
+
+ QML_NAMED_ELEMENT(MediaPlayer)
+
+public:
+ QQuickMediaPlayer(QObject *parent = nullptr);
+
+ void qmlSetSource(const QUrl &source);
+
+ QUrl qmlSource() const;
+
+ void setQmlPosition(int position);
+
+ int qmlPosition() const;
+
+ int qmlDuration() const;
+
+ bool autoPlay() const;
+
+ void setAutoPlay(bool autoPlay);
+
+private:
+ void onPositionChanged(qint64 position);
+ void onDurationChanged(qint64 position);
+ void onMediaStatusChanged(QMediaPlayer::MediaStatus status);
+
+Q_SIGNALS:
+ void qmlSourceChanged(const QUrl &source);
+ void qmlPositionChanged(int position);
+ void qmlDurationChanged(int duration);
+ void autoPlayChanged(bool autoPlay);
+
+private:
+ QUrl m_source;
+ bool m_autoPlay = false;
+ bool m_wasMediaLoaded = false;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimediaquick/qquickplaylist.cpp b/src/multimediaquick/qquickplaylist.cpp
new file mode 100644
index 000000000..986191647
--- /dev/null
+++ b/src/multimediaquick/qquickplaylist.cpp
@@ -0,0 +1,594 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickplaylist_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype PlaylistItem
+ \instantiates QQuickPlaylistItem
+ \since 5.6
+
+ \inqmlmodule QtMultimedia
+ \ingroup multimedia_qml
+ \ingroup multimedia_audio_qml
+ \ingroup multimedia_video_qml
+ \brief Defines an item in a Playlist.
+ \internal
+
+ \sa Playlist
+*/
+
+/*!
+ \qmlproperty url QtMultimedia::PlaylistItem::source
+
+ This property holds the source URL of the item.
+
+ \sa Playlist
+*/
+QQuickPlaylistItem::QQuickPlaylistItem(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QUrl QQuickPlaylistItem::source() const
+{
+ return m_source;
+}
+
+void QQuickPlaylistItem::setSource(const QUrl &source)
+{
+ m_source = source;
+}
+
+/*!
+ \qmltype Playlist
+ \instantiates QQuickPlaylist
+ \since 5.6
+ \brief For specifying a list of media to be played.
+ \internal
+
+ \inqmlmodule QtMultimedia
+ \ingroup multimedia_qml
+ \ingroup multimedia_audio_qml
+ \ingroup multimedia_video_qml
+
+ The Playlist type provides a way to play a list of media with the MediaPlayer, Audio and Video
+ types. It can be used as a data source for view elements (such as ListView) and other elements
+ that interact with model data (such as Repeater). When used as a data model, each playlist
+ item's source URL can be accessed using the \c source role.
+
+ \qml
+ Item {
+ width: 400;
+ height: 300;
+
+ Audio {
+ id: player;
+ playlist: Playlist {
+ id: playlist
+ PlaylistItem { source: "song1.ogg"; }
+ PlaylistItem { source: "song2.ogg"; }
+ PlaylistItem { source: "song3.ogg"; }
+ }
+ }
+
+ ListView {
+ model: playlist;
+ delegate: Text {
+ font.pixelSize: 16;
+ text: source;
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent;
+ onPressed: {
+ if (player.playbackState != Audio.PlayingState) {
+ player.play();
+ } else {
+ player.pause();
+ }
+ }
+ }
+ }
+ \endqml
+
+ \sa MediaPlayer, Audio, Video
+*/
+
+void QQuickPlaylist::_q_mediaAboutToBeInserted(int start, int end)
+{
+ emit itemAboutToBeInserted(start, end);
+
+ beginInsertRows(QModelIndex(), start, end);
+}
+
+void QQuickPlaylist::_q_mediaInserted(int start, int end)
+{
+ endInsertRows();
+
+ emit itemCountChanged();
+ emit itemInserted(start, end);
+}
+
+void QQuickPlaylist::_q_mediaAboutToBeRemoved(int start, int end)
+{
+ emit itemAboutToBeRemoved(start, end);
+
+ beginRemoveRows(QModelIndex(), start, end);
+}
+
+void QQuickPlaylist::_q_mediaRemoved(int start, int end)
+{
+ endRemoveRows();
+
+ emit itemCountChanged();
+ emit itemRemoved(start, end);
+}
+
+void QQuickPlaylist::_q_mediaChanged(int start, int end)
+{
+ emit dataChanged(createIndex(start, 0), createIndex(end, 0));
+ emit itemChanged(start, end);
+}
+
+void QQuickPlaylist::_q_loadFailed()
+{
+ m_error = m_playlist->error();
+ m_errorString = m_playlist->errorString();
+
+ emit error(Error(m_error), m_errorString);
+ emit errorChanged();
+ emit loadFailed();
+}
+
+QQuickPlaylist::QQuickPlaylist(QObject *parent)
+ : QAbstractListModel(parent)
+ , m_playlist(nullptr)
+ , m_error(QMediaPlaylist::NoError)
+{
+}
+
+QQuickPlaylist::~QQuickPlaylist()
+{
+ delete m_playlist;
+}
+
+/*!
+ \qmlproperty enumeration QtMultimedia::Playlist::playbackMode
+
+ This property holds the order in which items in the playlist are played.
+
+ \table
+ \header \li Value \li Description
+ \row \li CurrentItemOnce
+ \li The current item is played only once.
+ \row \li CurrentItemInLoop
+ \li The current item is played repeatedly in a loop.
+ \row \li Sequential
+ \li Playback starts from the current and moves through each successive item until the last
+ is reached and then stops. The next item is a null item when the last one is currently
+ playing.
+ \row \li Loop
+ \li Playback restarts at the first item after the last has finished playing.
+ \row \li Random
+ \li Play items in random order.
+ \endtable
+ */
+QQuickPlaylist::PlaybackMode QQuickPlaylist::playbackMode() const
+{
+ return PlaybackMode(m_playlist->playbackMode());
+}
+
+void QQuickPlaylist::setPlaybackMode(PlaybackMode mode)
+{
+ if (playbackMode() == mode)
+ return;
+
+ m_playlist->setPlaybackMode(QMediaPlaylist::PlaybackMode(mode));
+}
+
+/*!
+ \qmlproperty url QtMultimedia::Playlist::currentItemsource
+
+ This property holds the source URL of the current item in the playlist.
+ */
+QUrl QQuickPlaylist::currentItemSource() const
+{
+ return m_playlist->currentMedia();
+}
+
+/*!
+ \qmlproperty int QtMultimedia::Playlist::currentIndex
+
+ This property holds the position of the current item in the playlist.
+ */
+int QQuickPlaylist::currentIndex() const
+{
+ return m_playlist->currentIndex();
+}
+
+void QQuickPlaylist::setCurrentIndex(int index)
+{
+ if (currentIndex() == index)
+ return;
+
+ m_playlist->setCurrentIndex(index);
+}
+
+/*!
+ \qmlproperty int QtMultimedia::Playlist::itemCount
+
+ This property holds the number of items in the playlist.
+ */
+int QQuickPlaylist::itemCount() const
+{
+ return m_playlist->mediaCount();
+}
+
+/*!
+ \qmlproperty enumeration QtMultimedia::Playlist::error
+
+ This property holds the error condition of the playlist.
+
+ \table
+ \header \li Value \li Description
+ \row \li NoError
+ \li No errors
+ \row \li FormatError
+ \li Format error.
+ \row \li FormatNotSupportedError
+ \li Format not supported.
+ \row \li NetworkError
+ \li Network error.
+ \row \li AccessDeniedError
+ \li Access denied error.
+ \endtable
+ */
+QQuickPlaylist::Error QQuickPlaylist::error() const
+{
+ return Error(m_error);
+}
+
+/*!
+ \qmlproperty string QtMultimedia::Playlist::errorString
+
+ This property holds a string describing the current error condition of the playlist.
+*/
+QString QQuickPlaylist::errorString() const
+{
+ return m_errorString;
+}
+
+/*!
+ \qmlmethod url QtMultimedia::Playlist::itemSource(index)
+
+ Returns the source URL of the item at the given \a index in the playlist.
+*/
+QUrl QQuickPlaylist::itemSource(int index)
+{
+ return m_playlist->media(index);
+}
+
+/*!
+ \qmlmethod int QtMultimedia::Playlist::nextIndex(steps)
+
+ Returns the index of the item in the playlist which would be current after calling next()
+ \a steps times.
+
+ Returned value depends on the size of the playlist, the current position and the playback mode.
+
+ \sa playbackMode, previousIndex()
+*/
+int QQuickPlaylist::nextIndex(int steps)
+{
+ return m_playlist->nextIndex(steps);
+}
+
+/*!
+ \qmlmethod int QtMultimedia::Playlist::previousIndex(steps)
+
+ Returns the index of the item in the playlist which would be current after calling previous()
+ \a steps times.
+
+ Returned value depends on the size of the playlist, the current position and the playback mode.
+
+ \sa playbackMode, nextIndex()
+*/
+int QQuickPlaylist::previousIndex(int steps)
+{
+ return m_playlist->previousIndex(steps);
+}
+
+/*!
+ \qmlmethod QtMultimedia::Playlist::next()
+
+ Advances to the next item in the playlist.
+*/
+void QQuickPlaylist::next()
+{
+ m_playlist->next();
+}
+
+/*!
+ \qmlmethod QtMultimedia::Playlist::previous()
+
+ Returns to the previous item in the playlist.
+*/
+void QQuickPlaylist::previous()
+{
+ m_playlist->previous();
+}
+
+/*!
+ \qmlmethod QtMultimedia::Playlist::shuffle()
+
+ Shuffles items in the playlist.
+*/
+void QQuickPlaylist::shuffle()
+{
+ m_playlist->shuffle();
+}
+
+/*!
+ \qmlmethod QtMultimedia::Playlist::load(location, format)
+
+ Loads a playlist from the given \a location. If \a format is specified, it is used, otherwise
+ the format is guessed from the location name and the data.
+
+ New items are appended to the playlist.
+
+ \c onloaded() is emitted if the playlist loads successfully, otherwise \c onLoadFailed() is
+ emitted with \l error and \l errorString defined accordingly.
+*/
+void QQuickPlaylist::load(const QUrl &location, const QString &format)
+{
+ m_error = QMediaPlaylist::NoError;
+ m_errorString = QString();
+ emit errorChanged();
+ m_playlist->load(location, format.toLatin1().constData());
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::save(location, format)
+
+ Saves the playlist to the given \a location. If \a format is specified, it is used, otherwise
+ the format is guessed from the location name.
+
+ Returns true if the playlist is saved successfully.
+*/
+bool QQuickPlaylist::save(const QUrl &location, const QString &format)
+{
+ return m_playlist->save(location, format.toLatin1().constData());
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::addItem(source)
+
+ Appends the \a source URL to the playlist.
+
+ Returns true if the \a source is added successfully.
+*/
+void QQuickPlaylist::addItem(const QUrl &source)
+{
+ m_playlist->addMedia(QUrl(source));
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::addItems(sources)
+
+ Appends the list of URLs in \a sources to the playlist.
+
+ Returns true if the \a sources are added successfully.
+
+ \since 5.7
+*/
+void QQuickPlaylist::addItems(const QList<QUrl> &sources)
+{
+ if (sources.isEmpty())
+ return;
+
+ QList<QUrl> contents;
+ QList<QUrl>::const_iterator it = sources.constBegin();
+ while (it != sources.constEnd()) {
+ contents.push_back(QUrl(*it));
+ ++it;
+ }
+ m_playlist->addMedia(contents);
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::insertItem(index, source)
+
+ Inserts the \a source URL to the playlist at the given \a index.
+
+ Returns true if the \a source is added successfully.
+*/
+bool QQuickPlaylist::insertItem(int index, const QUrl &source)
+{
+ return m_playlist->insertMedia(index, QUrl(source));
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::insertItems(index, sources)
+
+ Inserts the list of URLs in \a sources to the playlist at the given \a index.
+
+ Returns true if the \a sources are added successfully.
+
+ \since 5.7
+*/
+bool QQuickPlaylist::insertItems(int index, const QList<QUrl> &sources)
+{
+ if (sources.empty())
+ return false;
+
+ QList<QUrl> contents;
+ QList<QUrl>::const_iterator it = sources.constBegin();
+ while (it != sources.constEnd()) {
+ contents.push_back(QUrl(*it));
+ ++it;
+ }
+ return m_playlist->insertMedia(index, contents);
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::moveItem(from, to)
+
+ Moves the item at index position \a from to index position \a to.
+
+ Returns \c true if the item is moved successfully.
+
+ \since 5.7
+*/
+bool QQuickPlaylist::moveItem(int from, int to)
+{
+ return m_playlist->moveMedia(from, to);
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::removeItem(index)
+
+ Removes the item at the given \a index from the playlist.
+
+ Returns \c true if the item is removed successfully.
+*/
+bool QQuickPlaylist::removeItem(int index)
+{
+ return m_playlist->removeMedia(index);
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::removeItems(int start, int end)
+
+ Removes items in the playlist from \a start to \a end inclusive.
+
+ Returns \c true if the items are removed successfully.
+
+ \since 5.7
+*/
+bool QQuickPlaylist::removeItems(int start, int end)
+{
+ return m_playlist->removeMedia(start, end);
+}
+
+/*!
+ \qmlmethod bool QtMultimedia::Playlist::clear()
+
+ Removes all the items from the playlist.
+
+ Returns \c true if the operation is successful.
+*/
+void QQuickPlaylist::clear()
+{
+ m_playlist->clear();
+}
+
+int QQuickPlaylist::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return m_playlist->mediaCount();
+}
+
+QVariant QQuickPlaylist::data(const QModelIndex &index, int role) const
+{
+ Q_UNUSED(role);
+
+ if (!index.isValid())
+ return QVariant();
+
+ return m_playlist->media(index.row());
+}
+
+QHash<int, QByteArray> QQuickPlaylist::roleNames() const
+{
+ QHash<int, QByteArray> roleNames;
+ roleNames[SourceRole] = "source";
+ return roleNames;
+}
+
+void QQuickPlaylist::classBegin()
+{
+ m_playlist = new QMediaPlaylist(this);
+
+ connect(m_playlist, SIGNAL(currentIndexChanged(int)),
+ this, SIGNAL(currentIndexChanged()));
+ connect(m_playlist, SIGNAL(playbackModeChanged(QMediaPlaylist::PlaybackMode)),
+ this, SIGNAL(playbackModeChanged()));
+ connect(m_playlist, SIGNAL(currentMediaChanged(QUrl)),
+ this, SIGNAL(currentItemSourceChanged()));
+ connect(m_playlist, SIGNAL(mediaAboutToBeInserted(int,int)),
+ this, SLOT(_q_mediaAboutToBeInserted(int,int)));
+ connect(m_playlist, SIGNAL(mediaInserted(int,int)),
+ this, SLOT(_q_mediaInserted(int,int)));
+ connect(m_playlist, SIGNAL(mediaAboutToBeRemoved(int,int)),
+ this, SLOT(_q_mediaAboutToBeRemoved(int,int)));
+ connect(m_playlist, SIGNAL(mediaRemoved(int,int)),
+ this, SLOT(_q_mediaRemoved(int,int)));
+ connect(m_playlist, SIGNAL(mediaChanged(int,int)),
+ this, SLOT(_q_mediaChanged(int,int)));
+ connect(m_playlist, SIGNAL(loaded()),
+ this, SIGNAL(loaded()));
+ connect(m_playlist, SIGNAL(loadFailed()),
+ this, SLOT(_q_loadFailed()));
+}
+
+void QQuickPlaylist::componentComplete()
+{
+}
+
+/*!
+ \qmlsignal QtMultimedia::Playlist::itemAboutToBeInserted(start, end)
+
+ This signal is emitted when items are to be inserted into the playlist at \a start and ending at
+ \a end.
+*/
+
+/*!
+ \qmlsignal QtMultimedia::Playlist::itemInserted(start, end)
+
+ This signal is emitted after items have been inserted into the playlist. The new items are those
+ between \a start and \a end inclusive.
+*/
+
+/*!
+ \qmlsignal QtMultimedia::Playlist::itemAboutToBeRemoved(start, end)
+
+ This signal emitted when items are to be deleted from the playlist at \a start and ending at
+ \a end.
+*/
+
+/*!
+ \qmlsignal QtMultimedia::Playlist::itemRemoved(start, end)
+
+ This signal is emitted after items have been removed from the playlist. The removed items are
+ those between \a start and \a end inclusive.
+*/
+
+/*!
+ \qmlsignal QtMultimedia::Playlist::itemChanged(start, end)
+
+ This signal is emitted after items have been changed in the playlist between \a start and
+ \a end positions inclusive.
+*/
+
+/*!
+ \qmlsignal QtMultimedia::Playlist::loaded()
+
+ This signal is emitted when the playlist loading succeeded.
+*/
+
+/*!
+ \qmlsignal QtMultimedia::Playlist::loadFailed()
+
+ This signal is emitted when the playlist loading failed. \l error and \l errorString can be
+ checked for more information on the failure.
+*/
+
+QT_END_NAMESPACE
+
+#include "moc_qquickplaylist_p.cpp"
diff --git a/src/multimediaquick/qquickplaylist_p.h b/src/multimediaquick/qquickplaylist_p.h
new file mode 100644
index 000000000..4003cefe7
--- /dev/null
+++ b/src/multimediaquick/qquickplaylist_p.h
@@ -0,0 +1,171 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKPLAYLIST_P_H
+#define QQUICKPLAYLIST_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QAbstractListModel>
+#include <QtQml/qqmlparserstatus.h>
+#include <QtQml/qqml.h>
+
+#include <qmediaplaylist.h>
+#include <private/qglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickPlaylistItem : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QUrl source READ source WRITE setSource)
+ QML_NAMED_ELEMENT(PlaylistItem)
+
+public:
+ QQuickPlaylistItem(QObject *parent = 0);
+
+ QUrl source() const;
+ void setSource(const QUrl &source);
+
+private:
+ QUrl m_source;
+};
+
+class QQuickPlaylist : public QAbstractListModel, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_PROPERTY(PlaybackMode playbackMode READ playbackMode WRITE setPlaybackMode NOTIFY playbackModeChanged)
+ Q_PROPERTY(QUrl currentItemSource READ currentItemSource NOTIFY currentItemSourceChanged)
+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+ Q_PROPERTY(int itemCount READ itemCount NOTIFY itemCountChanged)
+ Q_PROPERTY(Error error READ error NOTIFY errorChanged)
+ Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged)
+ Q_PROPERTY(QQmlListProperty<QQuickPlaylistItem> items READ items DESIGNABLE false)
+ Q_INTERFACES(QQmlParserStatus)
+ Q_CLASSINFO("DefaultProperty", "items")
+ QML_NAMED_ELEMENT(Playlist)
+
+public:
+ enum PlaybackMode
+ {
+ CurrentItemOnce = QMediaPlaylist::CurrentItemOnce,
+ CurrentItemInLoop = QMediaPlaylist::CurrentItemInLoop,
+ Sequential = QMediaPlaylist::Sequential,
+ Loop = QMediaPlaylist::Loop,
+// Random = QMediaPlaylist::Random
+ };
+ Q_ENUM(PlaybackMode)
+
+ enum Error
+ {
+ NoError = QMediaPlaylist::NoError,
+ FormatError = QMediaPlaylist::FormatError,
+ FormatNotSupportedError = QMediaPlaylist::FormatNotSupportedError,
+ NetworkError = QMediaPlaylist::NetworkError,
+ AccessDeniedError = QMediaPlaylist::AccessDeniedError
+ };
+ Q_ENUM(Error)
+
+ enum Roles
+ {
+ SourceRole = Qt::UserRole + 1
+ };
+
+ QQuickPlaylist(QObject *parent = 0);
+ ~QQuickPlaylist();
+
+ PlaybackMode playbackMode() const;
+ void setPlaybackMode(PlaybackMode playbackMode);
+ QUrl currentItemSource() const;
+ int currentIndex() const;
+ void setCurrentIndex(int currentIndex);
+ int itemCount() const;
+ Error error() const;
+ QString errorString() const;
+ QMediaPlaylist *mediaPlaylist() const { return m_playlist; }
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ void classBegin() override;
+ void componentComplete() override;
+
+ QQmlListProperty<QQuickPlaylistItem> items() {
+ return QQmlListProperty<QQuickPlaylistItem>(
+ this, 0, item_append, item_count, 0, item_clear);
+ }
+ static void item_append(QQmlListProperty<QQuickPlaylistItem> *list,
+ QQuickPlaylistItem* item) {
+ static_cast<QQuickPlaylist*>(list->object)->addItem(item->source());
+ }
+ static qsizetype item_count(QQmlListProperty<QQuickPlaylistItem> *list) {
+ return static_cast<QQuickPlaylist*>(list->object)->itemCount();
+ }
+ static void item_clear(QQmlListProperty<QQuickPlaylistItem> *list) {
+ static_cast<QQuickPlaylist*>(list->object)->clear();
+ }
+
+public Q_SLOTS:
+ QUrl itemSource(int index);
+ int nextIndex(int steps = 1);
+ int previousIndex(int steps = 1);
+ void next();
+ void previous();
+ void shuffle();
+ void load(const QUrl &location, const QString &format = QString());
+ bool save(const QUrl &location, const QString &format = QString());
+ void addItem(const QUrl &source);
+ Q_REVISION(1) void addItems(const QList<QUrl> &sources);
+ bool insertItem(int index, const QUrl &source);
+ Q_REVISION(1) bool insertItems(int index, const QList<QUrl> &sources);
+ Q_REVISION(1) bool moveItem(int from, int to);
+ bool removeItem(int index);
+ Q_REVISION(1) bool removeItems(int start, int end);
+ void clear();
+
+Q_SIGNALS:
+ void playbackModeChanged();
+ void currentItemSourceChanged();
+ void currentIndexChanged();
+ void itemCountChanged();
+ void errorChanged();
+
+ void itemAboutToBeInserted(int start, int end);
+ void itemInserted(int start, int end);
+ void itemAboutToBeRemoved(int start, int end);
+ void itemRemoved(int start, int end);
+ void itemChanged(int start, int end);
+ void loaded();
+ void loadFailed();
+
+ void error(QQuickPlaylist::Error error, const QString &errorString);
+
+private Q_SLOTS:
+ void _q_mediaAboutToBeInserted(int start, int end);
+ void _q_mediaInserted(int start, int end);
+ void _q_mediaAboutToBeRemoved(int start, int end);
+ void _q_mediaRemoved(int start, int end);
+ void _q_mediaChanged(int start, int end);
+ void _q_loadFailed();
+
+private:
+ Q_DISABLE_COPY(QQuickPlaylist)
+
+ QMediaPlaylist *m_playlist;
+ QString m_errorString;
+ QMediaPlaylist::Error m_error;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimediaquick/qquickscreencapture.cpp b/src/multimediaquick/qquickscreencapture.cpp
new file mode 100644
index 000000000..9c2b0ef6f
--- /dev/null
+++ b/src/multimediaquick/qquickscreencapture.cpp
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickscreencapture_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQuickScreenCatpure::QQuickScreenCatpure(QObject *parent) : QScreenCapture(parent)
+{
+ connect(this, &QScreenCapture::screenChanged, this, [this](QScreen *screen) {
+ emit QQuickScreenCatpure::screenChanged(new QQuickScreenInfo(this, screen));
+ });
+}
+
+void QQuickScreenCatpure::qmlSetScreen(const QQuickScreenInfo *info)
+{
+ setScreen(info ? info->wrappedScreen() : nullptr);
+}
+
+QQuickScreenInfo *QQuickScreenCatpure::qmlScreen()
+{
+ return new QQuickScreenInfo(this, screen());
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquickscreencapture_p.cpp"
diff --git a/src/multimediaquick/qquickscreencapture_p.h b/src/multimediaquick/qquickscreencapture_p.h
new file mode 100644
index 000000000..2c46493f5
--- /dev/null
+++ b/src/multimediaquick/qquickscreencapture_p.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKSCREENCAPTURE_H
+#define QQUICKSCREENCAPTURE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QScreenCapture>
+#include <qtmultimediaquickexports.h>
+#include <private/qglobal_p.h>
+#include <private/qquickscreen_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIAQUICK_EXPORT QQuickScreenCatpure : public QScreenCapture
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickScreenInfo *screen READ qmlScreen WRITE qmlSetScreen NOTIFY screenChanged)
+ QML_NAMED_ELEMENT(ScreenCapture)
+
+public:
+ QQuickScreenCatpure(QObject *parent = nullptr);
+
+ void qmlSetScreen(const QQuickScreenInfo *info);
+
+ QQuickScreenInfo *qmlScreen();
+
+Q_SIGNALS:
+ void screenChanged(QQuickScreenInfo *);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimediaquick/qquicksoundeffect.cpp b/src/multimediaquick/qquicksoundeffect.cpp
new file mode 100644
index 000000000..89530f07f
--- /dev/null
+++ b/src/multimediaquick/qquicksoundeffect.cpp
@@ -0,0 +1,29 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquicksoundeffect_p.h"
+#include <QtQml/qqmlcontext.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickSoundEffect::QQuickSoundEffect(QObject *parent) : QSoundEffect(parent) { }
+
+void QQuickSoundEffect::qmlSetSource(const QUrl &source)
+{
+ if (m_source == source)
+ return;
+
+ m_source = source;
+ const QQmlContext *context = qmlContext(this);
+ setSource(context ? context->resolvedUrl(source) : source);
+ emit sourceChanged(source);
+}
+
+QUrl QQuickSoundEffect::qmlSource() const
+{
+ return m_source;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquicksoundeffect_p.cpp"
diff --git a/src/multimediaquick/qquicksoundeffect_p.h b/src/multimediaquick/qquicksoundeffect_p.h
new file mode 100644
index 000000000..8a7246420
--- /dev/null
+++ b/src/multimediaquick/qquicksoundeffect_p.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKSOUNDEFFECT_H
+#define QQUICKSOUNDEFFECT_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QSoundEffect>
+#include <QtQml/qqml.h>
+#include <qtmultimediaquickexports.h>
+#include <qurl.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIAQUICK_EXPORT QQuickSoundEffect : public QSoundEffect
+{
+ Q_OBJECT
+ Q_PROPERTY(QUrl source READ qmlSource WRITE qmlSetSource NOTIFY sourceChanged)
+ QML_NAMED_ELEMENT(SoundEffect)
+
+public:
+ QQuickSoundEffect(QObject *parent = nullptr);
+
+ void qmlSetSource(const QUrl &source);
+
+ QUrl qmlSource() const;
+
+Q_SIGNALS:
+ void sourceChanged(const QUrl &source);
+
+private:
+ QUrl m_source;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimediaquick/qquickvideooutput.cpp b/src/multimediaquick/qquickvideooutput.cpp
new file mode 100644
index 000000000..50b344846
--- /dev/null
+++ b/src/multimediaquick/qquickvideooutput.cpp
@@ -0,0 +1,566 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qquickvideooutput_p.h"
+
+#include <private/qvideooutputorientationhandler_p.h>
+#include <QtMultimedia/qmediaplayer.h>
+#include <QtMultimedia/qmediacapturesession.h>
+#include <private/qfactoryloader_p.h>
+#include <QtCore/qloggingcategory.h>
+#include <qvideosink.h>
+#include <QtQuick/QQuickWindow>
+#include <private/qquickwindow_p.h>
+#include <private/qmultimediautils_p.h>
+#include <qsgvideonode_p.h>
+#include <QtCore/qrunnable.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_STATIC_LOGGING_CATEGORY(qLcVideo, "qt.multimedia.video")
+
+namespace {
+
+inline bool qIsDefaultAspect(int o)
+{
+ return (o % 180) == 0;
+}
+
+/*
+ * Return the orientation normalized to 0-359
+ */
+inline int qNormalizedOrientation(int o)
+{
+ // Negative orientations give negative results
+ int o2 = o % 360;
+ if (o2 < 0)
+ o2 += 360;
+ return o2;
+}
+
+}
+
+/*!
+ \qmltype VideoOutput
+ //! \instantiates QQuickVideoOutput
+ \brief Render video or camera viewfinder.
+
+ \ingroup multimedia_qml
+ \ingroup multimedia_video_qml
+ \inqmlmodule QtMultimedia
+
+ \qml
+
+ Rectangle {
+ width: 800
+ height: 600
+ color: "black"
+
+ MediaPlayer {
+ id: player
+ source: "file://video.webm"
+ videoOutput: videoOutput
+ }
+
+ VideoOutput {
+ id: videoOutput
+ anchors.fill: parent
+ }
+ }
+
+ \endqml
+
+ The VideoOutput item supports untransformed, stretched, and uniformly scaled video presentation.
+ For a description of stretched uniformly scaled presentation, see the \l fillMode property
+ description.
+
+ \sa MediaPlayer, Camera
+
+\omit
+ \section1 Screen Saver
+
+ If it is likely that an application will be playing video for an extended
+ period of time without user interaction it may be necessary to disable
+ the platform's screen saver. The \l ScreenSaver (from \l QtSystemInfo)
+ may be used to disable the screensaver in this fashion:
+
+ \qml
+ import QtSystemInfo
+
+ ScreenSaver { screenSaverEnabled: false }
+ \endqml
+\endomit
+*/
+
+// TODO: Restore Qt System Info docs when the module is released
+
+/*!
+ \internal
+ \class QQuickVideoOutput
+ \brief The QQuickVideoOutput class provides a video output item.
+*/
+
+QQuickVideoOutput::QQuickVideoOutput(QQuickItem *parent) :
+ QQuickItem(parent)
+{
+ setFlag(ItemHasContents, true);
+
+ m_sink = new QVideoSink(this);
+ qRegisterMetaType<QVideoFrameFormat>();
+ connect(m_sink, &QVideoSink::videoFrameChanged, this,
+ [this](const QVideoFrame &frame) {
+ setFrame(frame);
+ QMetaObject::invokeMethod(this, &QQuickVideoOutput::_q_newFrame, frame.size());
+ },
+ Qt::DirectConnection);
+
+ initRhiForSink();
+}
+
+QQuickVideoOutput::~QQuickVideoOutput()
+{
+}
+
+/*!
+ \qmlproperty object QtMultimedia::VideoOutput::videoSink
+
+ This property holds the underlaying C++ QVideoSink object that is used
+ to render the video frames to this VideoOutput element.
+
+ Normal usage of VideoOutput from QML should not require using this property.
+*/
+
+QVideoSink *QQuickVideoOutput::videoSink() const
+{
+ return m_sink;
+}
+
+/*!
+ \qmlproperty enumeration QtMultimedia::VideoOutput::fillMode
+
+ Set this property to define how the video is scaled to fit the target area.
+
+ \list
+ \li Stretch - the video is scaled to fit.
+ \li PreserveAspectFit - the video is scaled uniformly to fit without cropping
+ \li PreserveAspectCrop - the video is scaled uniformly to fill, cropping if necessary
+ \endlist
+
+ The default fill mode is PreserveAspectFit.
+*/
+
+QQuickVideoOutput::FillMode QQuickVideoOutput::fillMode() const
+{
+ return FillMode(m_aspectRatioMode);
+}
+
+void QQuickVideoOutput::setFillMode(FillMode mode)
+{
+ if (mode == fillMode())
+ return;
+
+ m_aspectRatioMode = Qt::AspectRatioMode(mode);
+
+ m_geometryDirty = true;
+ update();
+
+ emit fillModeChanged(mode);
+}
+
+void QQuickVideoOutput::_q_newFrame(QSize size)
+{
+ update();
+
+ size = qRotatedFrameSize(size, m_orientation + m_frameOrientation);
+
+ if (m_nativeSize != size) {
+ m_nativeSize = size;
+
+ m_geometryDirty = true;
+
+ setImplicitWidth(size.width());
+ setImplicitHeight(size.height());
+
+ emit sourceRectChanged();
+ }
+}
+
+/* Based on fill mode and our size, figure out the source/dest rects */
+void QQuickVideoOutput::_q_updateGeometry()
+{
+ const QRectF rect(0, 0, width(), height());
+ const QRectF absoluteRect(x(), y(), width(), height());
+
+ if (!m_geometryDirty && m_lastRect == absoluteRect)
+ return;
+
+ QRectF oldContentRect(m_contentRect);
+
+ m_geometryDirty = false;
+ m_lastRect = absoluteRect;
+
+ const auto fill = m_aspectRatioMode;
+ if (m_nativeSize.isEmpty()) {
+ //this is necessary for item to receive the
+ //first paint event and configure video surface.
+ m_contentRect = rect;
+ } else if (fill == Qt::IgnoreAspectRatio) {
+ m_contentRect = rect;
+ } else {
+ QSizeF scaled = m_nativeSize;
+ scaled.scale(rect.size(), fill);
+
+ m_contentRect = QRectF(QPointF(), scaled);
+ m_contentRect.moveCenter(rect.center());
+ }
+
+ updateGeometry();
+
+ if (m_contentRect != oldContentRect)
+ emit contentRectChanged();
+}
+
+/*!
+ \qmlproperty int QtMultimedia::VideoOutput::orientation
+
+ In some cases the source video stream requires a certain
+ orientation to be correct. This includes
+ sources like a camera viewfinder, where the displayed
+ viewfinder should match reality, no matter what rotation
+ the rest of the user interface has.
+
+ This property allows you to apply a rotation (in steps
+ of 90 degrees) to compensate for any user interface
+ rotation, with positive values in the anti-clockwise direction.
+
+ The orientation change will also affect the mapping
+ of coordinates from source to viewport.
+*/
+int QQuickVideoOutput::orientation() const
+{
+ return m_orientation;
+}
+
+void QQuickVideoOutput::setOrientation(int orientation)
+{
+ // Make sure it's a multiple of 90.
+ if (orientation % 90)
+ return;
+
+ // If there's no actual change, return
+ if (m_orientation == orientation)
+ return;
+
+ // If the new orientation is the same effect
+ // as the old one, don't update the video node stuff
+ if ((m_orientation % 360) == (orientation % 360)) {
+ m_orientation = orientation;
+ emit orientationChanged();
+ return;
+ }
+
+ m_geometryDirty = true;
+
+ // Otherwise, a new orientation
+ // See if we need to change aspect ratio orientation too
+ bool oldAspect = qIsDefaultAspect(m_orientation);
+ bool newAspect = qIsDefaultAspect(orientation);
+
+ m_orientation = orientation;
+
+ if (oldAspect != newAspect) {
+ m_nativeSize.transpose();
+
+ setImplicitWidth(m_nativeSize.width());
+ setImplicitHeight(m_nativeSize.height());
+
+ // Source rectangle does not change for orientation
+ }
+
+ update();
+ emit orientationChanged();
+}
+
+/*!
+ \qmlproperty rectangle QtMultimedia::VideoOutput::contentRect
+
+ This property holds the item coordinates of the area that
+ would contain video to render. With certain fill modes,
+ this rectangle will be larger than the visible area of the
+ \c VideoOutput.
+
+ This property is useful when other coordinates are specified
+ in terms of the source dimensions - this applied for relative
+ (normalized) frame coordinates in the range of 0 to 1.0.
+
+ Areas outside this will be transparent.
+*/
+QRectF QQuickVideoOutput::contentRect() const
+{
+ return m_contentRect;
+}
+
+/*!
+ \qmlproperty rectangle QtMultimedia::VideoOutput::sourceRect
+
+ This property holds the area of the source video
+ content that is considered for rendering. The
+ values are in source pixel coordinates, adjusted for
+ the source's pixel aspect ratio.
+
+ Note that typically the top left corner of this rectangle
+ will be \c {0,0} while the width and height will be the
+ width and height of the input content. Only when the video
+ source has a viewport set, these values will differ.
+
+ The orientation setting does not affect this rectangle.
+
+ \sa QVideoFrameFormat::viewport()
+*/
+QRectF QQuickVideoOutput::sourceRect() const
+{
+ // We might have to transpose back
+ QSizeF size = m_nativeSize;
+ if (!size.isValid())
+ return {};
+
+ if (!qIsDefaultAspect(m_orientation + m_frameOrientation))
+ size.transpose();
+
+
+ // Take the viewport into account for the top left position.
+ // m_nativeSize is already adjusted to the viewport, as it originates
+ // from QVideoFrameFormat::viewport(), which includes pixel aspect ratio
+ const QRectF viewport = adjustedViewport();
+ Q_ASSERT(viewport.size() == size);
+ return QRectF(viewport.topLeft(), size);
+}
+
+void QQuickVideoOutput::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ Q_UNUSED(newGeometry);
+ Q_UNUSED(oldGeometry);
+
+ QQuickItem::geometryChange(newGeometry, oldGeometry);
+
+ // Explicitly listen to geometry changes here. This is needed since changing the position does
+ // not trigger a call to updatePaintNode().
+ // We need to react to position changes though, as the window backened's display rect gets
+ // changed in that situation.
+ _q_updateGeometry();
+}
+
+void QQuickVideoOutput::_q_invalidateSceneGraph()
+{
+ invalidateSceneGraph();
+}
+
+void QQuickVideoOutput::_q_sceneGraphInitialized()
+{
+ initRhiForSink();
+}
+
+void QQuickVideoOutput::releaseResources()
+{
+ // Called on the gui thread when the window is closed or changed.
+ invalidateSceneGraph();
+}
+
+void QQuickVideoOutput::invalidateSceneGraph()
+{
+ // Called on the render thread, e.g. when the context is lost.
+ // QMutexLocker lock(&m_frameMutex);
+ initRhiForSink();
+}
+
+void QQuickVideoOutput::initRhiForSink()
+{
+ QRhi *rhi = m_window ? QQuickWindowPrivate::get(m_window)->rhi : nullptr;
+ m_sink->setRhi(rhi);
+}
+
+void QQuickVideoOutput::itemChange(QQuickItem::ItemChange change,
+ const QQuickItem::ItemChangeData &changeData)
+{
+ if (change != QQuickItem::ItemSceneChange)
+ return;
+
+ if (changeData.window == m_window)
+ return;
+ if (m_window)
+ disconnect(m_window);
+ m_window = changeData.window;
+
+ if (m_window) {
+ // We want to receive the signals in the render thread
+ connect(m_window, &QQuickWindow::sceneGraphInitialized, this,
+ &QQuickVideoOutput::_q_sceneGraphInitialized, Qt::DirectConnection);
+ connect(m_window, &QQuickWindow::sceneGraphInvalidated, this,
+ &QQuickVideoOutput::_q_invalidateSceneGraph, Qt::DirectConnection);
+ }
+ initRhiForSink();
+}
+
+QSize QQuickVideoOutput::nativeSize() const
+{
+ return m_videoFormat.viewport().size();
+}
+
+void QQuickVideoOutput::updateGeometry()
+{
+ const QRectF viewport = m_videoFormat.viewport();
+ const QSizeF frameSize = m_videoFormat.frameSize();
+ const QRectF normalizedViewport(viewport.x() / frameSize.width(),
+ viewport.y() / frameSize.height(),
+ viewport.width() / frameSize.width(),
+ viewport.height() / frameSize.height());
+ const QRectF rect(0, 0, width(), height());
+ if (nativeSize().isEmpty()) {
+ m_renderedRect = rect;
+ m_sourceTextureRect = normalizedViewport;
+ } else if (m_aspectRatioMode == Qt::IgnoreAspectRatio) {
+ m_renderedRect = rect;
+ m_sourceTextureRect = normalizedViewport;
+ } else if (m_aspectRatioMode == Qt::KeepAspectRatio) {
+ m_sourceTextureRect = normalizedViewport;
+ m_renderedRect = contentRect();
+ } else if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) {
+ m_renderedRect = rect;
+ const qreal contentHeight = contentRect().height();
+ const qreal contentWidth = contentRect().width();
+
+ // Calculate the size of the source rectangle without taking the viewport into account
+ const qreal relativeOffsetLeft = -contentRect().left() / contentWidth;
+ const qreal relativeOffsetTop = -contentRect().top() / contentHeight;
+ const qreal relativeWidth = rect.width() / contentWidth;
+ const qreal relativeHeight = rect.height() / contentHeight;
+
+ // Now take the viewport size into account
+ const qreal totalOffsetLeft = normalizedViewport.x() + relativeOffsetLeft * normalizedViewport.width();
+ const qreal totalOffsetTop = normalizedViewport.y() + relativeOffsetTop * normalizedViewport.height();
+ const qreal totalWidth = normalizedViewport.width() * relativeWidth;
+ const qreal totalHeight = normalizedViewport.height() * relativeHeight;
+
+ if (qIsDefaultAspect(orientation() + m_frameOrientation)) {
+ m_sourceTextureRect = QRectF(totalOffsetLeft, totalOffsetTop,
+ totalWidth, totalHeight);
+ } else {
+ m_sourceTextureRect = QRectF(totalOffsetTop, totalOffsetLeft,
+ totalHeight, totalWidth);
+ }
+ }
+
+ if (m_videoFormat.scanLineDirection() == QVideoFrameFormat::BottomToTop) {
+ qreal top = m_sourceTextureRect.top();
+ m_sourceTextureRect.setTop(m_sourceTextureRect.bottom());
+ m_sourceTextureRect.setBottom(top);
+ }
+
+ if (m_videoFormat.isMirrored()) {
+ qreal left = m_sourceTextureRect.left();
+ m_sourceTextureRect.setLeft(m_sourceTextureRect.right());
+ m_sourceTextureRect.setRight(left);
+ }
+}
+
+QSGNode *QQuickVideoOutput::updatePaintNode(QSGNode *oldNode,
+ QQuickItem::UpdatePaintNodeData *data)
+{
+ Q_UNUSED(data);
+ _q_updateGeometry();
+
+ QSGVideoNode *videoNode = static_cast<QSGVideoNode *>(oldNode);
+
+ QMutexLocker lock(&m_frameMutex);
+
+ if (m_frameChanged) {
+ if (videoNode && videoNode->pixelFormat() != m_frame.pixelFormat()) {
+ qCDebug(qLcVideo) << "updatePaintNode: deleting old video node because frame format changed";
+ delete videoNode;
+ videoNode = nullptr;
+ }
+
+ if (!m_frame.isValid()) {
+ qCDebug(qLcVideo) << "updatePaintNode: no frames yet";
+ m_frameChanged = false;
+ return nullptr;
+ }
+
+ if (!videoNode) {
+ // Get a node that supports our frame. The surface is irrelevant, our
+ // QSGVideoItemSurface supports (logically) anything.
+ updateGeometry();
+ videoNode = new QSGVideoNode(this, m_videoFormat);
+ qCDebug(qLcVideo) << "updatePaintNode: Video node created. Handle type:" << m_frame.handleType();
+ }
+ }
+
+ if (!videoNode) {
+ m_frameChanged = false;
+ m_frame = QVideoFrame();
+ return nullptr;
+ }
+
+ if (m_frameChanged) {
+ videoNode->setCurrentFrame(m_frame);
+
+ updateHdr(videoNode);
+
+ //don't keep the frame for more than really necessary
+ m_frameChanged = false;
+ m_frame = QVideoFrame();
+ }
+
+ // Negative rotations need lots of %360
+ videoNode->setTexturedRectGeometry(m_renderedRect, m_sourceTextureRect,
+ qNormalizedOrientation(orientation()));
+
+ return videoNode;
+}
+
+void QQuickVideoOutput::updateHdr(QSGVideoNode *videoNode)
+{
+ auto *videoOutputWindow = window();
+ if (!videoOutputWindow)
+ return;
+
+ auto *swapChain = videoOutputWindow->swapChain();
+ if (!swapChain)
+ return;
+
+ const auto requiredSwapChainFormat = qGetRequiredSwapChainFormat(m_frame.surfaceFormat());
+ if (qShouldUpdateSwapChainFormat(swapChain, requiredSwapChainFormat)) {
+ auto *recreateSwapChainJob = QRunnable::create([swapChain, requiredSwapChainFormat]() {
+ swapChain->destroy();
+ swapChain->setFormat(requiredSwapChainFormat);
+ swapChain->createOrResize();
+ });
+
+ // Even though the 'recreate swap chain' job is scheduled for the current frame the
+ // effect will be visible only starting from the next frame since the recreation would
+ // happen after the actual swap.
+ videoOutputWindow->scheduleRenderJob(recreateSwapChainJob, QQuickWindow::AfterSwapStage);
+ }
+
+ videoNode->setSurfaceFormat(swapChain->format());
+ videoNode->setHdrInfo(swapChain->hdrInfo());
+}
+
+QRectF QQuickVideoOutput::adjustedViewport() const
+{
+ return m_videoFormat.viewport();
+}
+
+void QQuickVideoOutput::setFrame(const QVideoFrame &frame)
+{
+ QMutexLocker lock(&m_frameMutex);
+
+ m_videoFormat = frame.surfaceFormat();
+ m_frame = frame;
+ m_frameOrientation = static_cast<int>(frame.rotation());
+ m_frameChanged = true;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquickvideooutput_p.cpp"
diff --git a/src/multimediaquick/qquickvideooutput_p.h b/src/multimediaquick/qquickvideooutput_p.h
new file mode 100644
index 000000000..d71051939
--- /dev/null
+++ b/src/multimediaquick/qquickvideooutput_p.h
@@ -0,0 +1,128 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKVIDEOOUTPUT_P_H
+#define QQUICKVIDEOOUTPUT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qrect.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtQuick/qquickitem.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qmutex.h>
+
+#include <private/qtmultimediaquickglobal_p.h>
+#include <qvideoframe.h>
+#include <qvideoframeformat.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickVideoBackend;
+class QVideoOutputOrientationHandler;
+class QVideoSink;
+class QSGVideoNode;
+
+class Q_MULTIMEDIAQUICK_EXPORT QQuickVideoOutput : public QQuickItem
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(QQuickVideoOutput)
+ Q_PROPERTY(FillMode fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged)
+ Q_PROPERTY(int orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
+ Q_PROPERTY(QRectF sourceRect READ sourceRect NOTIFY sourceRectChanged)
+ Q_PROPERTY(QRectF contentRect READ contentRect NOTIFY contentRectChanged)
+ Q_PROPERTY(QVideoSink* videoSink READ videoSink CONSTANT)
+ Q_MOC_INCLUDE(qvideosink.h)
+ Q_MOC_INCLUDE(qvideoframe.h)
+ QML_NAMED_ELEMENT(VideoOutput)
+
+public:
+
+ enum FillMode
+ {
+ Stretch = Qt::IgnoreAspectRatio,
+ PreserveAspectFit = Qt::KeepAspectRatio,
+ PreserveAspectCrop = Qt::KeepAspectRatioByExpanding
+ };
+ Q_ENUM(FillMode)
+
+ QQuickVideoOutput(QQuickItem *parent = 0);
+ ~QQuickVideoOutput();
+
+ Q_INVOKABLE QVideoSink *videoSink() const;
+
+ FillMode fillMode() const;
+ void setFillMode(FillMode mode);
+
+ int orientation() const;
+ void setOrientation(int);
+
+ QRectF sourceRect() const;
+ QRectF contentRect() const;
+
+Q_SIGNALS:
+ void sourceChanged();
+ void fillModeChanged(QQuickVideoOutput::FillMode);
+ void orientationChanged();
+ void sourceRectChanged();
+ void contentRectChanged();
+
+protected:
+ QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
+ void itemChange(ItemChange change, const ItemChangeData &changeData) override;
+ void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+ void releaseResources() override;
+
+private:
+ QSize nativeSize() const;
+ void updateGeometry();
+ QRectF adjustedViewport() const;
+
+ void setFrame(const QVideoFrame &frame);
+
+ void invalidateSceneGraph();
+
+ void initRhiForSink();
+ void updateHdr(QSGVideoNode *videoNode);
+
+private Q_SLOTS:
+ void _q_newFrame(QSize);
+ void _q_updateGeometry();
+ void _q_invalidateSceneGraph();
+ void _q_sceneGraphInitialized();
+
+private:
+ QSize m_nativeSize;
+
+ bool m_geometryDirty = true;
+ QRectF m_lastRect; // Cache of last rect to avoid recalculating geometry
+ QRectF m_contentRect; // Destination pixel coordinates, unclipped
+ int m_orientation = 0;
+ int m_frameOrientation = 0;
+ Qt::AspectRatioMode m_aspectRatioMode = Qt::KeepAspectRatio;
+
+ QPointer<QQuickWindow> m_window;
+ QVideoSink *m_sink = nullptr;
+ QVideoFrameFormat m_videoFormat;
+
+ QList<QVideoFrame> m_videoFrameQueue;
+ QVideoFrame m_frame;
+ bool m_frameChanged = false;
+ QMutex m_frameMutex;
+ QRectF m_renderedRect; // Destination pixel coordinates, clipped
+ QRectF m_sourceTextureRect; // Source texture coordinates
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKVIDEOOUTPUT_P_H
diff --git a/src/multimediaquick/qsgvideonode_p.cpp b/src/multimediaquick/qsgvideonode_p.cpp
new file mode 100644
index 000000000..405744507
--- /dev/null
+++ b/src/multimediaquick/qsgvideonode_p.cpp
@@ -0,0 +1,378 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgvideonode_p.h"
+#include "private/qmultimediautils_p.h"
+#include <QtQuick/qsgmaterial.h>
+#include "qsgvideotexture_p.h"
+#include <QtMultimedia/private/qvideotexturehelper_p.h>
+#include <private/qsginternaltextnode_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qquickvideooutput_p.h>
+#include <private/qhwvideobuffer_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/* Helpers */
+static inline void qSetGeom(QSGGeometry::TexturedPoint2D *v, const QPointF &p)
+{
+ v->x = p.x();
+ v->y = p.y();
+}
+
+static inline void qSetTex(QSGGeometry::TexturedPoint2D *v, const QPointF &p)
+{
+ v->tx = p.x();
+ v->ty = p.y();
+}
+
+static inline void qSwapTex(QSGGeometry::TexturedPoint2D *v0, QSGGeometry::TexturedPoint2D *v1)
+{
+ auto tvx = v0->tx;
+ auto tvy = v0->ty;
+ v0->tx = v1->tx;
+ v0->ty = v1->ty;
+ v1->tx = tvx;
+ v1->ty = tvy;
+}
+
+class QSGVideoMaterial;
+
+class QSGVideoMaterialRhiShader : public QSGMaterialShader
+{
+public:
+ QSGVideoMaterialRhiShader(const QVideoFrameFormat &videoFormat,
+ const QRhiSwapChain::Format surfaceFormat,
+ const QRhiSwapChainHdrInfo &hdrInfo)
+ : m_videoFormat(videoFormat)
+ , m_surfaceFormat(surfaceFormat)
+ , m_hdrInfo(hdrInfo)
+ {
+ setShaderFileName(VertexStage, QVideoTextureHelper::vertexShaderFileName(m_videoFormat));
+ setShaderFileName(
+ FragmentStage,
+ QVideoTextureHelper::fragmentShaderFileName(m_videoFormat, m_surfaceFormat));
+ }
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newMaterial,
+ QSGMaterial *oldMaterial) override;
+
+ void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+
+protected:
+ QVideoFrameFormat m_videoFormat;
+ QRhiSwapChain::Format m_surfaceFormat;
+ QRhiSwapChainHdrInfo m_hdrInfo;
+};
+
+class QSGVideoMaterial : public QSGMaterial
+{
+public:
+ QSGVideoMaterial(const QVideoFrameFormat &videoFormat);
+
+ [[nodiscard]] QSGMaterialType *type() const override {
+ static constexpr int NFormats = QRhiSwapChain::HDRExtendedDisplayP3Linear + 1;
+ static QSGMaterialType type[QVideoFrameFormat::NPixelFormats][NFormats];
+ return &type[m_videoFormat.pixelFormat()][m_surfaceFormat];
+ }
+
+ [[nodiscard]] QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override {
+ return new QSGVideoMaterialRhiShader(m_videoFormat, m_surfaceFormat, m_hdrInfo);
+ }
+
+ int compare(const QSGMaterial *other) const override {
+ const QSGVideoMaterial *m = static_cast<const QSGVideoMaterial *>(other);
+
+ qint64 diff = m_textures[0].comparisonKey() - m->m_textures[0].comparisonKey();
+ if (!diff)
+ diff = m_textures[1].comparisonKey() - m->m_textures[1].comparisonKey();
+ if (!diff)
+ diff = m_textures[2].comparisonKey() - m->m_textures[2].comparisonKey();
+
+ return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
+ }
+
+ void updateBlending() {
+ // ### respect video formats with Alpha
+ setFlag(Blending, !qFuzzyCompare(m_opacity, float(1.0)));
+ }
+
+ void setCurrentFrame(const QVideoFrame &frame) {
+ m_currentFrame = frame;
+ m_texturesDirty = true;
+ }
+
+ void setSurfaceFormat(const QRhiSwapChain::Format surfaceFormat)
+ {
+ m_surfaceFormat = surfaceFormat;
+ }
+
+ void setHdrInfo(const QRhiSwapChainHdrInfo &hdrInfo)
+ {
+ m_hdrInfo = hdrInfo;
+ }
+
+ void updateTextures(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates);
+
+ QVideoFrameFormat m_videoFormat;
+ QRhiSwapChain::Format m_surfaceFormat = QRhiSwapChain::SDR;
+ float m_opacity = 1.0f;
+ QRhiSwapChainHdrInfo m_hdrInfo;
+
+ bool m_texturesDirty = false;
+ QVideoFrame m_currentFrame;
+
+ enum { NVideoFrameSlots = 4 };
+ QVideoFrame m_videoFrameSlots[NVideoFrameSlots];
+ std::array<QSGVideoTexture, 3> m_textures;
+ std::unique_ptr<QVideoFrameTextures> m_videoFrameTextures;
+};
+
+void QSGVideoMaterial::updateTextures(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ if (!m_texturesDirty)
+ return;
+
+ // keep the video frames alive until we know that they are not needed anymore
+ Q_ASSERT(NVideoFrameSlots >= rhi->resourceLimit(QRhi::FramesInFlight));
+ m_videoFrameSlots[rhi->currentFrameSlot()] = m_currentFrame;
+
+ // update and upload all textures
+ m_videoFrameTextures = QVideoTextureHelper::createTextures(m_currentFrame, rhi, resourceUpdates, std::move(m_videoFrameTextures));
+ if (!m_videoFrameTextures)
+ return;
+
+ for (int plane = 0; plane < 3; ++plane)
+ m_textures[plane].setRhiTexture(m_videoFrameTextures->texture(plane));
+ m_texturesDirty = false;
+}
+
+
+bool QSGVideoMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial,
+ QSGMaterial *oldMaterial)
+{
+ Q_UNUSED(oldMaterial);
+
+ auto m = static_cast<QSGVideoMaterial *>(newMaterial);
+
+ if (!state.isMatrixDirty() && !state.isOpacityDirty())
+ return false;
+
+ if (state.isOpacityDirty()) {
+ m->m_opacity = state.opacity();
+ m->updateBlending();
+ }
+
+ // Do this here, not in updateSampledImage. First, with multiple textures we want to
+ // do this once. More importantly, on some platforms (Android) the externalMatrix is
+ // updated by this function and we need that already in updateUniformData.
+ m->updateTextures(state.rhi(), state.resourceUpdateBatch());
+
+ float maxNits = 100; // Default to de-facto SDR nits
+ if (m_surfaceFormat == QRhiSwapChain::HDRExtendedSrgbLinear) {
+ if (m_hdrInfo.limitsType == QRhiSwapChainHdrInfo::ColorComponentValue)
+ maxNits = 100 * m_hdrInfo.limits.colorComponentValue.maxColorComponentValue;
+ else
+ maxNits = m_hdrInfo.limits.luminanceInNits.maxLuminance;
+ }
+
+ QVideoTextureHelper::updateUniformData(state.uniformData(), m_videoFormat,
+ m->m_currentFrame, state.combinedMatrix(), state.opacity(), maxNits);
+
+ return true;
+}
+
+void QSGVideoMaterialRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+ Q_UNUSED(state);
+ Q_UNUSED(oldMaterial);
+ if (binding < 1 || binding > 3)
+ return;
+
+ auto m = static_cast<QSGVideoMaterial *>(newMaterial);
+ *texture = &m->m_textures[binding - 1];
+}
+
+QSGVideoMaterial::QSGVideoMaterial(const QVideoFrameFormat &videoFormat)
+ : m_videoFormat(videoFormat)
+{
+ setFlag(Blending, false);
+}
+
+QSGVideoNode::QSGVideoNode(QQuickVideoOutput *parent, const QVideoFrameFormat &videoFormat)
+ : m_parent(parent), m_videoFormat(videoFormat)
+{
+ setFlag(QSGNode::OwnsMaterial);
+ setFlag(QSGNode::OwnsGeometry);
+ m_material = new QSGVideoMaterial(videoFormat);
+ setMaterial(m_material);
+}
+
+QSGVideoNode::~QSGVideoNode()
+{
+ delete m_subtitleTextNode;
+}
+
+void QSGVideoNode::setCurrentFrame(const QVideoFrame &frame)
+{
+ m_material->setCurrentFrame(frame);
+ markDirty(DirtyMaterial);
+ updateSubtitle(frame);
+}
+
+void QSGVideoNode::setSurfaceFormat(const QRhiSwapChain::Format surfaceFormat)
+{
+ m_material->setSurfaceFormat(surfaceFormat);
+ markDirty(DirtyMaterial);
+}
+
+void QSGVideoNode::setHdrInfo(const QRhiSwapChainHdrInfo &hdrInfo)
+{
+ m_material->setHdrInfo(hdrInfo);
+ markDirty(DirtyMaterial);
+}
+
+void QSGVideoNode::updateSubtitle(const QVideoFrame &frame)
+{
+ QSize subtitleFrameSize = m_rect.size().toSize();
+ if (subtitleFrameSize.isEmpty())
+ return;
+
+ subtitleFrameSize = qRotatedFrameSize(subtitleFrameSize, m_orientation);
+
+ if (!m_subtitleLayout.update(subtitleFrameSize, frame.subtitleText()))
+ return;
+
+ delete m_subtitleTextNode;
+ m_subtitleTextNode = nullptr;
+ if (frame.subtitleText().isEmpty())
+ return;
+
+ QQuickItemPrivate *parent_d = QQuickItemPrivate::get(m_parent);
+
+ m_subtitleTextNode = parent_d->sceneGraphContext()->createInternalTextNode(parent_d->sceneGraphRenderContext());
+ m_subtitleTextNode->setColor(Qt::white);
+ QColor bgColor = Qt::black;
+ bgColor.setAlpha(128);
+ m_subtitleTextNode->addRectangleNode(m_subtitleLayout.bounds, bgColor);
+ m_subtitleTextNode->addTextLayout(m_subtitleLayout.layout.position(), &m_subtitleLayout.layout);
+ appendChildNode(m_subtitleTextNode);
+ setSubtitleGeometry();
+}
+
+void QSGVideoNode::setSubtitleGeometry()
+{
+ if (!m_subtitleTextNode)
+ return;
+
+ if (m_material)
+ updateSubtitle(m_material->m_currentFrame);
+
+ float rotate = -1.f * m_orientation;
+ float yTranslate = 0;
+ float xTranslate = 0;
+ if (m_orientation == 90) {
+ yTranslate = m_rect.height();
+ } else if (m_orientation == 180) {
+ yTranslate = m_rect.height();
+ xTranslate = m_rect.width();
+ } else if (m_orientation == 270) {
+ xTranslate = m_rect.width();
+ }
+
+ QMatrix4x4 transform;
+ transform.translate(m_rect.x() + xTranslate, m_rect.y() + yTranslate);
+ transform.rotate(rotate, 0, 0, 1);
+
+ m_subtitleTextNode->setMatrix(transform);
+ m_subtitleTextNode->markDirty(DirtyGeometry);
+}
+
+/* Update the vertices and texture coordinates. Orientation must be in {0,90,180,270} */
+void QSGVideoNode::setTexturedRectGeometry(const QRectF &rect, const QRectF &textureRect, int orientation)
+{
+ const auto currentFrameOrientation = m_material ? static_cast<int>(m_material->m_currentFrame.rotation()) : 0;
+ bool frameChanged = false;
+ if (m_material) {
+ if (currentFrameOrientation != m_frameOrientation
+ || m_material->m_currentFrame.mirrored() != m_frameMirrored) {
+ frameChanged = true;
+ }
+ }
+ if (rect == m_rect && textureRect == m_textureRect && orientation == m_orientation
+ && !frameChanged)
+ return;
+
+ m_rect = rect;
+ m_textureRect = textureRect;
+ m_orientation = orientation;
+ if (m_material) {
+ m_frameOrientation = currentFrameOrientation;
+ m_frameMirrored = m_material->m_currentFrame.mirrored();
+ }
+ const int videoRotation = (orientation + currentFrameOrientation) % 360;
+
+ QSGGeometry *g = geometry();
+
+ if (g == nullptr)
+ g = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
+
+ QSGGeometry::TexturedPoint2D *v = g->vertexDataAsTexturedPoint2D();
+
+ // Set geometry first
+ qSetGeom(v + 0, rect.topLeft());
+ qSetGeom(v + 1, rect.bottomLeft());
+ qSetGeom(v + 2, rect.topRight());
+ qSetGeom(v + 3, rect.bottomRight());
+
+ // and then texture coordinates
+ switch (videoRotation) {
+ default:
+ // tl, bl, tr, br
+ qSetTex(v + 0, textureRect.topLeft());
+ qSetTex(v + 1, textureRect.bottomLeft());
+ qSetTex(v + 2, textureRect.topRight());
+ qSetTex(v + 3, textureRect.bottomRight());
+ break;
+
+ case 90:
+ // bl, br, tl, tr
+ qSetTex(v + 0, textureRect.bottomLeft());
+ qSetTex(v + 1, textureRect.bottomRight());
+ qSetTex(v + 2, textureRect.topLeft());
+ qSetTex(v + 3, textureRect.topRight());
+ break;
+
+ case 180:
+ // br, tr, bl, tl
+ qSetTex(v + 0, textureRect.bottomRight());
+ qSetTex(v + 1, textureRect.topRight());
+ qSetTex(v + 2, textureRect.bottomLeft());
+ qSetTex(v + 3, textureRect.topLeft());
+ break;
+
+ case 270:
+ // tr, tl, br, bl
+ qSetTex(v + 0, textureRect.topRight());
+ qSetTex(v + 1, textureRect.topLeft());
+ qSetTex(v + 2, textureRect.bottomRight());
+ qSetTex(v + 3, textureRect.bottomLeft());
+ break;
+ }
+
+ if (m_material && m_material->m_currentFrame.mirrored()) {
+ qSwapTex(v + 0, v + 2);
+ qSwapTex(v + 1, v + 3);
+ }
+
+ if (!geometry())
+ setGeometry(g);
+
+ markDirty(DirtyGeometry);
+
+ setSubtitleGeometry();
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimediaquick/qsgvideonode_p.h b/src/multimediaquick/qsgvideonode_p.h
new file mode 100644
index 000000000..518fdfed2
--- /dev/null
+++ b/src/multimediaquick/qsgvideonode_p.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGVIDEONODE_P_H
+#define QSGVIDEONODE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/qsgnode.h>
+#include <private/qtmultimediaquickglobal_p.h>
+#include "private/qvideotexturehelper_p.h"
+
+#include <QtMultimedia/qvideoframe.h>
+#include <QtMultimedia/qvideoframeformat.h>
+#include <QtGui/qopenglfunctions.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGVideoMaterial;
+class QQuickVideoOutput;
+class QSGInternalTextNode;
+
+class QSGVideoNode : public QSGGeometryNode
+{
+public:
+ QSGVideoNode(QQuickVideoOutput *parent, const QVideoFrameFormat &videoFormat);
+ ~QSGVideoNode();
+
+ QVideoFrameFormat::PixelFormat pixelFormat() const { return m_videoFormat.pixelFormat(); }
+ void setCurrentFrame(const QVideoFrame &frame);
+ void setSurfaceFormat(const QRhiSwapChain::Format surfaceFormat);
+ void setHdrInfo(const QRhiSwapChainHdrInfo &hdrInfo);
+
+ void setTexturedRectGeometry(const QRectF &boundingRect, const QRectF &textureRect, int orientation);
+
+private:
+ void updateSubtitle(const QVideoFrame &frame);
+ void setSubtitleGeometry();
+
+ QQuickVideoOutput *m_parent = nullptr;
+ QRectF m_rect;
+ QRectF m_textureRect;
+ int m_orientation = -1;
+ int m_frameOrientation = -1;
+ bool m_frameMirrored = false;
+
+ QVideoFrameFormat m_videoFormat;
+ QSGVideoMaterial *m_material = nullptr;
+
+ QVideoTextureHelper::SubtitleLayout m_subtitleLayout;
+ QSGInternalTextNode *m_subtitleTextNode = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGVIDEONODE_H
diff --git a/src/multimediaquick/qsgvideotexture.cpp b/src/multimediaquick/qsgvideotexture.cpp
new file mode 100644
index 000000000..aa3da9b07
--- /dev/null
+++ b/src/multimediaquick/qsgvideotexture.cpp
@@ -0,0 +1,77 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgvideotexture_p.h"
+#include <QtQuick/qsgtexturematerial.h>
+#include <QtQuick/qsgmaterial.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGVideoTexturePrivate
+{
+ Q_DECLARE_PUBLIC(QSGVideoTexture)
+
+private:
+ QSGVideoTexture *q_ptr = nullptr;
+ QRhiTexture::Format m_format;
+ QSize m_size;
+ QByteArray m_data;
+ QRhiTexture *m_texture = nullptr;
+};
+
+QSGVideoTexture::QSGVideoTexture()
+ : d_ptr(new QSGVideoTexturePrivate)
+{
+ d_ptr->q_ptr = this;
+
+ // Nearest filtering just looks bad for any text in videos
+ setFiltering(Linear);
+}
+
+QSGVideoTexture::~QSGVideoTexture() = default;
+
+qint64 QSGVideoTexture::comparisonKey() const
+{
+ Q_D(const QSGVideoTexture);
+ if (d->m_texture)
+ return qint64(qintptr(d->m_texture));
+
+ // two textures (and so materials) with not-yet-created texture underneath are never equal
+ return qint64(qintptr(this));
+}
+
+QRhiTexture *QSGVideoTexture::rhiTexture() const
+{
+ return d_func()->m_texture;
+}
+
+QSize QSGVideoTexture::textureSize() const
+{
+ return d_func()->m_size;
+}
+
+bool QSGVideoTexture::hasAlphaChannel() const
+{
+ Q_D(const QSGVideoTexture);
+ return d->m_format == QRhiTexture::RGBA8 || d->m_format == QRhiTexture::BGRA8;
+}
+
+bool QSGVideoTexture::hasMipmaps() const
+{
+ return mipmapFiltering() != QSGTexture::None;
+}
+
+void QSGVideoTexture::setData(QRhiTexture::Format f, const QSize &s, const uchar *data, int bytes)
+{
+ Q_D(QSGVideoTexture);
+ d->m_size = s;
+ d->m_format = f;
+ d->m_data = {reinterpret_cast<const char *>(data), bytes};
+}
+
+void QSGVideoTexture::setRhiTexture(QRhiTexture *texture)
+{
+ d_func()->m_texture = texture;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimediaquick/qsgvideotexture_p.h b/src/multimediaquick/qsgvideotexture_p.h
new file mode 100644
index 000000000..f9a7377b8
--- /dev/null
+++ b/src/multimediaquick/qsgvideotexture_p.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSGVIDEOTEXTURE_H
+#define QSGVIDEOTEXTURE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/QSGTexture>
+#include <QImage>
+#include <rhi/qrhi.h>
+#include <private/qtmultimediaquickglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGVideoTexturePrivate;
+class Q_MULTIMEDIAQUICK_EXPORT QSGVideoTexture : public QSGTexture
+{
+ Q_DECLARE_PRIVATE(QSGVideoTexture)
+public:
+ QSGVideoTexture();
+ ~QSGVideoTexture();
+
+ qint64 comparisonKey() const override;
+ QRhiTexture *rhiTexture() const override;
+ QSize textureSize() const override;
+ bool hasAlphaChannel() const override;
+ bool hasMipmaps() const override;
+ void setRhiTexture(QRhiTexture *texture);
+ void setData(QRhiTexture::Format f, const QSize &s, const uchar *data, int bytes);
+
+protected:
+ QScopedPointer<QSGVideoTexturePrivate> d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGVIDEOTEXTURE_H
diff --git a/src/multimediaquick/qtmultimediaquickglobal_p.h b/src/multimediaquick/qtmultimediaquickglobal_p.h
new file mode 100644
index 000000000..44eff1649
--- /dev/null
+++ b/src/multimediaquick/qtmultimediaquickglobal_p.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QMULTIMEDIAQUICKDEFS_P_H
+#define QMULTIMEDIAQUICKDEFS_P_H
+
+#include <QtCore/private/qglobal_p.h>
+#include <QtMultimediaQuick/qtmultimediaquickexports.h>
+
+QT_BEGIN_NAMESPACE
+
+void Q_MULTIMEDIAQUICK_EXPORT qml_register_types_QtMultimedia();
+
+QT_END_NAMESPACE
+
+#endif // QMULTIMEDIAQUICKDEFS_P_H
+
diff --git a/src/multimediaquick/qtmultimediaquicktypes.cpp b/src/multimediaquick/qtmultimediaquicktypes.cpp
new file mode 100644
index 000000000..7ba5711a5
--- /dev/null
+++ b/src/multimediaquick/qtmultimediaquicktypes.cpp
@@ -0,0 +1,12 @@
+// Copyright (C) 2022 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qtmultimediaquicktypes_p.h"
+
+QT_BEGIN_NAMESPACE
+
+// TODO...
+
+QT_END_NAMESPACE
+
+#include "moc_qtmultimediaquicktypes_p.cpp"
diff --git a/src/multimediaquick/qtmultimediaquicktypes_p.h b/src/multimediaquick/qtmultimediaquicktypes_p.h
new file mode 100644
index 000000000..80133c203
--- /dev/null
+++ b/src/multimediaquick/qtmultimediaquicktypes_p.h
@@ -0,0 +1,201 @@
+// Copyright (C) 2022 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QTMULTIMEDIAQUICKTYPES_H
+#define QTMULTIMEDIAQUICKTYPES_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQml/qqml.h>
+#include <QtMultimedia/QtMultimedia>
+#include <private/qtmultimediaquickglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QMediaCaptureSessionForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QMediaCaptureSession)
+ QML_NAMED_ELEMENT(CaptureSession) // ### MediaCaptureSession?
+};
+
+struct QCameraForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QCamera)
+ QML_NAMED_ELEMENT(Camera)
+};
+
+struct QImageCaptureForeign
+{
+ Q_GADGET
+ QML_ANONYMOUS
+ QML_FOREIGN(QImageCapture)
+};
+
+struct QScreenCaptureForeign
+{
+ Q_GADGET
+ QML_ANONYMOUS
+ QML_FOREIGN(QScreenCapture)
+};
+
+struct QScreenForeign
+{
+ Q_GADGET
+ QML_ANONYMOUS
+ QML_FOREIGN(QScreen)
+};
+
+struct QMediaRecorderForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QMediaRecorder)
+ QML_NAMED_ELEMENT(MediaRecorder)
+};
+
+struct QMediaMetaDataForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QMediaMetaData)
+ QML_NAMED_ELEMENT(mediaMetaData)
+};
+
+// To prevent the same type from being exported twice into qmltypes
+// (for value type and for the enums)
+struct QMediaMetaDataDerived : public QMediaMetaData
+{
+ Q_GADGET
+};
+
+namespace QMediaMetaDataNamespaceForeign
+{
+ Q_NAMESPACE
+ QML_FOREIGN_NAMESPACE(QMediaMetaDataDerived)
+ QML_NAMED_ELEMENT(MediaMetaData)
+};
+
+struct QMediaDevicesForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QMediaDevices)
+ QML_NAMED_ELEMENT(MediaDevices)
+};
+
+struct QAudioInputForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QAudioInput)
+ QML_NAMED_ELEMENT(AudioInput)
+};
+
+struct QAudioOutputForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QAudioOutput)
+ QML_NAMED_ELEMENT(AudioOutput)
+};
+
+struct QAudioDeviceForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QAudioDevice)
+ QML_NAMED_ELEMENT(audioDevice)
+};
+
+// To prevent the same type from being exported twice into qmltypes
+// (for value type and for the enums)
+struct QAudioDeviceDerived : public QAudioDevice
+{
+ Q_GADGET
+};
+
+namespace QAudioDeviceNamespaceForeign
+{
+ Q_NAMESPACE
+ QML_FOREIGN_NAMESPACE(QAudioDeviceDerived)
+ QML_NAMED_ELEMENT(AudioDevice)
+};
+
+struct QCameraDeviceForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QCameraDevice)
+ QML_NAMED_ELEMENT(cameraDevice)
+};
+
+// To prevent the same type from being exported twice into qmltypes
+// (for value type and for the enums)
+struct QCameraDeviceDerived : public QCameraDevice
+{
+ Q_GADGET
+};
+
+namespace QCameraDeviceNamespaceForeign
+{
+ Q_NAMESPACE
+ QML_FOREIGN_NAMESPACE(QCameraDeviceDerived)
+ QML_NAMED_ELEMENT(CameraDevice)
+};
+
+struct QMediaFormatForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QMediaFormat)
+ QML_NAMED_ELEMENT(mediaFormat)
+};
+
+// To prevent the same type from being exported twice into qmltypes
+// (for value type and for the enums)
+struct QMediaFormatDerived : public QMediaFormat
+{
+ Q_GADGET
+};
+
+namespace QMediaFormatNamespaceForeign
+{
+ Q_NAMESPACE
+ QML_FOREIGN_NAMESPACE(QMediaFormatDerived)
+ QML_NAMED_ELEMENT(MediaFormat)
+};
+
+struct QCameraFormatForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QCameraFormat)
+ QML_NAMED_ELEMENT(cameraFormat)
+};
+
+struct QVideoSinkForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QVideoSink)
+ QML_NAMED_ELEMENT(VideoSink)
+};
+
+struct QCapturableWindowForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QCapturableWindow)
+ QML_NAMED_ELEMENT(capturableWindow)
+};
+
+struct QWindowCaptureForeign
+{
+ Q_GADGET
+ QML_FOREIGN(QWindowCapture)
+ QML_NAMED_ELEMENT(WindowCapture)
+};
+
+QT_END_NAMESPACE
+
+#endif