diff options
Diffstat (limited to 'src/multimediaquick')
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 |