summaryrefslogtreecommitdiff
path: root/examples/multimedia
diff options
context:
space:
mode:
Diffstat (limited to 'examples/multimedia')
-rw-r--r--examples/multimedia/CMakeLists.txt10
-rw-r--r--examples/multimedia/camera/CMakeLists.txt81
-rw-r--r--examples/multimedia/camera/android/AndroidManifest.xml68
-rw-r--r--examples/multimedia/camera/camera.cpp387
-rw-r--r--examples/multimedia/camera/camera.h101
-rw-r--r--examples/multimedia/camera/camera.pro40
-rw-r--r--examples/multimedia/camera/camera.qrc5
-rw-r--r--examples/multimedia/camera/camera.ui488
-rw-r--r--examples/multimedia/camera/camera_mobile.ui504
-rw-r--r--examples/multimedia/camera/doc/images/camera-example.pngbin0 -> 13647 bytes
-rw-r--r--examples/multimedia/camera/doc/src/camera.qdoc58
-rw-r--r--examples/multimedia/camera/images/shutter.svg21
-rw-r--r--examples/multimedia/camera/imagesettings.cpp83
-rw-r--r--examples/multimedia/camera/imagesettings.h39
-rw-r--r--examples/multimedia/camera/imagesettings.ui123
-rw-r--r--examples/multimedia/camera/ios/Info.plist.in50
-rw-r--r--examples/multimedia/camera/macos/Info.plist.in49
-rw-r--r--examples/multimedia/camera/main.cpp16
-rw-r--r--examples/multimedia/camera/metadatadialog.cpp80
-rw-r--r--examples/multimedia/camera/metadatadialog.h31
-rw-r--r--examples/multimedia/camera/videosettings.cpp201
-rw-r--r--examples/multimedia/camera/videosettings.h38
-rw-r--r--examples/multimedia/camera/videosettings.ui213
-rw-r--r--examples/multimedia/camera/videosettings_mobile.ui207
-rw-r--r--examples/multimedia/multimedia.pro10
-rw-r--r--examples/multimedia/player/CMakeLists.txt41
-rw-r--r--examples/multimedia/player/doc/images/mediaplayerex.jpgbin0 -> 28825 bytes
-rw-r--r--examples/multimedia/player/doc/src/player.qdoc48
-rw-r--r--examples/multimedia/player/main.cpp37
-rw-r--r--examples/multimedia/player/player.cpp506
-rw-r--r--examples/multimedia/player/player.h99
-rw-r--r--examples/multimedia/player/player.pro27
-rw-r--r--examples/multimedia/player/playercontrols.cpp172
-rw-r--r--examples/multimedia/player/playercontrols.h62
-rw-r--r--examples/multimedia/player/playlistmodel.cpp102
-rw-r--r--examples/multimedia/player/playlistmodel.h52
-rw-r--r--examples/multimedia/player/qmediaplaylist.cpp653
-rw-r--r--examples/multimedia/player/qmediaplaylist.h96
-rw-r--r--examples/multimedia/player/qmediaplaylist_p.h112
-rw-r--r--examples/multimedia/player/qplaylistfileparser.cpp605
-rw-r--r--examples/multimedia/player/qplaylistfileparser_p.h80
-rw-r--r--examples/multimedia/player/videowidget.cpp46
-rw-r--r--examples/multimedia/player/videowidget.h22
-rw-r--r--examples/multimedia/videographicsitem/CMakeLists.txt39
-rw-r--r--examples/multimedia/videographicsitem/doc/images/video-videographicsitem.pngbin0 -> 54436 bytes
-rw-r--r--examples/multimedia/videographicsitem/doc/src/videographicsitem.qdoc19
-rw-r--r--examples/multimedia/videographicsitem/main.cpp39
-rw-r--r--examples/multimedia/videographicsitem/videographicsitem.pro14
-rw-r--r--examples/multimedia/videographicsitem/videoplayer.cpp139
-rw-r--r--examples/multimedia/videographicsitem/videoplayer.h48
-rw-r--r--examples/multimedia/videowidget/CMakeLists.txt39
-rw-r--r--examples/multimedia/videowidget/doc/images/video-videowidget.pngbin0 -> 54199 bytes
-rw-r--r--examples/multimedia/videowidget/doc/src/videowidget.qdoc17
-rw-r--r--examples/multimedia/videowidget/main.cpp41
-rw-r--r--examples/multimedia/videowidget/videoplayer.cpp130
-rw-r--r--examples/multimedia/videowidget/videoplayer.h44
-rw-r--r--examples/multimedia/videowidget/videowidget.pro16
57 files changed, 6242 insertions, 6 deletions
diff --git a/examples/multimedia/CMakeLists.txt b/examples/multimedia/CMakeLists.txt
index 6123c7994..19a999541 100644
--- a/examples/multimedia/CMakeLists.txt
+++ b/examples/multimedia/CMakeLists.txt
@@ -3,13 +3,17 @@ if(NOT ANDROID AND NOT IOS)
qt_internal_add_example(devices)
endif()
if(TARGET Qt::Widgets)
- qt_internal_add_example(spectrum)
- qt_internal_add_example(audiorecorder)
if(NOT ANDROID AND NOT IOS)
qt_internal_add_example(audiodevices)
endif()
- qt_internal_add_example(audiosource)
qt_internal_add_example(audiooutput)
+ qt_internal_add_example(audiorecorder)
+ qt_internal_add_example(audiosource)
+ qt_internal_add_example(camera)
+ qt_internal_add_example(player)
+ qt_internal_add_example(spectrum)
+ qt_internal_add_example(videographicsitem)
+ qt_internal_add_example(videowidget)
endif()
if(TARGET Qt::Quick)
qt_internal_add_example(declarative-camera)
diff --git a/examples/multimedia/camera/CMakeLists.txt b/examples/multimedia/camera/CMakeLists.txt
new file mode 100644
index 000000000..2c2c2a935
--- /dev/null
+++ b/examples/multimedia/camera/CMakeLists.txt
@@ -0,0 +1,81 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(camera LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/camera")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia MultimediaWidgets Widgets)
+
+set(camera_form "")
+set(videosettings_form "")
+if(ANDROID OR IOS)
+ set(camera_form camera_mobile.ui)
+ set(videosettings_form videosettings_mobile.ui)
+else()
+ set(camera_form camera.ui)
+ set(videosettings_form videosettings.ui)
+endif()
+
+qt_add_executable(camera
+ MANUAL_FINALIZATION
+ camera.cpp camera.h ${camera_form}
+ imagesettings.cpp imagesettings.h imagesettings.ui
+ main.cpp
+ videosettings.cpp videosettings.h ${videosettings_form}
+ metadatadialog.cpp metadatadialog.h
+)
+
+set_target_properties(camera PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+if(APPLE AND NOT IOS)
+ set_target_properties(camera PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in"
+ )
+elseif(IOS)
+ set_target_properties(camera PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/Info.plist.in"
+ )
+endif()
+
+set_property(TARGET camera APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR}/android)
+
+target_link_libraries(camera PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Multimedia
+ Qt::MultimediaWidgets
+ Qt::Widgets
+)
+
+# Resources:
+set(camera_resource_files
+ "images/shutter.svg"
+)
+
+qt_add_resources(camera "camera"
+ PREFIX
+ "/"
+ FILES
+ ${camera_resource_files}
+)
+
+qt_finalize_executable(camera)
+
+install(TARGETS camera
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/multimedia/camera/android/AndroidManifest.xml b/examples/multimedia/camera/android/AndroidManifest.xml
new file mode 100644
index 000000000..29c4672cf
--- /dev/null
+++ b/examples/multimedia/camera/android/AndroidManifest.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.qtproject.example.camera"
+ android:installLocation="auto"
+ android:versionCode="-- %%INSERT_VERSION_CODE%% --"
+ android:versionName="-- %%INSERT_VERSION_NAME%% --">
+ <!-- The comment below will be replaced with dependencies permissions upon deployment.
+ Remove the comment if you do not require these default permissions. -->
+ <!-- %%INSERT_PERMISSIONS -->
+
+ <!-- The comment below will be replaced with dependencies permissions upon deployment.
+ Remove the comment if you do not require these default features. -->
+ <!-- %%INSERT_FEATURES -->
+
+ <supports-screens
+ android:anyDensity="true"
+ android:largeScreens="true"
+ android:normalScreens="true"
+ android:smallScreens="true" />
+ <application
+ android:name="org.qtproject.qt.android.bindings.QtApplication"
+ android:extractNativeLibs="true"
+ android:hardwareAccelerated="true"
+ android:label="-- %%INSERT_APP_NAME%% --"
+ android:requestLegacyExternalStorage="true">
+ <activity
+ android:name="org.qtproject.qt.android.bindings.QtActivity"
+ android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
+ android:label="-- %%INSERT_APP_NAME%% --"
+ android:launchMode="singleTop"
+ android:screenOrientation="portrait">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <!-- Application arguments -->
+
+ <meta-data
+ android:name="android.app.arguments"
+ android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
+ <!-- Application arguments -->
+ <meta-data
+ android:name="android.app.lib_name"
+ android:value="-- %%INSERT_APP_LIB_NAME%% --" />
+
+ <!-- Background running -->
+ <!-- Warning: changing this value to true may cause unexpected crashes if the
+ application still try to draw after
+ "applicationStateChanged(Qt::ApplicationSuspended)" signal is sent! -->
+ <meta-data
+ android:name="android.app.background_running"
+ android:value="false" />
+ <!-- Background running -->
+
+ <!-- extract android style -->
+ <!-- available android:values :
+ * default - In most cases this will be the same as "full", but it can also be
+ * something else if needed, e.g., for compatibility reasons
+ * full - useful QWidget & Quick Controls 1 apps
+ * minimal - useful for Quick Controls 2 apps, it is much faster than "full"
+ * none - useful for apps that don't use any of the above Qt modules -->
+ <meta-data
+ android:name="android.app.extract_android_style"
+ android:value="minimal" />
+ <!-- extract android style -->
+ </activity>
+ </application>
+</manifest>
diff --git a/examples/multimedia/camera/camera.cpp b/examples/multimedia/camera/camera.cpp
new file mode 100644
index 000000000..201e6e985
--- /dev/null
+++ b/examples/multimedia/camera/camera.cpp
@@ -0,0 +1,387 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "camera.h"
+#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
+#include "ui_camera_mobile.h"
+#else
+#include "ui_camera.h"
+#endif
+#include "videosettings.h"
+#include "imagesettings.h"
+#include "metadatadialog.h"
+
+#include <QMediaRecorder>
+#include <QVideoWidget>
+#include <QCameraDevice>
+#include <QMediaMetaData>
+#include <QMediaDevices>
+#include <QAudioDevice>
+#include <QAudioInput>
+
+#include <QMessageBox>
+#include <QPalette>
+#include <QImage>
+
+#include <QtWidgets>
+#include <QMediaDevices>
+#include <QMediaFormat>
+
+Camera::Camera()
+ : ui(new Ui::Camera)
+{
+ ui->setupUi(this);
+
+ m_audioInput.reset(new QAudioInput);
+ m_captureSession.setAudioInput(m_audioInput.get());
+
+ //Camera devices:
+
+ videoDevicesGroup = new QActionGroup(this);
+ videoDevicesGroup->setExclusive(true);
+ updateCameras();
+ connect(&m_devices, &QMediaDevices::videoInputsChanged, this, &Camera::updateCameras);
+
+ connect(videoDevicesGroup, &QActionGroup::triggered, this, &Camera::updateCameraDevice);
+ connect(ui->captureWidget, &QTabWidget::currentChanged, this, &Camera::updateCaptureMode);
+
+ connect(ui->metaDataButton, &QPushButton::clicked, this, &Camera::showMetaDataDialog);
+
+ setCamera(QMediaDevices::defaultVideoInput());
+}
+
+void Camera::setCamera(const QCameraDevice &cameraDevice)
+{
+ m_camera.reset(new QCamera(cameraDevice));
+ m_captureSession.setCamera(m_camera.data());
+
+ connect(m_camera.data(), &QCamera::activeChanged, this, &Camera::updateCameraActive);
+ connect(m_camera.data(), &QCamera::errorOccurred, this, &Camera::displayCameraError);
+
+ if (!m_mediaRecorder) {
+ m_mediaRecorder.reset(new QMediaRecorder);
+ m_captureSession.setRecorder(m_mediaRecorder.data());
+ connect(m_mediaRecorder.data(), &QMediaRecorder::recorderStateChanged, this, &Camera::updateRecorderState);
+ }
+
+ m_imageCapture = new QImageCapture;
+ m_captureSession.setImageCapture(m_imageCapture);
+
+ connect(m_mediaRecorder.data(), &QMediaRecorder::durationChanged, this, &Camera::updateRecordTime);
+ connect(m_mediaRecorder.data(), &QMediaRecorder::errorChanged, this, &Camera::displayRecorderError);
+
+ connect(ui->exposureCompensation, &QAbstractSlider::valueChanged, this, &Camera::setExposureCompensation);
+
+ m_captureSession.setVideoOutput(ui->viewfinder);
+
+ updateCameraActive(m_camera->isActive());
+ updateRecorderState(m_mediaRecorder->recorderState());
+
+ connect(m_imageCapture, &QImageCapture::readyForCaptureChanged, this, &Camera::readyForCapture);
+ connect(m_imageCapture, &QImageCapture::imageCaptured, this, &Camera::processCapturedImage);
+ connect(m_imageCapture, &QImageCapture::imageSaved, this, &Camera::imageSaved);
+ connect(m_imageCapture, &QImageCapture::errorOccurred, this, &Camera::displayCaptureError);
+ readyForCapture(m_imageCapture->isReadyForCapture());
+
+ updateCaptureMode();
+
+ if (m_camera->cameraFormat().isNull()) {
+ auto formats = cameraDevice.videoFormats();
+ if (!formats.isEmpty()) {
+ // Choose a decent camera format: Maximum resolution at at least 30 FPS
+ // we use 29 FPS to compare against as some cameras report 29.97 FPS...
+ QCameraFormat bestFormat;
+ for (const auto &fmt : formats) {
+ if (bestFormat.maxFrameRate() < 29 && fmt.maxFrameRate() > bestFormat.maxFrameRate())
+ bestFormat = fmt;
+ else if (bestFormat.maxFrameRate() == fmt.maxFrameRate() &&
+ bestFormat.resolution().width()*bestFormat.resolution().height() <
+ fmt.resolution().width()*fmt.resolution().height())
+ bestFormat = fmt;
+ }
+
+ m_camera->setCameraFormat(bestFormat);
+ m_mediaRecorder->setVideoFrameRate(bestFormat.maxFrameRate());
+ }
+ }
+
+ m_camera->start();
+}
+
+void Camera::keyPressEvent(QKeyEvent * event)
+{
+ if (event->isAutoRepeat())
+ return;
+
+ switch (event->key()) {
+ case Qt::Key_CameraFocus:
+ displayViewfinder();
+ event->accept();
+ break;
+ case Qt::Key_Camera:
+ if (m_doImageCapture) {
+ takeImage();
+ } else {
+ if (m_mediaRecorder->recorderState() == QMediaRecorder::RecordingState)
+ stop();
+ else
+ record();
+ }
+ event->accept();
+ break;
+ default:
+ QMainWindow::keyPressEvent(event);
+ }
+}
+
+void Camera::keyReleaseEvent(QKeyEvent *event)
+{
+ QMainWindow::keyReleaseEvent(event);
+}
+
+void Camera::updateRecordTime()
+{
+ QString str = QString("Recorded %1 sec").arg(m_mediaRecorder->duration()/1000);
+ ui->statusbar->showMessage(str);
+}
+
+void Camera::processCapturedImage(int requestId, const QImage& img)
+{
+ Q_UNUSED(requestId);
+ QImage scaledImage = img.scaled(ui->viewfinder->size(),
+ Qt::KeepAspectRatio,
+ Qt::SmoothTransformation);
+
+ ui->lastImagePreviewLabel->setPixmap(QPixmap::fromImage(scaledImage));
+
+ // Display captured image for 4 seconds.
+ displayCapturedImage();
+ QTimer::singleShot(4000, this, &Camera::displayViewfinder);
+}
+
+void Camera::configureCaptureSettings()
+{
+ if (m_doImageCapture)
+ configureImageSettings();
+ else
+ configureVideoSettings();
+}
+
+void Camera::configureVideoSettings()
+{
+ VideoSettings settingsDialog(m_mediaRecorder.data());
+ settingsDialog.setWindowFlags(settingsDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ if (settingsDialog.exec())
+ settingsDialog.applySettings();
+}
+
+void Camera::configureImageSettings()
+{
+ ImageSettings settingsDialog(m_imageCapture);
+ settingsDialog.setWindowFlags(settingsDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ if (settingsDialog.exec()) {
+ settingsDialog.applyImageSettings();
+ }
+}
+
+void Camera::record()
+{
+ m_mediaRecorder->record();
+ updateRecordTime();
+}
+
+void Camera::pause()
+{
+ m_mediaRecorder->pause();
+}
+
+void Camera::stop()
+{
+ m_mediaRecorder->stop();
+}
+
+void Camera::setMuted(bool muted)
+{
+ m_captureSession.audioInput()->setMuted(muted);
+}
+
+void Camera::takeImage()
+{
+ m_isCapturingImage = true;
+ m_imageCapture->captureToFile();
+}
+
+void Camera::displayCaptureError(int id, const QImageCapture::Error error, const QString &errorString)
+{
+ Q_UNUSED(id);
+ Q_UNUSED(error);
+ QMessageBox::warning(this, tr("Image Capture Error"), errorString);
+ m_isCapturingImage = false;
+}
+
+void Camera::startCamera()
+{
+ m_camera->start();
+}
+
+void Camera::stopCamera()
+{
+ m_camera->stop();
+}
+
+void Camera::updateCaptureMode()
+{
+ int tabIndex = ui->captureWidget->currentIndex();
+ m_doImageCapture = (tabIndex == 0);
+}
+
+void Camera::updateCameraActive(bool active)
+{
+ if (active) {
+ ui->actionStartCamera->setEnabled(false);
+ ui->actionStopCamera->setEnabled(true);
+ ui->captureWidget->setEnabled(true);
+ ui->actionSettings->setEnabled(true);
+ } else {
+ ui->actionStartCamera->setEnabled(true);
+ ui->actionStopCamera->setEnabled(false);
+ ui->captureWidget->setEnabled(false);
+ ui->actionSettings->setEnabled(false);
+ }
+}
+
+void Camera::updateRecorderState(QMediaRecorder::RecorderState state)
+{
+ switch (state) {
+ case QMediaRecorder::StoppedState:
+ ui->recordButton->setEnabled(true);
+ ui->pauseButton->setEnabled(true);
+ ui->stopButton->setEnabled(false);
+ ui->metaDataButton->setEnabled(true);
+ break;
+ case QMediaRecorder::PausedState:
+ ui->recordButton->setEnabled(true);
+ ui->pauseButton->setEnabled(false);
+ ui->stopButton->setEnabled(true);
+ ui->metaDataButton->setEnabled(false);
+ break;
+ case QMediaRecorder::RecordingState:
+ ui->recordButton->setEnabled(false);
+ ui->pauseButton->setEnabled(true);
+ ui->stopButton->setEnabled(true);
+ ui->metaDataButton->setEnabled(false);
+ break;
+ }
+}
+
+void Camera::setExposureCompensation(int index)
+{
+ m_camera->setExposureCompensation(index*0.5);
+}
+
+void Camera::displayRecorderError()
+{
+ if (m_mediaRecorder->error() != QMediaRecorder::NoError)
+ QMessageBox::warning(this, tr("Capture Error"), m_mediaRecorder->errorString());
+}
+
+void Camera::displayCameraError()
+{
+ if (m_camera->error() != QCamera::NoError)
+ QMessageBox::warning(this, tr("Camera Error"), m_camera->errorString());
+}
+
+void Camera::updateCameraDevice(QAction *action)
+{
+ setCamera(qvariant_cast<QCameraDevice>(action->data()));
+}
+
+void Camera::displayViewfinder()
+{
+ ui->stackedWidget->setCurrentIndex(0);
+}
+
+void Camera::displayCapturedImage()
+{
+ ui->stackedWidget->setCurrentIndex(1);
+}
+
+void Camera::readyForCapture(bool ready)
+{
+ ui->takeImageButton->setEnabled(ready);
+}
+
+void Camera::imageSaved(int id, const QString &fileName)
+{
+ Q_UNUSED(id);
+ ui->statusbar->showMessage(tr("Captured \"%1\"").arg(QDir::toNativeSeparators(fileName)));
+
+ m_isCapturingImage = false;
+ if (m_applicationExiting)
+ close();
+}
+
+void Camera::closeEvent(QCloseEvent *event)
+{
+ if (m_isCapturingImage) {
+ setEnabled(false);
+ m_applicationExiting = true;
+ event->ignore();
+ } else {
+ event->accept();
+ }
+}
+
+void Camera::updateCameras()
+{
+ ui->menuDevices->clear();
+ const QList<QCameraDevice> availableCameras = QMediaDevices::videoInputs();
+ for (const QCameraDevice &cameraDevice : availableCameras) {
+ QAction *videoDeviceAction = new QAction(cameraDevice.description(), videoDevicesGroup);
+ videoDeviceAction->setCheckable(true);
+ videoDeviceAction->setData(QVariant::fromValue(cameraDevice));
+ if (cameraDevice == QMediaDevices::defaultVideoInput())
+ videoDeviceAction->setChecked(true);
+
+ ui->menuDevices->addAction(videoDeviceAction);
+ }
+}
+
+void Camera::showMetaDataDialog()
+{
+ if (!m_metaDataDialog)
+ m_metaDataDialog = new MetaDataDialog(this);
+ m_metaDataDialog->setAttribute(Qt::WA_DeleteOnClose, false);
+ if (m_metaDataDialog->exec() == QDialog::Accepted)
+ saveMetaData();
+}
+
+void Camera::saveMetaData()
+{
+ QMediaMetaData data;
+ for (int i = 0; i < QMediaMetaData::NumMetaData; i++) {
+ QString val = m_metaDataDialog->m_metaDataFields[i]->text();
+ if (!val.isEmpty()) {
+ auto key = static_cast<QMediaMetaData::Key>(i);
+ if (i == QMediaMetaData::CoverArtImage) {
+ QImage coverArt(val);
+ data.insert(key, coverArt);
+ }
+ else if (i == QMediaMetaData::ThumbnailImage) {
+ QImage thumbnail(val);
+ data.insert(key, thumbnail);
+ }
+ else if (i == QMediaMetaData::Date) {
+ QDateTime date = QDateTime::fromString(val);
+ data.insert(key, date);
+ }
+ else {
+ data.insert(key, val);
+ }
+ }
+ }
+ m_mediaRecorder->setMetaData(data);
+}
+
diff --git a/examples/multimedia/camera/camera.h b/examples/multimedia/camera/camera.h
new file mode 100644
index 000000000..ae8eb5919
--- /dev/null
+++ b/examples/multimedia/camera/camera.h
@@ -0,0 +1,101 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef CAMERA_H
+#define CAMERA_H
+
+#include <QCamera>
+#include <QImageCapture>
+#include <QMediaRecorder>
+#include <QScopedPointer>
+#include <QMediaMetaData>
+#include <QMediaCaptureSession>
+#include <QMediaDevices>
+#include <QAudioInput>
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class Camera; }
+class QActionGroup;
+QT_END_NAMESPACE
+
+class MetaDataDialog;
+
+class Camera : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ Camera();
+
+public slots:
+ void saveMetaData();
+
+private slots:
+ void setCamera(const QCameraDevice &cameraDevice);
+
+ void startCamera();
+ void stopCamera();
+
+ void record();
+ void pause();
+ void stop();
+ void setMuted(bool);
+
+ void takeImage();
+ void displayCaptureError(int, QImageCapture::Error, const QString &errorString);
+
+ void configureCaptureSettings();
+ void configureVideoSettings();
+ void configureImageSettings();
+
+ void displayRecorderError();
+ void displayCameraError();
+
+ void updateCameraDevice(QAction *action);
+
+ void updateCameraActive(bool active);
+ void updateCaptureMode();
+ void updateRecorderState(QMediaRecorder::RecorderState state);
+ void setExposureCompensation(int index);
+
+ void updateRecordTime();
+
+ void processCapturedImage(int requestId, const QImage &img);
+
+ void displayViewfinder();
+ void displayCapturedImage();
+
+ void readyForCapture(bool ready);
+ void imageSaved(int id, const QString &fileName);
+
+ void updateCameras();
+
+ void showMetaDataDialog();
+
+protected:
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *event) override;
+ void closeEvent(QCloseEvent *event) override;
+
+private:
+ Ui::Camera *ui;
+
+ QActionGroup *videoDevicesGroup = nullptr;
+
+ QMediaDevices m_devices;
+ QMediaCaptureSession m_captureSession;
+ QScopedPointer<QCamera> m_camera;
+ QScopedPointer<QAudioInput> m_audioInput;
+ QImageCapture *m_imageCapture;
+ QScopedPointer<QMediaRecorder> m_mediaRecorder;
+
+ bool m_isCapturingImage = false;
+ bool m_applicationExiting = false;
+ bool m_doImageCapture = true;
+
+ MetaDataDialog *m_metaDataDialog = nullptr;
+};
+
+#endif
diff --git a/examples/multimedia/camera/camera.pro b/examples/multimedia/camera/camera.pro
new file mode 100644
index 000000000..283d84640
--- /dev/null
+++ b/examples/multimedia/camera/camera.pro
@@ -0,0 +1,40 @@
+TEMPLATE = app
+TARGET = camera
+
+QT += multimedia multimediawidgets
+
+HEADERS = \
+ camera.h \
+ imagesettings.h \
+ videosettings.h \
+ metadatadialog.h
+
+SOURCES = \
+ main.cpp \
+ camera.cpp \
+ imagesettings.cpp \
+ videosettings.cpp \
+ metadatadialog.cpp
+
+FORMS += \
+ imagesettings.ui
+
+android|ios {
+ FORMS += \
+ camera_mobile.ui \
+ videosettings_mobile.ui
+} else {
+ FORMS += \
+ camera.ui \
+ videosettings.ui
+}
+RESOURCES += camera.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/camera
+INSTALLS += target
+
+QT += widgets
+include(../../multimedia/shared/shared.pri)
+
+ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
+OTHER_FILES += android/AndroidManifest.xml
diff --git a/examples/multimedia/camera/camera.qrc b/examples/multimedia/camera/camera.qrc
new file mode 100644
index 000000000..a915eb596
--- /dev/null
+++ b/examples/multimedia/camera/camera.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/shutter.svg</file>
+</qresource>
+</RCC>
diff --git a/examples/multimedia/camera/camera.ui b/examples/multimedia/camera/camera.ui
new file mode 100644
index 000000000..560ee7fed
--- /dev/null
+++ b/examples/multimedia/camera/camera.ui
@@ -0,0 +1,488 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Camera</class>
+ <widget class="QMainWindow" name="Camera">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>668</width>
+ <height>429</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Camera</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="1" colspan="2">
+ <widget class="QTabWidget" name="captureWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Image</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>161</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0">
+ <widget class="QPushButton" name="takeImageButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Capture Photo</string>
+ </property>
+ <property name="icon">
+ <iconset resource="camera.qrc">
+ <normaloff>:/images/shutter.svg</normaloff>:/images/shutter.svg</iconset>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QSlider" name="exposureCompensation">
+ <property name="minimum">
+ <number>-4</number>
+ </property>
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="pageStep">
+ <number>2</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksAbove</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Exposure Compensation:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Video</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QPushButton" name="recordButton">
+ <property name="text">
+ <string>Record</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QPushButton" name="pauseButton">
+ <property name="text">
+ <string>Pause</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QPushButton" name="stopButton">
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>76</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="4" column="0">
+ <widget class="QPushButton" name="muteButton">
+ <property name="text">
+ <string>Mute</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QPushButton" name="metaDataButton">
+ <property name="text">
+ <string>Set metadata</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0" rowspan="2">
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </active>
+ <inactive>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </inactive>
+ <disabled>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </disabled>
+ </palette>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="viewfinderPage">
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <widget class="QVideoWidget" name="viewfinder" native="true"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="previewPage">
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lastImagePreviewLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>668</width>
+ <height>28</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <addaction name="actionStartCamera"/>
+ <addaction name="actionStopCamera"/>
+ <addaction name="separator"/>
+ <addaction name="actionSettings"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuDevices">
+ <property name="title">
+ <string>Devices</string>
+ </property>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuDevices"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionExit">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </action>
+ <action name="actionStartCamera">
+ <property name="text">
+ <string>Start Camera</string>
+ </property>
+ </action>
+ <action name="actionStopCamera">
+ <property name="text">
+ <string>Stop Camera</string>
+ </property>
+ </action>
+ <action name="actionSettings">
+ <property name="text">
+ <string>Change Settings</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QVideoWidget</class>
+ <extends>QWidget</extends>
+ <header>qvideowidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="camera.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>recordButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>record()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>149</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>61</x>
+ <y>238</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>stopButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>stop()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>225</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>140</x>
+ <y>236</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pauseButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>pause()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>187</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>234</x>
+ <y>237</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionExit</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>154</x>
+ <y>130</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>takeImageButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>takeImage()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>625</x>
+ <y>132</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>603</x>
+ <y>169</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>muteButton</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Camera</receiver>
+ <slot>setMuted(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>377</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>5</x>
+ <y>280</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>exposureCompensation</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>Camera</receiver>
+ <slot>setExposureCompensation(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>559</x>
+ <y>367</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>665</x>
+ <y>365</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionSettings</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>configureCaptureSettings()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionStartCamera</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>startCamera()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionStopCamera</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>stopCamera()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+ <slots>
+ <slot>record()</slot>
+ <slot>pause()</slot>
+ <slot>stop()</slot>
+ <slot>enablePreview(bool)</slot>
+ <slot>configureCaptureSettings()</slot>
+ <slot>takeImage()</slot>
+ <slot>startCamera()</slot>
+ <slot>toggleLock()</slot>
+ <slot>setMuted(bool)</slot>
+ <slot>stopCamera()</slot>
+ <slot>setExposureCompensation(int)</slot>
+ </slots>
+</ui>
diff --git a/examples/multimedia/camera/camera_mobile.ui b/examples/multimedia/camera/camera_mobile.ui
new file mode 100644
index 000000000..7f269b17b
--- /dev/null
+++ b/examples/multimedia/camera/camera_mobile.ui
@@ -0,0 +1,504 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Camera</class>
+ <widget class="QMainWindow" name="Camera">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>668</width>
+ <height>429</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Camera</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="1" colspan="2">
+ <widget class="QTabWidget" name="captureWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Image</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="4" column="0">
+ <widget class="QSlider" name="exposureCompensation">
+ <property name="minimum">
+ <number>-4</number>
+ </property>
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="pageStep">
+ <number>2</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksAbove</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Exposure Compensation:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QPushButton" name="takeImageButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Capture Photo</string>
+ </property>
+ <property name="icon">
+ <iconset>
+ <normaloff>:/images/shutter.svg</normaloff>:/images/shutter.svg</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Video</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QPushButton" name="recordButton">
+ <property name="text">
+ <string>Record</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pauseButton">
+ <property name="text">
+ <string>Pause</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="stopButton">
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="muteButton">
+ <property name="text">
+ <string>Mute</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="metaDataButton">
+ <property name="text">
+ <string>Set metadata</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </active>
+ <inactive>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </inactive>
+ <disabled>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </disabled>
+ </palette>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="viewfinderPage">
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <widget class="QVideoWidget" name="viewfinder" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="previewPage">
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lastImagePreviewLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>668</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <addaction name="actionStartCamera"/>
+ <addaction name="actionStopCamera"/>
+ <addaction name="separator"/>
+ <addaction name="actionSettings"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuDevices">
+ <property name="title">
+ <string>Devices</string>
+ </property>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuDevices"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionExit">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </action>
+ <action name="actionStartCamera">
+ <property name="text">
+ <string>Start Camera</string>
+ </property>
+ </action>
+ <action name="actionStopCamera">
+ <property name="text">
+ <string>Stop Camera</string>
+ </property>
+ </action>
+ <action name="actionSettings">
+ <property name="text">
+ <string>Change Settings</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QVideoWidget</class>
+ <extends>QWidget</extends>
+ <header>qvideowidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>recordButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>record()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>149</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>61</x>
+ <y>238</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>stopButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>stop()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>225</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>140</x>
+ <y>236</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pauseButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>pause()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>187</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>234</x>
+ <y>237</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionExit</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>154</x>
+ <y>130</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>takeImageButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>takeImage()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>625</x>
+ <y>132</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>603</x>
+ <y>169</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>muteButton</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Camera</receiver>
+ <slot>setMuted(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>377</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>5</x>
+ <y>280</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>exposureCompensation</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>Camera</receiver>
+ <slot>setExposureCompensation(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>559</x>
+ <y>367</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>665</x>
+ <y>365</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionSettings</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>configureCaptureSettings()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionStartCamera</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>startCamera()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionStopCamera</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>stopCamera()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+ <slots>
+ <slot>record()</slot>
+ <slot>pause()</slot>
+ <slot>stop()</slot>
+ <slot>enablePreview(bool)</slot>
+ <slot>configureCaptureSettings()</slot>
+ <slot>takeImage()</slot>
+ <slot>startCamera()</slot>
+ <slot>toggleLock()</slot>
+ <slot>setMuted(bool)</slot>
+ <slot>stopCamera()</slot>
+ <slot>setExposureCompensation(int)</slot>
+ </slots>
+</ui>
diff --git a/examples/multimedia/camera/doc/images/camera-example.png b/examples/multimedia/camera/doc/images/camera-example.png
new file mode 100644
index 000000000..12e1b5728
--- /dev/null
+++ b/examples/multimedia/camera/doc/images/camera-example.png
Binary files differ
diff --git a/examples/multimedia/camera/doc/src/camera.qdoc b/examples/multimedia/camera/doc/src/camera.qdoc
new file mode 100644
index 000000000..7a3b88d1b
--- /dev/null
+++ b/examples/multimedia/camera/doc/src/camera.qdoc
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+/*!
+
+\example camera
+\title Camera Example
+\ingroup multimedia_examples
+\ingroup video_examples
+\ingroup camera_examples
+\meta {tag} {widgets}
+\brief Shows how to capture a still image or record video.
+or video.
+
+The Camera Example demonstrates how you can use \l{Qt Multimedia} to implement
+some basic Camera functionality to take still images and record video clips
+with audio.
+
+\include examples-run.qdocinc
+
+The example implements a \c Camera class that acts as our camera interface. It
+has a user interface, control functions, setting values and a means of defining
+the location where the image or video clip is to be saved. It will also store
+the image and video settings.
+
+The Camera class uses:
+\list
+ \li An instance of \l {QCamera}, the API class interface to the hardware.
+ \li An instance of \l {QImageCapture} to take still images.
+ \li An instance of \l {QMediaRecorder} to record video. It also contains
+ the user interface object.
+\endlist
+
+The Camera constructor does some basic initialization:
+\list
+ \li The user interface is initialized.
+ \li UI signals are connected to slots that react to the triggering event.
+\endlist
+However, most of the work is done when the \e{setCamera()} function is called,
+passing in a \l QCameraDevice.
+
+\e{setCamera()} sets up various connections between the user interface and the
+functionality of the Camera class using signals and slots. It also instantiates
+and initializes the \l {QCamera}, \l {QImageCapture}, and \l {QMediaRecorder}
+objects mentioned above. The still and video recording visual tabs are enabled
+and finally the \l {QCamera::start}{start()} function of the \l{QCamera}
+object is called.
+
+Now that the camera is ready for user commands it waits for a suitable event.
+Such an event can be a key press of either the \l {Qt::Key_CameraFocus} or
+\l {Qt::Key_Camera} buttons on the application window. Camera focus will
+simply display the preview and lock the camera settings. \c Key_Camera will
+either call \e{takeImage()} if doing an image capture, or call
+\c record() or \c stop() (if already recording) on the QMediaRecorder instance
+when recording video.
+
+\image camera-example.png
+
+*/
diff --git a/examples/multimedia/camera/images/shutter.svg b/examples/multimedia/camera/images/shutter.svg
new file mode 100644
index 000000000..18493361d
--- /dev/null
+++ b/examples/multimedia/camera/images/shutter.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 23.3 19.4" style="enable-background:new 0 0 23.3 19.4;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:none;}
+</style>
+<g>
+ <path class="st0" d="M6.2,4.8H2.4c-0.2,0-0.1-0.1-0.1,0.1V17c0,0.2-0.1,0.8,0.1,0.8h3.9V4.8z"/>
+ <circle class="st0" cx="14" cy="11" r="4.5"/>
+ <path class="st0" d="M20.9,4.8h-1.8c-0.3,0-0.6-0.4-0.8-0.6l-1.7-2.4h-5.3L9.7,4.2C9.5,4.4,9.2,4.8,8.9,4.8H7.2v13h13.7
+ c0.2,0,0.3-0.6,0.3-0.8V4.9C21.2,4.7,21.1,4.8,20.9,4.8z M14,16.4c-3,0-5.5-2.4-5.5-5.5c0-3,2.4-5.5,5.5-5.5c3,0,5.5,2.4,5.5,5.5
+ C19.5,14,17,16.4,14,16.4z"/>
+ <path d="M14,5.5C11,5.5,8.6,8,8.6,11c0,3,2.4,5.5,5.5,5.5c3,0,5.5-2.4,5.5-5.5C19.5,8,17,5.5,14,5.5z M14,15.4
+ c-2.5,0-4.5-2-4.5-4.5c0-2.5,2-4.5,4.5-4.5c2.5,0,4.5,2,4.5,4.5C18.5,13.4,16.5,15.4,14,15.4z"/>
+ <path d="M20.9,2.8h-1.3l-1.7-2.4c-0.2-0.2-0.5-0.6-0.8-0.6h-6.3c-0.3,0-0.6,0.4-0.8,0.6L8.4,2.8h-6c-1.3,0-2.1,0.8-2.1,2.1V17
+ c0,1.3,0.8,2.8,2.1,2.8h18.5c1.3,0,2.3-1.5,2.3-2.8V4.9C23.2,3.6,22.2,2.8,20.9,2.8z M2.2,17V4.9c0-0.2-0.1-0.1,0.1-0.1h3.9v13H2.4
+ C2.2,17.8,2.2,17.2,2.2,17z M21.2,17c0,0.2-0.1,0.8-0.3,0.8H7.2v-13h1.7c0.3,0,0.6-0.4,0.8-0.6l1.7-2.4h5.3l1.7,2.4
+ c0.2,0.2,0.5,0.6,0.8,0.6h1.8c0.2,0,0.3-0.1,0.3,0.1V17z"/>
+</g>
+</svg>
diff --git a/examples/multimedia/camera/imagesettings.cpp b/examples/multimedia/camera/imagesettings.cpp
new file mode 100644
index 000000000..a107cc62d
--- /dev/null
+++ b/examples/multimedia/camera/imagesettings.cpp
@@ -0,0 +1,83 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "imagesettings.h"
+#include "ui_imagesettings.h"
+
+#include <QComboBox>
+#include <QDebug>
+#include <QImageCapture>
+#include <QCamera>
+#include <QMediaCaptureSession>
+
+ImageSettings::ImageSettings(QImageCapture *imageCapture, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ImageSettingsUi),
+ imagecapture(imageCapture)
+{
+ ui->setupUi(this);
+
+ //image codecs
+ ui->imageCodecBox->addItem(tr("Default image format"), QVariant(QString()));
+ const auto supportedImageFormats = QImageCapture::supportedFormats();
+ for (const auto &f : supportedImageFormats) {
+ QString description = QImageCapture::fileFormatDescription(f);
+ ui->imageCodecBox->addItem(QImageCapture::fileFormatName(f) + ": " + description, QVariant::fromValue(f));
+ }
+
+ ui->imageQualitySlider->setRange(0, int(QImageCapture::VeryHighQuality));
+
+ ui->imageResolutionBox->addItem(tr("Default Resolution"));
+ const QList<QSize> supportedResolutions = imagecapture->captureSession()->camera()->cameraDevice().photoResolutions();
+ for (const QSize &resolution : supportedResolutions) {
+ ui->imageResolutionBox->addItem(QString("%1x%2").arg(resolution.width()).arg(resolution.height()),
+ QVariant(resolution));
+ }
+
+ selectComboBoxItem(ui->imageCodecBox, QVariant::fromValue(imagecapture->fileFormat()));
+ selectComboBoxItem(ui->imageResolutionBox, QVariant(imagecapture->resolution()));
+ ui->imageQualitySlider->setValue(imagecapture->quality());
+}
+
+ImageSettings::~ImageSettings()
+{
+ delete ui;
+}
+
+void ImageSettings::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ImageSettings::applyImageSettings() const
+{
+ imagecapture->setFileFormat(boxValue(ui->imageCodecBox).value<QImageCapture::FileFormat>());
+ imagecapture->setQuality(QImageCapture::Quality(ui->imageQualitySlider->value()));
+ imagecapture->setResolution(boxValue(ui->imageResolutionBox).toSize());
+}
+
+QVariant ImageSettings::boxValue(const QComboBox *box) const
+{
+ int idx = box->currentIndex();
+ if (idx == -1)
+ return QVariant();
+
+ return box->itemData(idx);
+}
+
+void ImageSettings::selectComboBoxItem(QComboBox *box, const QVariant &value)
+{
+ for (int i = 0; i < box->count(); ++i) {
+ if (box->itemData(i) == value) {
+ box->setCurrentIndex(i);
+ break;
+ }
+ }
+}
diff --git a/examples/multimedia/camera/imagesettings.h b/examples/multimedia/camera/imagesettings.h
new file mode 100644
index 000000000..13bd6dc4a
--- /dev/null
+++ b/examples/multimedia/camera/imagesettings.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef IMAGESETTINGS_H
+#define IMAGESETTINGS_H
+
+#include <QDialog>
+
+QT_BEGIN_NAMESPACE
+class QComboBox;
+class QImageCapture;
+namespace Ui { class ImageSettingsUi; }
+QT_END_NAMESPACE
+
+class ImageSettings : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit ImageSettings(QImageCapture *imageCapture, QWidget *parent = nullptr);
+ ~ImageSettings();
+
+ void applyImageSettings() const;
+
+ QString format() const;
+ void setFormat(const QString &format);
+
+protected:
+ void changeEvent(QEvent *e) override;
+
+private:
+ QVariant boxValue(const QComboBox *box) const;
+ void selectComboBoxItem(QComboBox *box, const QVariant &value);
+
+ Ui::ImageSettingsUi *ui;
+ QImageCapture *imagecapture;
+};
+
+#endif // IMAGESETTINGS_H
diff --git a/examples/multimedia/camera/imagesettings.ui b/examples/multimedia/camera/imagesettings.ui
new file mode 100644
index 000000000..8c59ca01d
--- /dev/null
+++ b/examples/multimedia/camera/imagesettings.ui
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ImageSettingsUi</class>
+ <widget class="QDialog" name="ImageSettingsUi">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>332</width>
+ <height>270</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Image Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Image</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Resolution:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QComboBox" name="imageResolutionBox"/>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Image Format:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QComboBox" name="imageCodecBox"/>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Quality:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QSlider" name="imageQualitySlider">
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>14</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ImageSettingsUi</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>322</x>
+ <y>272</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>44</x>
+ <y>230</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ImageSettingsUi</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>405</x>
+ <y>262</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>364</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/multimedia/camera/ios/Info.plist.in b/examples/multimedia/camera/ios/Info.plist.in
new file mode 100644
index 000000000..6a6b8db11
--- /dev/null
+++ b/examples/multimedia/camera/ios/Info.plist.in
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+</dict>
+</plist>
+
diff --git a/examples/multimedia/camera/macos/Info.plist.in b/examples/multimedia/camera/macos/Info.plist.in
new file mode 100644
index 000000000..ae2d945f1
--- /dev/null
+++ b/examples/multimedia/camera/macos/Info.plist.in
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/examples/multimedia/camera/main.cpp b/examples/multimedia/camera/main.cpp
new file mode 100644
index 000000000..50b411e4e
--- /dev/null
+++ b/examples/multimedia/camera/main.cpp
@@ -0,0 +1,16 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "camera.h"
+
+#include <QtWidgets>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ Camera camera;
+ camera.show();
+
+ return app.exec();
+};
diff --git a/examples/multimedia/camera/metadatadialog.cpp b/examples/multimedia/camera/metadatadialog.cpp
new file mode 100644
index 000000000..096217014
--- /dev/null
+++ b/examples/multimedia/camera/metadatadialog.cpp
@@ -0,0 +1,80 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "metadatadialog.h"
+#include "camera.h"
+
+#include <QtWidgets>
+#include <QFormLayout>
+#include <QMediaMetaData>
+
+MetaDataDialog::MetaDataDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ QFormLayout *metaDataLayout = new QFormLayout;
+ for (int key = 0; key < QMediaMetaData::NumMetaData; key++) {
+ QString label = QMediaMetaData::metaDataKeyToString(static_cast<QMediaMetaData::Key>(key));
+ m_metaDataFields[key] = new QLineEdit;
+ if (key == QMediaMetaData::ThumbnailImage) {
+ QPushButton *openThumbnail = new QPushButton(tr("Open"));
+ connect(openThumbnail, &QPushButton::clicked, this, &MetaDataDialog::openThumbnailImage);
+ QHBoxLayout *layout = new QHBoxLayout;
+ layout->addWidget(m_metaDataFields[key]);
+ layout->addWidget(openThumbnail);
+ metaDataLayout->addRow(label, layout);
+ }
+ else if (key == QMediaMetaData::CoverArtImage) {
+ QPushButton *openCoverArt = new QPushButton(tr("Open"));
+ connect(openCoverArt, &QPushButton::clicked, this, &MetaDataDialog::openCoverArtImage);
+ QHBoxLayout *layout = new QHBoxLayout;
+ layout->addWidget(m_metaDataFields[key]);
+ layout->addWidget(openCoverArt);
+ metaDataLayout->addRow(label, layout);
+ }
+ else {
+ if (key == QMediaMetaData::Title)
+ m_metaDataFields[key]->setText(tr("Qt Camera Example"));
+ else if (key == QMediaMetaData::Author)
+ m_metaDataFields[key]->setText(tr("The Qt Company"));
+ else if (key == QMediaMetaData::Date)
+ m_metaDataFields[key]->setText(QDateTime::currentDateTime().toString());
+ else if (key == QMediaMetaData::Date)
+ m_metaDataFields[key]->setText(QDate::currentDate().toString());
+ metaDataLayout->addRow(label, m_metaDataFields[key]);
+ }
+ }
+
+ QWidget *viewport = new QWidget;
+ viewport->setLayout(metaDataLayout);
+ QScrollArea *scrollArea = new QScrollArea;
+ scrollArea->setWidget(viewport);
+ QVBoxLayout *dialogLayout = new QVBoxLayout();
+ this->setLayout(dialogLayout);
+ this->layout()->addWidget(scrollArea);
+
+ auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
+ | QDialogButtonBox::Cancel);
+ this->layout()->addWidget(buttonBox);
+
+ this->setWindowTitle(tr("Set Metadata"));
+ this->resize(400, 300);
+
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &MetaDataDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &MetaDataDialog::reject);
+}
+
+void MetaDataDialog::openThumbnailImage()
+{
+ QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Open Image"), QDir::currentPath(), tr("Image Files (*.png *.jpg *.bmp)"));
+ if (!fileName.isEmpty())
+ m_metaDataFields[QMediaMetaData::ThumbnailImage]->setText(fileName);
+}
+
+void MetaDataDialog::openCoverArtImage()
+{
+ QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Open Image"), QDir::currentPath(), tr("Image Files (*.png *.jpg *.bmp)"));
+ if (!fileName.isEmpty())
+ m_metaDataFields[QMediaMetaData::CoverArtImage]->setText(fileName);
+}
diff --git a/examples/multimedia/camera/metadatadialog.h b/examples/multimedia/camera/metadatadialog.h
new file mode 100644
index 000000000..5bb5a4b0b
--- /dev/null
+++ b/examples/multimedia/camera/metadatadialog.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef DIALOG_H
+#define DIALOG_H
+
+#include <QDialog>
+#include <QMediaMetaData>
+
+QT_BEGIN_NAMESPACE
+class QLabel;
+class QLineEdit;
+QT_END_NAMESPACE
+
+//! [0]
+class MetaDataDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit MetaDataDialog(QWidget *parent = nullptr);
+
+ QLineEdit *m_metaDataFields[QMediaMetaData::NumMetaData] = {};
+
+private slots:
+ void openThumbnailImage();
+ void openCoverArtImage();
+};
+//! [0]
+
+#endif
diff --git a/examples/multimedia/camera/videosettings.cpp b/examples/multimedia/camera/videosettings.cpp
new file mode 100644
index 000000000..b2c62bafc
--- /dev/null
+++ b/examples/multimedia/camera/videosettings.cpp
@@ -0,0 +1,201 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "videosettings.h"
+#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
+#include "ui_videosettings_mobile.h"
+#else
+#include "ui_videosettings.h"
+#endif
+
+#include <QComboBox>
+#include <QSpinBox>
+#include <QDebug>
+#include <QMediaRecorder>
+#include <QMediaFormat>
+#include <QAudioDevice>
+#include <QMediaCaptureSession>
+#include <QCameraDevice>
+#include <QCamera>
+#include <QAudioInput>
+
+QString toFormattedString(const QCameraFormat &cameraFormat)
+{
+ QString string;
+ const auto &separator = QStringLiteral(" ");
+
+ string.append(QVideoFrameFormat::pixelFormatToString(cameraFormat.pixelFormat()));
+ string.append(separator);
+
+ string.append(QString::number(cameraFormat.resolution().width()));
+ string.append(QStringLiteral("x"));
+ string.append(QString::number(cameraFormat.resolution().height()));
+ string.append(separator);
+
+ string.append(QString::number(cameraFormat.minFrameRate()));
+ string.append(QStringLiteral("-"));
+ string.append(QString::number(cameraFormat.maxFrameRate()));
+ string.append(QStringLiteral("FPS"));
+
+ return string;
+}
+
+VideoSettings::VideoSettings(QMediaRecorder *mediaRecorder, QWidget *parent)
+ : QDialog(parent),
+ ui(new Ui::VideoSettingsUi),
+ mediaRecorder(mediaRecorder)
+{
+ ui->setupUi(this);
+
+ //sample rate:
+ auto audioDevice = mediaRecorder->captureSession()->audioInput()->device();
+ ui->audioSampleRateBox->setRange(audioDevice.minimumSampleRate(),
+ audioDevice.maximumSampleRate());
+
+ // camera format
+ ui->videoFormatBox->addItem(tr("Default camera format"));
+
+ const QList<QCameraFormat> videoFormats =
+ mediaRecorder->captureSession()->camera()->cameraDevice().videoFormats();
+
+ for (const QCameraFormat &format : videoFormats) {
+ ui->videoFormatBox->addItem(toFormattedString(format), QVariant::fromValue(format));
+ }
+
+ connect(ui->videoFormatBox, &QComboBox::currentIndexChanged, [this](int /*index*/) {
+ const auto &cameraFormat = boxValue(ui->videoFormatBox).value<QCameraFormat>();
+ ui->fpsSlider->setRange(cameraFormat.minFrameRate(), cameraFormat.maxFrameRate());
+ ui->fpsSpinBox->setRange(cameraFormat.minFrameRate(), cameraFormat.maxFrameRate());
+ });
+
+ auto currentCameraFormat = mediaRecorder->captureSession()->camera()->cameraFormat();
+ ui->fpsSlider->setRange(currentCameraFormat.minFrameRate(), currentCameraFormat.maxFrameRate());
+ ui->fpsSpinBox->setRange(currentCameraFormat.minFrameRate(),
+ currentCameraFormat.maxFrameRate());
+
+ connect(ui->fpsSlider, &QSlider::valueChanged, ui->fpsSpinBox, &QSpinBox::setValue);
+ connect(ui->fpsSpinBox, &QSpinBox::valueChanged, ui->fpsSlider, &QSlider::setValue);
+
+ updateFormatsAndCodecs();
+ connect(ui->audioCodecBox, &QComboBox::currentIndexChanged, this, &VideoSettings::updateFormatsAndCodecs);
+ connect(ui->videoCodecBox, &QComboBox::currentIndexChanged, this, &VideoSettings::updateFormatsAndCodecs);
+ connect(ui->containerFormatBox, &QComboBox::currentIndexChanged, this, &VideoSettings::updateFormatsAndCodecs);
+
+ ui->qualitySlider->setRange(0, int(QMediaRecorder::VeryHighQuality));
+
+ QMediaFormat format = mediaRecorder->mediaFormat();
+ selectComboBoxItem(ui->containerFormatBox, QVariant::fromValue(format.fileFormat()));
+ selectComboBoxItem(ui->audioCodecBox, QVariant::fromValue(format.audioCodec()));
+ selectComboBoxItem(ui->videoCodecBox, QVariant::fromValue(format.videoCodec()));
+
+ ui->qualitySlider->setValue(mediaRecorder->quality());
+ ui->audioSampleRateBox->setValue(mediaRecorder->audioSampleRate());
+ selectComboBoxItem(
+ ui->videoFormatBox,
+ QVariant::fromValue(mediaRecorder->captureSession()->camera()->cameraFormat()));
+
+ ui->fpsSlider->setValue(mediaRecorder->videoFrameRate());
+ ui->fpsSpinBox->setValue(mediaRecorder->videoFrameRate());
+}
+
+VideoSettings::~VideoSettings()
+{
+ delete ui;
+}
+
+void VideoSettings::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void VideoSettings::applySettings()
+{
+ QMediaFormat format;
+ format.setFileFormat(boxValue(ui->containerFormatBox).value<QMediaFormat::FileFormat>());
+ format.setAudioCodec(boxValue(ui->audioCodecBox).value<QMediaFormat::AudioCodec>());
+ format.setVideoCodec(boxValue(ui->videoCodecBox).value<QMediaFormat::VideoCodec>());
+
+ mediaRecorder->setMediaFormat(format);
+ mediaRecorder->setQuality(QMediaRecorder::Quality(ui->qualitySlider->value()));
+ mediaRecorder->setAudioSampleRate(ui->audioSampleRateBox->value());
+
+ const auto &cameraFormat = boxValue(ui->videoFormatBox).value<QCameraFormat>();
+ mediaRecorder->setVideoResolution(cameraFormat.resolution());
+ mediaRecorder->setVideoFrameRate(ui->fpsSlider->value());
+
+ mediaRecorder->captureSession()->camera()->setCameraFormat(cameraFormat);
+}
+
+void VideoSettings::updateFormatsAndCodecs()
+{
+ if (m_updatingFormats)
+ return;
+ m_updatingFormats = true;
+
+ QMediaFormat format;
+ if (ui->containerFormatBox->count())
+ format.setFileFormat(boxValue(ui->containerFormatBox).value<QMediaFormat::FileFormat>());
+ if (ui->audioCodecBox->count())
+ format.setAudioCodec(boxValue(ui->audioCodecBox).value<QMediaFormat::AudioCodec>());
+ if (ui->videoCodecBox->count())
+ format.setVideoCodec(boxValue(ui->videoCodecBox).value<QMediaFormat::VideoCodec>());
+
+ int currentIndex = 0;
+ ui->audioCodecBox->clear();
+ ui->audioCodecBox->addItem(tr("Default audio codec"), QVariant::fromValue(QMediaFormat::AudioCodec::Unspecified));
+ for (auto codec : format.supportedAudioCodecs(QMediaFormat::Encode)) {
+ if (codec == format.audioCodec())
+ currentIndex = ui->audioCodecBox->count();
+ ui->audioCodecBox->addItem(QMediaFormat::audioCodecDescription(codec), QVariant::fromValue(codec));
+ }
+ ui->audioCodecBox->setCurrentIndex(currentIndex);
+
+ currentIndex = 0;
+ ui->videoCodecBox->clear();
+ ui->videoCodecBox->addItem(tr("Default video codec"), QVariant::fromValue(QMediaFormat::VideoCodec::Unspecified));
+ for (auto codec : format.supportedVideoCodecs(QMediaFormat::Encode)) {
+ if (codec == format.videoCodec())
+ currentIndex = ui->videoCodecBox->count();
+ ui->videoCodecBox->addItem(QMediaFormat::videoCodecDescription(codec), QVariant::fromValue(codec));
+ }
+ ui->videoCodecBox->setCurrentIndex(currentIndex);
+
+ currentIndex = 0;
+ ui->containerFormatBox->clear();
+ ui->containerFormatBox->addItem(tr("Default file format"), QVariant::fromValue(QMediaFormat::UnspecifiedFormat));
+ for (auto container : format.supportedFileFormats(QMediaFormat::Encode)) {
+ if (container == format.fileFormat())
+ currentIndex = ui->containerFormatBox->count();
+ ui->containerFormatBox->addItem(QMediaFormat::fileFormatDescription(container), QVariant::fromValue(container));
+ }
+ ui->containerFormatBox->setCurrentIndex(currentIndex);
+
+ m_updatingFormats = false;
+
+}
+
+QVariant VideoSettings::boxValue(const QComboBox *box) const
+{
+ int idx = box->currentIndex();
+ if (idx == -1)
+ return QVariant();
+
+ return box->itemData(idx);
+}
+
+void VideoSettings::selectComboBoxItem(QComboBox *box, const QVariant &value)
+{
+ for (int i = 0; i < box->count(); ++i) {
+ if (box->itemData(i) == value) {
+ box->setCurrentIndex(i);
+ break;
+ }
+ }
+}
diff --git a/examples/multimedia/camera/videosettings.h b/examples/multimedia/camera/videosettings.h
new file mode 100644
index 000000000..2f356d90f
--- /dev/null
+++ b/examples/multimedia/camera/videosettings.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef VIDEOSETTINGS_H
+#define VIDEOSETTINGS_H
+
+#include <QDialog>
+
+QT_BEGIN_NAMESPACE
+class QComboBox;
+class QMediaRecorder;
+namespace Ui { class VideoSettingsUi; }
+QT_END_NAMESPACE
+
+class VideoSettings : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit VideoSettings(QMediaRecorder *mediaRecorder, QWidget *parent = nullptr);
+ ~VideoSettings();
+
+ void applySettings();
+ void updateFormatsAndCodecs();
+
+protected:
+ void changeEvent(QEvent *e) override;
+
+private:
+ QVariant boxValue(const QComboBox*) const;
+ void selectComboBoxItem(QComboBox *box, const QVariant &value);
+
+ Ui::VideoSettingsUi *ui;
+ QMediaRecorder *mediaRecorder;
+ bool m_updatingFormats = false;
+};
+
+#endif // VIDEOSETTINGS_H
diff --git a/examples/multimedia/camera/videosettings.ui b/examples/multimedia/camera/videosettings.ui
new file mode 100644
index 000000000..3c1f71f11
--- /dev/null
+++ b/examples/multimedia/camera/videosettings.ui
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VideoSettingsUi</class>
+ <widget class="QDialog" name="VideoSettingsUi">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>686</width>
+ <height>499</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Video Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="4" column="1">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Video</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Camera Format</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="2">
+ <widget class="QComboBox" name="videoCodecBox"/>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Framerate:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="2">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Video Codec:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QComboBox" name="videoFormatBox"/>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSpinBox" name="fpsSpinBox"/>
+ </item>
+ <item>
+ <widget class="QSlider" name="fpsSlider">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Audio</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Audio Codec:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="audioCodecBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Sample Rate:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="audioSampleRateBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Quality:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="qualitySlider">
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>File Format:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="containerFormatBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VideoSettingsUi</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>322</x>
+ <y>272</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>44</x>
+ <y>230</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VideoSettingsUi</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>405</x>
+ <y>262</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>364</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/multimedia/camera/videosettings_mobile.ui b/examples/multimedia/camera/videosettings_mobile.ui
new file mode 100644
index 000000000..6584f07f9
--- /dev/null
+++ b/examples/multimedia/camera/videosettings_mobile.ui
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VideoSettingsUi</class>
+ <widget class="QDialog" name="VideoSettingsUi">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>329</width>
+ <height>591</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Video Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Audio</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Audio Codec:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="audioCodecBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Sample Rate:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="audioSampleRateBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Quality:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="qualitySlider">
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>File Format:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="containerFormatBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Video</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Frames per second:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0" colspan="2">
+ <widget class="QComboBox" name="videoCodecBox"/>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Camera Format:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="2">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Video Codec:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QComboBox" name="videoFormatBox"/>
+ </item>
+ <item row="7" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSpinBox" name="fpsSpinBox">
+ <property name="minimum">
+ <number>8</number>
+ </property>
+ <property name="maximum">
+ <number>30</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="fpsSlider">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VideoSettingsUi</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>322</x>
+ <y>272</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>44</x>
+ <y>230</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VideoSettingsUi</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>405</x>
+ <y>262</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>364</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/multimedia/multimedia.pro b/examples/multimedia/multimedia.pro
index 1f737a76c..cf631bb2c 100644
--- a/examples/multimedia/multimedia.pro
+++ b/examples/multimedia/multimedia.pro
@@ -8,10 +8,14 @@ SUBDIRS += \
# These examples all need widgets for now (using creator templates that use widgets)
qtHaveModule(widgets) {
SUBDIRS += \
- spectrum \
- audiorecorder \
audiodevices \
- audiooutput
+ audiooutput \
+ audiorecorder \
+ camera \
+ player \
+ spectrum \
+ videographicsitem \
+ videowidget
}
qtHaveModule(quick) {
diff --git a/examples/multimedia/player/CMakeLists.txt b/examples/multimedia/player/CMakeLists.txt
new file mode 100644
index 000000000..bd6631899
--- /dev/null
+++ b/examples/multimedia/player/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(player LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/player")
+
+find_package(Qt6 REQUIRED COMPONENTS MultimediaWidgets Network)
+
+qt_add_executable(player
+ main.cpp
+ player.cpp player.h
+ playercontrols.cpp playercontrols.h
+ playlistmodel.cpp playlistmodel.h
+ videowidget.cpp videowidget.h
+ qmediaplaylist.cpp qmediaplaylist.h qmediaplaylist_p.h
+ qplaylistfileparser.cpp qplaylistfileparser_p.h
+)
+
+set_target_properties(player PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(player PUBLIC
+ Qt::MultimediaWidgets
+ Qt::Network
+)
+
+install(TARGETS player
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/multimedia/player/doc/images/mediaplayerex.jpg b/examples/multimedia/player/doc/images/mediaplayerex.jpg
new file mode 100644
index 000000000..e875bd134
--- /dev/null
+++ b/examples/multimedia/player/doc/images/mediaplayerex.jpg
Binary files differ
diff --git a/examples/multimedia/player/doc/src/player.qdoc b/examples/multimedia/player/doc/src/player.qdoc
new file mode 100644
index 000000000..d63b99ae3
--- /dev/null
+++ b/examples/multimedia/player/doc/src/player.qdoc
@@ -0,0 +1,48 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example player
+ \title Media Player Example
+ \ingroup multimedia_examples
+ \ingroup video_examples
+ \brief Playing audio and video.
+ \meta {tag} {widgets}
+
+ \image mediaplayerex.jpg
+
+ \e{Media Player} demonstrates a simple multimedia player that can play
+ audio and or video files using various codecs.
+
+ \include examples-run.qdocinc
+
+ The example uses a QMediaPlayer object passed into a QVideoWidget to
+ control the video output. To give the application playlist capability
+ we also use a QPlayList object.
+
+ To activate the various functions such as play and stop on the dialog,
+ the button clicked events emit the play() and stop() signals, which
+ are connected to the play() and stop() slots of QMediaPlayer.
+
+ \code
+ connect(controls, SIGNAL(play()), player, SLOT(play()));
+ connect(controls, SIGNAL(pause()), player, SLOT(pause()));
+ connect(controls, SIGNAL(stop()), player, SLOT(stop()));
+ \endcode
+
+ We can get the volume (and set our user interface representation)
+
+ \code
+ controls->setVolume(player->volume());
+ \endcode
+
+ and we can make widget 'volume' changes change the volume
+
+ \code
+ connect(controls, SIGNAL(changeVolume(int)), player, SLOT(setVolume(int)));
+ \endcode
+
+ The example also allows us to change video properties by means
+ of the QVideoWidget object. We can go to Full Screen mode with a single
+ button click, and back again.
+*/
diff --git a/examples/multimedia/player/main.cpp b/examples/multimedia/player/main.cpp
new file mode 100644
index 000000000..befe1d561
--- /dev/null
+++ b/examples/multimedia/player/main.cpp
@@ -0,0 +1,37 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "player.h"
+
+#include <QApplication>
+#include <QCommandLineParser>
+#include <QCommandLineOption>
+#include <QDir>
+#include <QUrl>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QCoreApplication::setApplicationName("Player Example");
+ QCoreApplication::setOrganizationName("QtProject");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Qt MultiMedia Player Example");
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument("url", "The URL(s) to open.");
+ parser.process(app);
+
+ Player player;
+
+ if (!parser.positionalArguments().isEmpty() && player.isPlayerAvailable()) {
+ QList<QUrl> urls;
+ for (auto &a: parser.positionalArguments())
+ urls.append(QUrl::fromUserInput(a, QDir::currentPath()));
+ player.addToPlaylist(urls);
+ }
+
+ player.show();
+ return app.exec();
+}
diff --git a/examples/multimedia/player/player.cpp b/examples/multimedia/player/player.cpp
new file mode 100644
index 000000000..22146d7e9
--- /dev/null
+++ b/examples/multimedia/player/player.cpp
@@ -0,0 +1,506 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "player.h"
+
+#include "playercontrols.h"
+#include "playlistmodel.h"
+#include "qmediaplaylist.h"
+#include "videowidget.h"
+
+#include <QMediaMetaData>
+#include <QMediaDevices>
+#include <QAudioDevice>
+#include <QAudioOutput>
+#include <QMediaFormat>
+#include <QtWidgets>
+
+Player::Player(QWidget *parent)
+ : QWidget(parent)
+{
+//! [create-objs]
+ m_player = new QMediaPlayer(this);
+ m_audioOutput = new QAudioOutput(this);
+ m_player->setAudioOutput(m_audioOutput);
+//! [create-objs]
+ connect(m_player, &QMediaPlayer::durationChanged, this, &Player::durationChanged);
+ connect(m_player, &QMediaPlayer::positionChanged, this, &Player::positionChanged);
+ connect(m_player, QOverload<>::of(&QMediaPlayer::metaDataChanged), this, &Player::metaDataChanged);
+ connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &Player::statusChanged);
+ connect(m_player, &QMediaPlayer::bufferProgressChanged, this, &Player::bufferingProgress);
+ connect(m_player, &QMediaPlayer::hasVideoChanged, this, &Player::videoAvailableChanged);
+ connect(m_player, &QMediaPlayer::errorChanged, this, &Player::displayErrorMessage);
+ connect(m_player, &QMediaPlayer::tracksChanged, this, &Player::tracksChanged);
+
+//! [2]
+ m_videoWidget = new VideoWidget(this);
+ m_videoWidget->resize(1280, 720);
+ m_player->setVideoOutput(m_videoWidget);
+
+ m_playlistModel = new PlaylistModel(this);
+ m_playlist = m_playlistModel->playlist();
+//! [2]
+ connect(m_playlist, &QMediaPlaylist::currentIndexChanged, this, &Player::playlistPositionChanged);
+
+ // player layout
+ QBoxLayout *layout = new QVBoxLayout(this);
+
+ // display
+ QBoxLayout *displayLayout = new QHBoxLayout;
+ displayLayout->addWidget(m_videoWidget, 2);
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ m_playlistView = new QListView();
+ m_playlistView->setModel(m_playlistModel);
+ m_playlistView->setCurrentIndex(m_playlistModel->index(m_playlist->currentIndex(), 0));
+ connect(m_playlistView, &QAbstractItemView::activated, this, &Player::jump);
+ displayLayout->addWidget(m_playlistView);
+#endif
+ layout->addLayout(displayLayout);
+
+ // duration slider and label
+ QHBoxLayout *hLayout = new QHBoxLayout;
+
+ m_slider = new QSlider(Qt::Horizontal, this);
+ m_slider->setRange(0, m_player->duration());
+ connect(m_slider, &QSlider::sliderMoved, this, &Player::seek);
+ hLayout->addWidget(m_slider);
+
+ m_labelDuration = new QLabel();
+ m_labelDuration->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ hLayout->addWidget(m_labelDuration);
+ layout->addLayout(hLayout);
+
+ // controls
+ QBoxLayout *controlLayout = new QHBoxLayout;
+ controlLayout->setContentsMargins(0, 0, 0, 0);
+
+ QPushButton *openButton = new QPushButton(tr("Open"), this);
+ connect(openButton, &QPushButton::clicked, this, &Player::open);
+ controlLayout->addWidget(openButton);
+ controlLayout->addStretch(1);
+
+ PlayerControls *controls = new PlayerControls();
+ controls->setState(m_player->playbackState());
+ controls->setVolume(m_audioOutput->volume());
+ controls->setMuted(controls->isMuted());
+
+ connect(controls, &PlayerControls::play, m_player, &QMediaPlayer::play);
+ connect(controls, &PlayerControls::pause, m_player, &QMediaPlayer::pause);
+ connect(controls, &PlayerControls::stop, m_player, &QMediaPlayer::stop);
+ connect(controls, &PlayerControls::next, m_playlist, &QMediaPlaylist::next);
+ connect(controls, &PlayerControls::previous, this, &Player::previousClicked);
+ connect(controls, &PlayerControls::changeVolume, m_audioOutput, &QAudioOutput::setVolume);
+ connect(controls, &PlayerControls::changeMuting, m_audioOutput, &QAudioOutput::setMuted);
+ connect(controls, &PlayerControls::changeRate, m_player, &QMediaPlayer::setPlaybackRate);
+ connect(controls, &PlayerControls::stop, m_videoWidget, QOverload<>::of(&QVideoWidget::update));
+
+ connect(m_player, &QMediaPlayer::playbackStateChanged, controls, &PlayerControls::setState);
+ connect(m_audioOutput, &QAudioOutput::volumeChanged, controls, &PlayerControls::setVolume);
+ connect(m_audioOutput, &QAudioOutput::mutedChanged, controls, &PlayerControls::setMuted);
+
+ controlLayout->addWidget(controls);
+ controlLayout->addStretch(1);
+
+ m_fullScreenButton = new QPushButton(tr("FullScreen"), this);
+ m_fullScreenButton->setCheckable(true);
+ controlLayout->addWidget(m_fullScreenButton);
+
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ m_audioOutputCombo = new QComboBox(this);
+ m_audioOutputCombo->addItem(QString::fromUtf8("Default"), QVariant::fromValue(QAudioDevice()));
+ for (auto &deviceInfo: QMediaDevices::audioOutputs())
+ m_audioOutputCombo->addItem(deviceInfo.description(), QVariant::fromValue(deviceInfo));
+ connect(m_audioOutputCombo, QOverload<int>::of(&QComboBox::activated), this,
+ &Player::audioOutputChanged);
+ controlLayout->addWidget(m_audioOutputCombo);
+#endif
+
+ layout->addLayout(controlLayout);
+
+ // tracks
+ QGridLayout *tracksLayout = new QGridLayout;
+
+ m_audioTracks = new QComboBox(this);
+ connect(m_audioTracks, &QComboBox::activated, this, &Player::selectAudioStream);
+ tracksLayout->addWidget(new QLabel(tr("Audio Tracks:")), 0, 0);
+ tracksLayout->addWidget(m_audioTracks, 0, 1);
+
+ m_videoTracks = new QComboBox(this);
+ connect(m_videoTracks, &QComboBox::activated, this, &Player::selectVideoStream);
+ tracksLayout->addWidget(new QLabel(tr("Video Tracks:")), 1, 0);
+ tracksLayout->addWidget(m_videoTracks, 1, 1);
+
+ m_subtitleTracks = new QComboBox(this);
+ connect(m_subtitleTracks, &QComboBox::activated, this, &Player::selectSubtitleStream);
+ tracksLayout->addWidget(new QLabel(tr("Subtitle Tracks:")), 2, 0);
+ tracksLayout->addWidget(m_subtitleTracks, 2, 1);
+
+ layout->addLayout(tracksLayout);
+
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ // metadata
+
+ QLabel *metaDataLabel = new QLabel(tr("Metadata for file:"));
+ layout->addWidget(metaDataLabel);
+
+ QGridLayout *metaDataLayout = new QGridLayout;
+ int key = QMediaMetaData::Title;
+ for (int i = 0; i < (QMediaMetaData::NumMetaData + 2) / 3; i++) {
+ for (int j = 0; j < 6; j += 2) {
+ m_metaDataLabels[key] = new QLabel(
+ QMediaMetaData::metaDataKeyToString(static_cast<QMediaMetaData::Key>(key)));
+ if (key == QMediaMetaData::ThumbnailImage || key == QMediaMetaData::CoverArtImage)
+ m_metaDataFields[key] = new QLabel;
+ else
+ m_metaDataFields[key] = new QLineEdit;
+ m_metaDataLabels[key]->setDisabled(true);
+ m_metaDataFields[key]->setDisabled(true);
+ metaDataLayout->addWidget(m_metaDataLabels[key], i, j);
+ metaDataLayout->addWidget(m_metaDataFields[key], i, j + 1);
+ key++;
+ if (key == QMediaMetaData::NumMetaData)
+ break;
+ }
+ }
+
+ layout->addLayout(metaDataLayout);
+#endif
+
+#if defined(Q_OS_QNX)
+ // On QNX, the main window doesn't have a title bar (or any other decorations).
+ // Create a status bar for the status information instead.
+ m_statusLabel = new QLabel;
+ m_statusBar = new QStatusBar;
+ m_statusBar->addPermanentWidget(m_statusLabel);
+ m_statusBar->setSizeGripEnabled(false); // Without mouse grabbing, it doesn't work very well.
+ layout->addWidget(m_statusBar);
+#endif
+
+ setLayout(layout);
+
+ if (!isPlayerAvailable()) {
+ QMessageBox::warning(this, tr("Service not available"),
+ tr("The QMediaPlayer object does not have a valid service.\n"\
+ "Please check the media service plugins are installed."));
+
+ controls->setEnabled(false);
+ if (m_playlistView)
+ m_playlistView->setEnabled(false);
+ openButton->setEnabled(false);
+ m_fullScreenButton->setEnabled(false);
+ }
+
+ metaDataChanged();
+}
+
+bool Player::isPlayerAvailable() const
+{
+ return m_player->isAvailable();
+}
+
+void Player::open()
+{
+ QFileDialog fileDialog(this);
+ fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
+ fileDialog.setWindowTitle(tr("Open Files"));
+ fileDialog.setDirectory(QStandardPaths::standardLocations(QStandardPaths::MoviesLocation).value(0, QDir::homePath()));
+ if (fileDialog.exec() == QDialog::Accepted)
+ addToPlaylist(fileDialog.selectedUrls());
+}
+
+static bool isPlaylist(const QUrl &url) // Check for ".m3u" playlists.
+{
+ if (!url.isLocalFile())
+ return false;
+ const QFileInfo fileInfo(url.toLocalFile());
+ return fileInfo.exists() && !fileInfo.suffix().compare(QLatin1String("m3u"), Qt::CaseInsensitive);
+}
+
+void Player::addToPlaylist(const QList<QUrl> &urls)
+{
+ const int previousMediaCount = m_playlist->mediaCount();
+ for (auto &url: urls) {
+ if (isPlaylist(url))
+ m_playlist->load(url);
+ else
+ m_playlist->addMedia(url);
+ }
+ if (m_playlist->mediaCount() > previousMediaCount) {
+ auto index = m_playlistModel->index(previousMediaCount, 0);
+ if (m_playlistView)
+ m_playlistView->setCurrentIndex(index);
+ jump(index);
+ }
+}
+
+void Player::durationChanged(qint64 duration)
+{
+ m_duration = duration / 1000;
+ m_slider->setMaximum(duration);
+}
+
+void Player::positionChanged(qint64 progress)
+{
+ if (!m_slider->isSliderDown())
+ m_slider->setValue(progress);
+
+ updateDurationInfo(progress / 1000);
+}
+
+void Player::metaDataChanged()
+{
+ auto metaData = m_player->metaData();
+ setTrackInfo(QString("%1 - %2")
+ .arg(metaData.value(QMediaMetaData::AlbumArtist).toString())
+ .arg(metaData.value(QMediaMetaData::Title).toString()));
+
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ for (int i = 0; i < QMediaMetaData::NumMetaData; i++) {
+ if (QLineEdit* field = qobject_cast<QLineEdit*>(m_metaDataFields[i]))
+ field->clear();
+ else if (QLabel* label = qobject_cast<QLabel*>(m_metaDataFields[i]))
+ label->clear();
+ m_metaDataFields[i]->setDisabled(true);
+ m_metaDataLabels[i]->setDisabled(true);
+ }
+
+ for (auto &key : metaData.keys()) {
+ int i = int(key);
+ if (key == QMediaMetaData::CoverArtImage) {
+ QVariant v = metaData.value(key);
+ if (QLabel *cover = qobject_cast<QLabel*>(m_metaDataFields[key])) {
+ QImage coverImage = v.value<QImage>();
+ cover->setPixmap(QPixmap::fromImage(coverImage));
+ }
+ } else if (key == QMediaMetaData::ThumbnailImage) {
+ QVariant v = metaData.value(key);
+ if (QLabel *thumbnail = qobject_cast<QLabel*>(m_metaDataFields[key])) {
+ QImage thumbnailImage = v.value<QImage>();
+ thumbnail->setPixmap(QPixmap::fromImage(thumbnailImage));
+ }
+ } else if (QLineEdit *field = qobject_cast<QLineEdit*>(m_metaDataFields[key])) {
+ QString stringValue = metaData.stringValue(key);
+ field->setText(stringValue);
+ }
+ m_metaDataFields[i]->setDisabled(false);
+ m_metaDataLabels[i]->setDisabled(false);
+ }
+#endif
+}
+
+QString Player::trackName(const QMediaMetaData &metaData, int index)
+{
+ QString name;
+ QString title = metaData.stringValue(QMediaMetaData::Title);
+ QLocale::Language lang = metaData.value(QMediaMetaData::Language).value<QLocale::Language>();
+
+ if (title.isEmpty()) {
+ if (lang == QLocale::Language::AnyLanguage)
+ name = tr("Track %1").arg(index+1);
+ else
+ name = QLocale::languageToString(lang);
+ } else {
+ if (lang == QLocale::Language::AnyLanguage)
+ name = title;
+ else
+ name = QString("%1 - [%2]").arg(title).arg(QLocale::languageToString(lang));
+ }
+ return name;
+}
+
+void Player::tracksChanged()
+{
+ m_audioTracks->clear();
+ m_videoTracks->clear();
+ m_subtitleTracks->clear();
+
+ const auto audioTracks = m_player->audioTracks();
+ m_audioTracks->addItem(QString::fromUtf8("No audio"), -1);
+ for (int i = 0; i < audioTracks.size(); ++i)
+ m_audioTracks->addItem(trackName(audioTracks.at(i), i), i);
+ m_audioTracks->setCurrentIndex(m_player->activeAudioTrack() + 1);
+
+ const auto videoTracks = m_player->videoTracks();
+ m_videoTracks->addItem(QString::fromUtf8("No video"), -1);
+ for (int i = 0; i < videoTracks.size(); ++i)
+ m_videoTracks->addItem(trackName(videoTracks.at(i), i), i);
+ m_videoTracks->setCurrentIndex(m_player->activeVideoTrack() + 1);
+
+ m_subtitleTracks->addItem(QString::fromUtf8("No subtitles"), -1);
+ const auto subtitleTracks = m_player->subtitleTracks();
+ for (int i = 0; i < subtitleTracks.size(); ++i)
+ m_subtitleTracks->addItem(trackName(subtitleTracks.at(i), i), i);
+ m_subtitleTracks->setCurrentIndex(m_player->activeSubtitleTrack() + 1);
+}
+
+void Player::previousClicked()
+{
+ // Go to previous track if we are within the first 5 seconds of playback
+ // Otherwise, seek to the beginning.
+ if (m_player->position() <= 5000) {
+ m_playlist->previous();
+ } else {
+ m_player->setPosition(0);
+ }
+}
+
+void Player::jump(const QModelIndex &index)
+{
+ if (index.isValid()) {
+ m_playlist->setCurrentIndex(index.row());
+ }
+}
+
+void Player::playlistPositionChanged(int currentItem)
+{
+ if (m_playlistView)
+ m_playlistView->setCurrentIndex(m_playlistModel->index(currentItem, 0));
+ m_player->setSource(m_playlist->currentMedia());
+}
+
+void Player::seek(int mseconds)
+{
+ m_player->setPosition(mseconds);
+}
+
+void Player::statusChanged(QMediaPlayer::MediaStatus status)
+{
+ handleCursor(status);
+
+ // handle status message
+ switch (status) {
+ case QMediaPlayer::NoMedia:
+ case QMediaPlayer::LoadedMedia:
+ setStatusInfo(QString());
+ break;
+ case QMediaPlayer::LoadingMedia:
+ setStatusInfo(tr("Loading..."));
+ break;
+ case QMediaPlayer::BufferingMedia:
+ case QMediaPlayer::BufferedMedia:
+ setStatusInfo(tr("Buffering %1%").arg(qRound(m_player->bufferProgress()*100.)));
+ break;
+ case QMediaPlayer::StalledMedia:
+ setStatusInfo(tr("Stalled %1%").arg(qRound(m_player->bufferProgress()*100.)));
+ break;
+ case QMediaPlayer::EndOfMedia:
+ QApplication::alert(this);
+ m_playlist->next();
+ break;
+ case QMediaPlayer::InvalidMedia:
+ displayErrorMessage();
+ break;
+ }
+}
+
+void Player::handleCursor(QMediaPlayer::MediaStatus status)
+{
+#ifndef QT_NO_CURSOR
+ if (status == QMediaPlayer::LoadingMedia ||
+ status == QMediaPlayer::BufferingMedia ||
+ status == QMediaPlayer::StalledMedia)
+ setCursor(QCursor(Qt::BusyCursor));
+ else
+ unsetCursor();
+#endif
+}
+
+void Player::bufferingProgress(float progress)
+{
+ if (m_player->mediaStatus() == QMediaPlayer::StalledMedia)
+ setStatusInfo(tr("Stalled %1%").arg(qRound(progress*100.)));
+ else
+ setStatusInfo(tr("Buffering %1%").arg(qRound(progress*100.)));
+}
+
+void Player::videoAvailableChanged(bool available)
+{
+ if (!available) {
+ disconnect(m_fullScreenButton, &QPushButton::clicked, m_videoWidget, &QVideoWidget::setFullScreen);
+ disconnect(m_videoWidget, &QVideoWidget::fullScreenChanged, m_fullScreenButton, &QPushButton::setChecked);
+ m_videoWidget->setFullScreen(false);
+ } else {
+ connect(m_fullScreenButton, &QPushButton::clicked, m_videoWidget, &QVideoWidget::setFullScreen);
+ connect(m_videoWidget, &QVideoWidget::fullScreenChanged, m_fullScreenButton, &QPushButton::setChecked);
+
+ if (m_fullScreenButton->isChecked())
+ m_videoWidget->setFullScreen(true);
+ }
+}
+
+void Player::selectAudioStream()
+{
+ int stream = m_audioTracks->currentData().toInt();
+ m_player->setActiveAudioTrack(stream);
+}
+
+void Player::selectVideoStream()
+{
+ int stream = m_videoTracks->currentData().toInt();
+ m_player->setActiveVideoTrack(stream);
+}
+
+void Player::selectSubtitleStream()
+{
+ int stream = m_subtitleTracks->currentData().toInt();
+ m_player->setActiveSubtitleTrack(stream);
+}
+
+void Player::setTrackInfo(const QString &info)
+{
+ m_trackInfo = info;
+
+ if (m_statusBar) {
+ m_statusBar->showMessage(m_trackInfo);
+ m_statusLabel->setText(m_statusInfo);
+ } else {
+ if (!m_statusInfo.isEmpty())
+ setWindowTitle(QString("%1 | %2").arg(m_trackInfo).arg(m_statusInfo));
+ else
+ setWindowTitle(m_trackInfo);
+ }
+}
+
+void Player::setStatusInfo(const QString &info)
+{
+ m_statusInfo = info;
+
+ if (m_statusBar) {
+ m_statusBar->showMessage(m_trackInfo);
+ m_statusLabel->setText(m_statusInfo);
+ } else {
+ if (!m_statusInfo.isEmpty())
+ setWindowTitle(QString("%1 | %2").arg(m_trackInfo).arg(m_statusInfo));
+ else
+ setWindowTitle(m_trackInfo);
+ }
+}
+
+void Player::displayErrorMessage()
+{
+ if (m_player->error() == QMediaPlayer::NoError)
+ return;
+ setStatusInfo(m_player->errorString());
+}
+
+void Player::updateDurationInfo(qint64 currentInfo)
+{
+ QString tStr;
+ if (currentInfo || m_duration) {
+ QTime currentTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60,
+ currentInfo % 60, (currentInfo * 1000) % 1000);
+ QTime totalTime((m_duration / 3600) % 60, (m_duration / 60) % 60,
+ m_duration % 60, (m_duration * 1000) % 1000);
+ QString format = "mm:ss";
+ if (m_duration > 3600)
+ format = "hh:mm:ss";
+ tStr = currentTime.toString(format) + " / " + totalTime.toString(format);
+ }
+ m_labelDuration->setText(tStr);
+}
+
+void Player::audioOutputChanged(int index)
+{
+ auto device = m_audioOutputCombo->itemData(index).value<QAudioDevice>();
+ m_player->audioOutput()->setDevice(device);
+}
diff --git a/examples/multimedia/player/player.h b/examples/multimedia/player/player.h
new file mode 100644
index 000000000..1d328d307
--- /dev/null
+++ b/examples/multimedia/player/player.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef PLAYER_H
+#define PLAYER_H
+
+#include "qmediaplaylist.h"
+
+#include <QWidget>
+#include <QMediaPlayer>
+#include <QMediaMetaData>
+
+QT_BEGIN_NAMESPACE
+class QAbstractItemView;
+class QLabel;
+class QMediaPlayer;
+class QModelIndex;
+class QPushButton;
+class QComboBox;
+class QSlider;
+class QStatusBar;
+class QVideoWidget;
+QT_END_NAMESPACE
+
+class PlaylistModel;
+
+class Player : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Player(QWidget *parent = nullptr);
+ ~Player() = default;
+
+ bool isPlayerAvailable() const;
+
+ void addToPlaylist(const QList<QUrl> &urls);
+
+signals:
+ void fullScreenChanged(bool fullScreen);
+
+private slots:
+ void open();
+ void durationChanged(qint64 duration);
+ void positionChanged(qint64 progress);
+ void metaDataChanged();
+ void tracksChanged();
+
+ void previousClicked();
+
+ void seek(int mseconds);
+ void jump(const QModelIndex &index);
+ void playlistPositionChanged(int);
+
+ void statusChanged(QMediaPlayer::MediaStatus status);
+ void bufferingProgress(float progress);
+ void videoAvailableChanged(bool available);
+
+ void selectAudioStream();
+ void selectVideoStream();
+ void selectSubtitleStream();
+
+ void displayErrorMessage();
+
+ void audioOutputChanged(int);
+
+private:
+ void setTrackInfo(const QString &info);
+ void setStatusInfo(const QString &info);
+ void handleCursor(QMediaPlayer::MediaStatus status);
+ void updateDurationInfo(qint64 currentInfo);
+ QString trackName(const QMediaMetaData &metaData, int index);
+
+ QMediaPlayer *m_player = nullptr;
+ QAudioOutput *m_audioOutput = nullptr;
+ QMediaPlaylist *m_playlist = nullptr;
+ QVideoWidget *m_videoWidget = nullptr;
+ QSlider *m_slider = nullptr;
+ QLabel *m_labelDuration = nullptr;
+ QPushButton *m_fullScreenButton = nullptr;
+ QComboBox *m_audioOutputCombo = nullptr;
+ QLabel *m_statusLabel = nullptr;
+ QStatusBar *m_statusBar = nullptr;
+
+ QComboBox *m_audioTracks = nullptr;
+ QComboBox *m_videoTracks = nullptr;
+ QComboBox *m_subtitleTracks = nullptr;
+
+ PlaylistModel *m_playlistModel = nullptr;
+ QAbstractItemView *m_playlistView = nullptr;
+ QString m_trackInfo;
+ QString m_statusInfo;
+ qint64 m_duration;
+
+ QWidget *m_metaDataFields[QMediaMetaData::NumMetaData] = {};
+ QLabel *m_metaDataLabels[QMediaMetaData::NumMetaData] = {};
+};
+
+#endif // PLAYER_H
diff --git a/examples/multimedia/player/player.pro b/examples/multimedia/player/player.pro
new file mode 100644
index 000000000..703942441
--- /dev/null
+++ b/examples/multimedia/player/player.pro
@@ -0,0 +1,27 @@
+TEMPLATE = app
+TARGET = player
+
+QT += network \
+ multimedia \
+ multimediawidgets \
+ widgets
+
+HEADERS = \
+ player.h \
+ playercontrols.h \
+ playlistmodel.h \
+ videowidget.h \
+ qmediaplaylist.h \
+ qmediaplaylist_p.h \
+ qplaylistfileparser_p.h
+
+SOURCES = main.cpp \
+ player.cpp \
+ playercontrols.cpp \
+ playlistmodel.cpp \
+ videowidget.cpp \
+ qmediaplaylist.cpp \
+ qplaylistfileparser.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/player
+INSTALLS += target
diff --git a/examples/multimedia/player/playercontrols.cpp b/examples/multimedia/player/playercontrols.cpp
new file mode 100644
index 000000000..0a6827326
--- /dev/null
+++ b/examples/multimedia/player/playercontrols.cpp
@@ -0,0 +1,172 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "playercontrols.h"
+
+#include <QBoxLayout>
+#include <QSlider>
+#include <QStyle>
+#include <QToolButton>
+#include <QComboBox>
+#include <QAudio>
+
+PlayerControls::PlayerControls(QWidget *parent)
+ : QWidget(parent)
+{
+ m_playButton = new QToolButton(this);
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
+
+ connect(m_playButton, &QAbstractButton::clicked, this, &PlayerControls::playClicked);
+
+ m_stopButton = new QToolButton(this);
+ m_stopButton->setIcon(style()->standardIcon(QStyle::SP_MediaStop));
+ m_stopButton->setEnabled(false);
+
+ connect(m_stopButton, &QAbstractButton::clicked, this, &PlayerControls::stop);
+
+ m_nextButton = new QToolButton(this);
+ m_nextButton->setIcon(style()->standardIcon(QStyle::SP_MediaSkipForward));
+
+ connect(m_nextButton, &QAbstractButton::clicked, this, &PlayerControls::next);
+
+ m_previousButton = new QToolButton(this);
+ m_previousButton->setIcon(style()->standardIcon(QStyle::SP_MediaSkipBackward));
+
+ connect(m_previousButton, &QAbstractButton::clicked, this, &PlayerControls::previous);
+
+ m_muteButton = new QToolButton(this);
+ m_muteButton->setIcon(style()->standardIcon(QStyle::SP_MediaVolume));
+
+ connect(m_muteButton, &QAbstractButton::clicked, this, &PlayerControls::muteClicked);
+
+ m_volumeSlider = new QSlider(Qt::Horizontal, this);
+ m_volumeSlider->setRange(0, 100);
+
+ connect(m_volumeSlider, &QSlider::valueChanged, this, &PlayerControls::onVolumeSliderValueChanged);
+
+ m_rateBox = new QComboBox(this);
+ m_rateBox->addItem("0.5x", QVariant(0.5));
+ m_rateBox->addItem("1.0x", QVariant(1.0));
+ m_rateBox->addItem("2.0x", QVariant(2.0));
+ m_rateBox->setCurrentIndex(1);
+
+ connect(m_rateBox, QOverload<int>::of(&QComboBox::activated), this, &PlayerControls::updateRate);
+
+ QBoxLayout *layout = new QHBoxLayout;
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addWidget(m_stopButton);
+ layout->addWidget(m_previousButton);
+ layout->addWidget(m_playButton);
+ layout->addWidget(m_nextButton);
+ layout->addWidget(m_muteButton);
+ layout->addWidget(m_volumeSlider);
+ layout->addWidget(m_rateBox);
+ setLayout(layout);
+}
+
+QMediaPlayer::PlaybackState PlayerControls::state() const
+{
+ return m_playerState;
+}
+
+void PlayerControls::setState(QMediaPlayer::PlaybackState state)
+{
+ if (state != m_playerState) {
+ m_playerState = state;
+
+ switch (state) {
+ case QMediaPlayer::StoppedState:
+ m_stopButton->setEnabled(false);
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
+ break;
+ case QMediaPlayer::PlayingState:
+ m_stopButton->setEnabled(true);
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause));
+ break;
+ case QMediaPlayer::PausedState:
+ m_stopButton->setEnabled(true);
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
+ break;
+ }
+ }
+}
+
+float PlayerControls::volume() const
+{
+ qreal linearVolume = QAudio::convertVolume(m_volumeSlider->value() / qreal(100),
+ QAudio::LogarithmicVolumeScale,
+ QAudio::LinearVolumeScale);
+
+ return linearVolume;
+}
+
+void PlayerControls::setVolume(float volume)
+{
+ qreal logarithmicVolume = QAudio::convertVolume(volume,
+ QAudio::LinearVolumeScale,
+ QAudio::LogarithmicVolumeScale);
+
+ m_volumeSlider->setValue(qRound(logarithmicVolume * 100));
+}
+
+bool PlayerControls::isMuted() const
+{
+ return m_playerMuted;
+}
+
+void PlayerControls::setMuted(bool muted)
+{
+ if (muted != m_playerMuted) {
+ m_playerMuted = muted;
+
+ m_muteButton->setIcon(style()->standardIcon(muted
+ ? QStyle::SP_MediaVolumeMuted
+ : QStyle::SP_MediaVolume));
+ }
+}
+
+void PlayerControls::playClicked()
+{
+ switch (m_playerState) {
+ case QMediaPlayer::StoppedState:
+ case QMediaPlayer::PausedState:
+ emit play();
+ break;
+ case QMediaPlayer::PlayingState:
+ emit pause();
+ break;
+ }
+}
+
+void PlayerControls::muteClicked()
+{
+ emit changeMuting(!m_playerMuted);
+}
+
+qreal PlayerControls::playbackRate() const
+{
+ return m_rateBox->itemData(m_rateBox->currentIndex()).toDouble();
+}
+
+void PlayerControls::setPlaybackRate(float rate)
+{
+ for (int i = 0; i < m_rateBox->count(); ++i) {
+ if (qFuzzyCompare(rate, float(m_rateBox->itemData(i).toDouble()))) {
+ m_rateBox->setCurrentIndex(i);
+ return;
+ }
+ }
+
+ m_rateBox->addItem(QString("%1x").arg(rate), QVariant(rate));
+ m_rateBox->setCurrentIndex(m_rateBox->count() - 1);
+}
+
+void PlayerControls::updateRate()
+{
+ emit changeRate(playbackRate());
+}
+
+void PlayerControls::onVolumeSliderValueChanged()
+{
+ emit changeVolume(volume());
+}
diff --git a/examples/multimedia/player/playercontrols.h b/examples/multimedia/player/playercontrols.h
new file mode 100644
index 000000000..72dddd68f
--- /dev/null
+++ b/examples/multimedia/player/playercontrols.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef PLAYERCONTROLS_H
+#define PLAYERCONTROLS_H
+
+#include <QMediaPlayer>
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+class QAbstractButton;
+class QAbstractSlider;
+class QComboBox;
+QT_END_NAMESPACE
+
+class PlayerControls : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit PlayerControls(QWidget *parent = nullptr);
+
+ QMediaPlayer::PlaybackState state() const;
+ float volume() const;
+ bool isMuted() const;
+ qreal playbackRate() const;
+
+public slots:
+ void setState(QMediaPlayer::PlaybackState state);
+ void setVolume(float volume);
+ void setMuted(bool muted);
+ void setPlaybackRate(float rate);
+
+signals:
+ void play();
+ void pause();
+ void stop();
+ void next();
+ void previous();
+ void changeVolume(float volume);
+ void changeMuting(bool muting);
+ void changeRate(qreal rate);
+
+private slots:
+ void playClicked();
+ void muteClicked();
+ void updateRate();
+ void onVolumeSliderValueChanged();
+
+private:
+ QMediaPlayer::PlaybackState m_playerState = QMediaPlayer::StoppedState;
+ bool m_playerMuted = false;
+ QAbstractButton *m_playButton = nullptr;
+ QAbstractButton *m_stopButton = nullptr;
+ QAbstractButton *m_nextButton = nullptr;
+ QAbstractButton *m_previousButton = nullptr;
+ QAbstractButton *m_muteButton = nullptr;
+ QAbstractSlider *m_volumeSlider = nullptr;
+ QComboBox *m_rateBox = nullptr;
+};
+
+#endif // PLAYERCONTROLS_H
diff --git a/examples/multimedia/player/playlistmodel.cpp b/examples/multimedia/player/playlistmodel.cpp
new file mode 100644
index 000000000..871aed0b8
--- /dev/null
+++ b/examples/multimedia/player/playlistmodel.cpp
@@ -0,0 +1,102 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "playlistmodel.h"
+#include "qmediaplaylist.h"
+
+#include <QFileInfo>
+#include <QUrl>
+
+PlaylistModel::PlaylistModel(QObject *parent)
+ : QAbstractItemModel(parent)
+{
+ m_playlist.reset(new QMediaPlaylist);
+ connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems);
+ connect(m_playlist.data(), &QMediaPlaylist::mediaInserted, this, &PlaylistModel::endInsertItems);
+ connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeRemoved, this, &PlaylistModel::beginRemoveItems);
+ connect(m_playlist.data(), &QMediaPlaylist::mediaRemoved, this, &PlaylistModel::endRemoveItems);
+ connect(m_playlist.data(), &QMediaPlaylist::mediaChanged, this, &PlaylistModel::changeItems);
+}
+
+PlaylistModel::~PlaylistModel() = default;
+
+int PlaylistModel::rowCount(const QModelIndex &parent) const
+{
+ return m_playlist && !parent.isValid() ? m_playlist->mediaCount() : 0;
+}
+
+int PlaylistModel::columnCount(const QModelIndex &parent) const
+{
+ return !parent.isValid() ? ColumnCount : 0;
+}
+
+QModelIndex PlaylistModel::index(int row, int column, const QModelIndex &parent) const
+{
+ return m_playlist && !parent.isValid()
+ && row >= 0 && row < m_playlist->mediaCount()
+ && column >= 0 && column < ColumnCount
+ ? createIndex(row, column)
+ : QModelIndex();
+}
+
+QModelIndex PlaylistModel::parent(const QModelIndex &child) const
+{
+ Q_UNUSED(child);
+
+ return QModelIndex();
+}
+
+QVariant PlaylistModel::data(const QModelIndex &index, int role) const
+{
+ if (index.isValid() && role == Qt::DisplayRole) {
+ QVariant value = m_data[index];
+ if (!value.isValid() && index.column() == Title) {
+ QUrl location = m_playlist->media(index.row());
+ return QFileInfo(location.path()).fileName();
+ }
+
+ return value;
+ }
+ return QVariant();
+}
+
+QMediaPlaylist *PlaylistModel::playlist() const
+{
+ return m_playlist.data();
+}
+
+bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ Q_UNUSED(role);
+ m_data[index] = value;
+ emit dataChanged(index, index);
+ return true;
+}
+
+void PlaylistModel::beginInsertItems(int start, int end)
+{
+ m_data.clear();
+ beginInsertRows(QModelIndex(), start, end);
+}
+
+void PlaylistModel::endInsertItems()
+{
+ endInsertRows();
+}
+
+void PlaylistModel::beginRemoveItems(int start, int end)
+{
+ m_data.clear();
+ beginRemoveRows(QModelIndex(), start, end);
+}
+
+void PlaylistModel::endRemoveItems()
+{
+ endInsertRows();
+}
+
+void PlaylistModel::changeItems(int start, int end)
+{
+ m_data.clear();
+ emit dataChanged(index(start,0), index(end,ColumnCount));
+}
diff --git a/examples/multimedia/player/playlistmodel.h b/examples/multimedia/player/playlistmodel.h
new file mode 100644
index 000000000..6c20cc1d6
--- /dev/null
+++ b/examples/multimedia/player/playlistmodel.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef PLAYLISTMODEL_H
+#define PLAYLISTMODEL_H
+
+#include <QAbstractItemModel>
+#include <QScopedPointer>
+
+QT_BEGIN_NAMESPACE
+class QMediaPlaylist;
+QT_END_NAMESPACE
+
+class PlaylistModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ enum Column
+ {
+ Title = 0,
+ ColumnCount
+ };
+
+ explicit PlaylistModel(QObject *parent = nullptr);
+ ~PlaylistModel();
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex &child) const override;
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ QMediaPlaylist *playlist() const;
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override;
+
+private slots:
+ void beginInsertItems(int start, int end);
+ void endInsertItems();
+ void beginRemoveItems(int start, int end);
+ void endRemoveItems();
+ void changeItems(int start, int end);
+
+private:
+ QScopedPointer<QMediaPlaylist> m_playlist;
+ QMap<QModelIndex, QVariant> m_data;
+};
+
+#endif // PLAYLISTMODEL_H
diff --git a/examples/multimedia/player/qmediaplaylist.cpp b/examples/multimedia/player/qmediaplaylist.cpp
new file mode 100644
index 000000000..529720808
--- /dev/null
+++ b/examples/multimedia/player/qmediaplaylist.cpp
@@ -0,0 +1,653 @@
+// 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 "qmediaplaylist.h"
+#include "qmediaplaylist_p.h"
+#include "qplaylistfileparser_p.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qcoreevent.h>
+#include <QtCore/qcoreapplication.h>
+#include <QRandomGenerator>
+
+QT_BEGIN_NAMESPACE
+
+class QM3uPlaylistWriter
+{
+public:
+ QM3uPlaylistWriter(QIODevice *device)
+ :m_device(device), m_textStream(new QTextStream(m_device))
+ {
+ }
+
+ ~QM3uPlaylistWriter()
+ {
+ delete m_textStream;
+ }
+
+ bool writeItem(const QUrl& item)
+ {
+ *m_textStream << item.toString() << Qt::endl;
+ return true;
+ }
+
+private:
+ QIODevice *m_device;
+ QTextStream *m_textStream;
+};
+
+
+int QMediaPlaylistPrivate::nextPosition(int steps) const
+{
+ if (playlist.count() == 0)
+ return -1;
+
+ int next = currentPos + steps;
+
+ switch (playbackMode) {
+ case QMediaPlaylist::CurrentItemOnce:
+ return steps != 0 ? -1 : currentPos;
+ case QMediaPlaylist::CurrentItemInLoop:
+ return currentPos;
+ case QMediaPlaylist::Sequential:
+ if (next >= playlist.size())
+ next = -1;
+ break;
+ case QMediaPlaylist::Loop:
+ next %= playlist.count();
+ break;
+ }
+
+ return next;
+}
+
+int QMediaPlaylistPrivate::prevPosition(int steps) const
+{
+ if (playlist.count() == 0)
+ return -1;
+
+ int next = currentPos;
+ if (next < 0)
+ next = playlist.size();
+ next -= steps;
+
+ switch (playbackMode) {
+ case QMediaPlaylist::CurrentItemOnce:
+ return steps != 0 ? -1 : currentPos;
+ case QMediaPlaylist::CurrentItemInLoop:
+ return currentPos;
+ case QMediaPlaylist::Sequential:
+ if (next < 0)
+ next = -1;
+ break;
+ case QMediaPlaylist::Loop:
+ next %= playlist.size();
+ if (next < 0)
+ next += playlist.size();
+ break;
+ }
+
+ return next;
+}
+
+/*!
+ \class QMediaPlaylist
+ \inmodule QtMultimedia
+ \ingroup multimedia
+ \ingroup multimedia_playback
+
+
+ \brief The QMediaPlaylist class provides a list of media content to play.
+
+ QMediaPlaylist is intended to be used with other media objects,
+ like QMediaPlayer.
+
+ QMediaPlaylist allows to access the service intrinsic playlist functionality
+ if available, otherwise it provides the local memory playlist implementation.
+
+ \snippet multimedia-snippets/media.cpp Movie playlist
+
+ Depending on playlist source implementation, most of the playlist mutating
+ operations can be asynchronous.
+
+ QMediaPlayList currently supports M3U playlists (file extension .m3u and .m3u8).
+
+ \sa QUrl
+*/
+
+
+/*!
+ \enum QMediaPlaylist::PlaybackMode
+
+ The QMediaPlaylist::PlaybackMode describes the order items in playlist are played.
+
+ \value CurrentItemOnce The current item is played only once.
+
+ \value CurrentItemInLoop The current item is played repeatedly in a loop.
+
+ \value Sequential 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.
+
+ \value Loop Playback restarts at the first item after the last has finished playing.
+
+ \value Random Play items in random order.
+*/
+
+
+
+/*!
+ Create a new playlist object with the given \a parent.
+*/
+
+QMediaPlaylist::QMediaPlaylist(QObject *parent)
+ : QObject(parent)
+ , d_ptr(new QMediaPlaylistPrivate)
+{
+ Q_D(QMediaPlaylist);
+
+ d->q_ptr = this;
+}
+
+/*!
+ Destroys the playlist.
+ */
+
+QMediaPlaylist::~QMediaPlaylist()
+{
+ delete d_ptr;
+}
+
+/*!
+ \property QMediaPlaylist::playbackMode
+
+ This property defines the order that items in the playlist are played.
+
+ \sa QMediaPlaylist::PlaybackMode
+*/
+
+QMediaPlaylist::PlaybackMode QMediaPlaylist::playbackMode() const
+{
+ return d_func()->playbackMode;
+}
+
+void QMediaPlaylist::setPlaybackMode(QMediaPlaylist::PlaybackMode mode)
+{
+ Q_D(QMediaPlaylist);
+
+ if (mode == d->playbackMode)
+ return;
+
+ d->playbackMode = mode;
+
+ emit playbackModeChanged(mode);
+}
+
+/*!
+ Returns position of the current media content in the playlist.
+*/
+int QMediaPlaylist::currentIndex() const
+{
+ return d_func()->currentPos;
+}
+
+/*!
+ Returns the current media content.
+*/
+
+QUrl QMediaPlaylist::currentMedia() const
+{
+ Q_D(const QMediaPlaylist);
+ if (d->currentPos < 0 || d->currentPos >= d->playlist.size())
+ return QUrl();
+ return d_func()->playlist.at(d_func()->currentPos);
+}
+
+/*!
+ Returns the index of the item, which would be current after calling next()
+ \a steps times.
+
+ Returned value depends on the size of playlist, current position
+ and playback mode.
+
+ \sa QMediaPlaylist::playbackMode(), previousIndex()
+*/
+int QMediaPlaylist::nextIndex(int steps) const
+{
+ return d_func()->nextPosition(steps);
+}
+
+/*!
+ Returns the index of the item, which would be current after calling previous()
+ \a steps times.
+
+ \sa QMediaPlaylist::playbackMode(), nextIndex()
+*/
+
+int QMediaPlaylist::previousIndex(int steps) const
+{
+ return d_func()->prevPosition(steps);
+}
+
+
+/*!
+ Returns the number of items in the playlist.
+
+ \sa isEmpty()
+ */
+int QMediaPlaylist::mediaCount() const
+{
+ return d_func()->playlist.count();
+}
+
+/*!
+ Returns true if the playlist contains no items, otherwise returns false.
+
+ \sa mediaCount()
+ */
+bool QMediaPlaylist::isEmpty() const
+{
+ return mediaCount() == 0;
+}
+
+/*!
+ Returns the media content at \a index in the playlist.
+*/
+
+QUrl QMediaPlaylist::media(int index) const
+{
+ Q_D(const QMediaPlaylist);
+ if (index < 0 || index >= d->playlist.size())
+ return QUrl();
+ return d->playlist.at(index);
+}
+
+/*!
+ Append the media \a content to the playlist.
+
+ Returns true if the operation is successful, otherwise returns false.
+ */
+void QMediaPlaylist::addMedia(const QUrl &content)
+{
+ Q_D(QMediaPlaylist);
+ int pos = d->playlist.size();
+ emit mediaAboutToBeInserted(pos, pos);
+ d->playlist.append(content);
+ emit mediaInserted(pos, pos);
+}
+
+/*!
+ Append multiple media content \a items to the playlist.
+
+ Returns true if the operation is successful, otherwise returns false.
+ */
+void QMediaPlaylist::addMedia(const QList<QUrl> &items)
+{
+ if (!items.size())
+ return;
+
+ Q_D(QMediaPlaylist);
+ int first = d->playlist.size();
+ int last = first + items.size() - 1;
+ emit mediaAboutToBeInserted(first, last);
+ d_func()->playlist.append(items);
+ emit mediaInserted(first, last);
+}
+
+/*!
+ Insert the media \a content to the playlist at position \a pos.
+
+ Returns true if the operation is successful, otherwise returns false.
+*/
+
+bool QMediaPlaylist::insertMedia(int pos, const QUrl &content)
+{
+ Q_D(QMediaPlaylist);
+ pos = qBound(0, pos, d->playlist.size());
+ emit mediaAboutToBeInserted(pos, pos);
+ d->playlist.insert(pos, content);
+ emit mediaInserted(pos, pos);
+ return true;
+}
+
+/*!
+ Insert multiple media content \a items to the playlist at position \a pos.
+
+ Returns true if the operation is successful, otherwise returns false.
+*/
+
+bool QMediaPlaylist::insertMedia(int pos, const QList<QUrl> &items)
+{
+ if (!items.size())
+ return true;
+
+ Q_D(QMediaPlaylist);
+ pos = qBound(0, pos, d->playlist.size());
+ int last = pos + items.size() - 1;
+ emit mediaAboutToBeInserted(pos, last);
+ auto newList = d->playlist.mid(0, pos);
+ newList += items;
+ newList += d->playlist.mid(pos);
+ d->playlist = newList;
+ emit mediaInserted(pos, last);
+ return true;
+}
+
+/*!
+ Move the item from position \a from to position \a to.
+
+ Returns true if the operation is successful, otherwise false.
+
+ \since 5.7
+*/
+bool QMediaPlaylist::moveMedia(int from, int to)
+{
+ Q_D(QMediaPlaylist);
+ if (from < 0 || from > d->playlist.count() ||
+ to < 0 || to > d->playlist.count())
+ return false;
+
+ d->playlist.move(from, to);
+ emit mediaChanged(from, to);
+ return true;
+}
+
+/*!
+ Remove the item from the playlist at position \a pos.
+
+ Returns true if the operation is successful, otherwise return false.
+ */
+bool QMediaPlaylist::removeMedia(int pos)
+{
+ return removeMedia(pos, pos);
+}
+
+/*!
+ Remove items in the playlist from \a start to \a end inclusive.
+
+ Returns true if the operation is successful, otherwise return false.
+ */
+bool QMediaPlaylist::removeMedia(int start, int end)
+{
+ Q_D(QMediaPlaylist);
+ if (end < start || end < 0 || start >= d->playlist.count())
+ return false;
+ start = qBound(0, start, d->playlist.size() - 1);
+ end = qBound(0, end, d->playlist.size() - 1);
+
+ emit mediaAboutToBeRemoved(start, end);
+ d->playlist.remove(start, end - start + 1);
+ emit mediaRemoved(start, end);
+ return true;
+}
+
+/*!
+ Remove all the items from the playlist.
+
+ Returns true if the operation is successful, otherwise return false.
+ */
+void QMediaPlaylist::clear()
+{
+ Q_D(QMediaPlaylist);
+ int size = d->playlist.size();
+ emit mediaAboutToBeRemoved(0, size - 1);
+ d->playlist.clear();
+ emit mediaRemoved(0, size - 1);
+}
+
+/*!
+ Load playlist from \a location. If \a format is specified, it is used,
+ otherwise format is guessed from location name and data.
+
+ New items are appended to playlist.
+
+ QMediaPlaylist::loaded() signal is emitted if playlist was loaded successfully,
+ otherwise the playlist emits loadFailed().
+*/
+
+void QMediaPlaylist::load(const QUrl &location, const char *format)
+{
+ Q_D(QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ d->ensureParser();
+ d->parser->start(location, QString::fromUtf8(format));
+}
+
+/*!
+ Load playlist from QIODevice \a device. If \a format is specified, it is used,
+ otherwise format is guessed from device data.
+
+ New items are appended to playlist.
+
+ QMediaPlaylist::loaded() signal is emitted if playlist was loaded successfully,
+ otherwise the playlist emits loadFailed().
+*/
+void QMediaPlaylist::load(QIODevice *device, const char *format)
+{
+ Q_D(QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ d->ensureParser();
+ d->parser->start(device, QString::fromUtf8(format));
+}
+
+/*!
+ Save playlist to \a location. If \a format is specified, it is used,
+ otherwise format is guessed from location name.
+
+ Returns true if playlist was saved successfully, otherwise returns false.
+ */
+bool QMediaPlaylist::save(const QUrl &location, const char *format) const
+{
+ Q_D(const QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ if (!d->checkFormat(format))
+ return false;
+
+ QFile file(location.toLocalFile());
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ d->error = AccessDeniedError;
+ d->errorString = tr("The file could not be accessed.");
+ return false;
+ }
+
+ return save(&file, format);
+}
+
+/*!
+ Save playlist to QIODevice \a device using format \a format.
+
+ Returns true if playlist was saved successfully, otherwise returns false.
+*/
+bool QMediaPlaylist::save(QIODevice *device, const char *format) const
+{
+ Q_D(const QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ if (!d->checkFormat(format))
+ return false;
+
+ QM3uPlaylistWriter writer(device);
+ for (const auto &entry : d->playlist)
+ writer.writeItem(entry);
+ return true;
+}
+
+/*!
+ Returns the last error condition.
+*/
+QMediaPlaylist::Error QMediaPlaylist::error() const
+{
+ return d_func()->error;
+}
+
+/*!
+ Returns the string describing the last error condition.
+*/
+QString QMediaPlaylist::errorString() const
+{
+ return d_func()->errorString;
+}
+
+/*!
+ Shuffle items in the playlist.
+*/
+void QMediaPlaylist::shuffle()
+{
+ Q_D(QMediaPlaylist);
+ QList<QUrl> playlist;
+
+ // keep the current item when shuffling
+ QUrl current;
+ if (d->currentPos != -1)
+ current = d->playlist.takeAt(d->currentPos);
+
+ while (!d->playlist.isEmpty())
+ playlist.append(d->playlist.takeAt(QRandomGenerator::global()->bounded(int(d->playlist.size()))));
+
+ if (d->currentPos != -1)
+ playlist.insert(d->currentPos, current);
+ d->playlist = playlist;
+ emit mediaChanged(0, d->playlist.count());
+}
+
+
+/*!
+ Advance to the next media content in playlist.
+*/
+void QMediaPlaylist::next()
+{
+ Q_D(QMediaPlaylist);
+ d->currentPos = d->nextPosition(1);
+
+ emit currentIndexChanged(d->currentPos);
+ emit currentMediaChanged(currentMedia());
+}
+
+/*!
+ Return to the previous media content in playlist.
+*/
+void QMediaPlaylist::previous()
+{
+ Q_D(QMediaPlaylist);
+ d->currentPos = d->prevPosition(1);
+
+ emit currentIndexChanged(d->currentPos);
+ emit currentMediaChanged(currentMedia());
+}
+
+/*!
+ Activate media content from playlist at position \a playlistPosition.
+*/
+
+void QMediaPlaylist::setCurrentIndex(int playlistPosition)
+{
+ Q_D(QMediaPlaylist);
+ if (playlistPosition < 0 || playlistPosition >= d->playlist.size())
+ playlistPosition = -1;
+ d->currentPos = playlistPosition;
+
+ emit currentIndexChanged(d->currentPos);
+ emit currentMediaChanged(currentMedia());
+}
+
+/*!
+ \fn void QMediaPlaylist::mediaInserted(int start, int end)
+
+ This signal is emitted after media has been inserted into the playlist.
+ The new items are those between \a start and \a end inclusive.
+ */
+
+/*!
+ \fn void QMediaPlaylist::mediaRemoved(int start, int end)
+
+ This signal is emitted after media has been removed from the playlist.
+ The removed items are those between \a start and \a end inclusive.
+ */
+
+/*!
+ \fn void QMediaPlaylist::mediaChanged(int start, int end)
+
+ This signal is emitted after media has been changed in the playlist
+ between \a start and \a end positions inclusive.
+ */
+
+/*!
+ \fn void QMediaPlaylist::currentIndexChanged(int position)
+
+ Signal emitted when playlist position changed to \a position.
+*/
+
+/*!
+ \fn void QMediaPlaylist::playbackModeChanged(QMediaPlaylist::PlaybackMode mode)
+
+ Signal emitted when playback mode changed to \a mode.
+*/
+
+/*!
+ \fn void QMediaPlaylist::mediaAboutToBeInserted(int start, int end)
+
+ Signal emitted when items are to be inserted at \a start and ending at \a end.
+*/
+
+/*!
+ \fn void QMediaPlaylist::mediaAboutToBeRemoved(int start, int end)
+
+ Signal emitted when item are to be deleted at \a start and ending at \a end.
+*/
+
+/*!
+ \fn void QMediaPlaylist::currentMediaChanged(const QUrl &content)
+
+ Signal emitted when current media changes to \a content.
+*/
+
+/*!
+ \property QMediaPlaylist::currentIndex
+ \brief Current position.
+*/
+
+/*!
+ \property QMediaPlaylist::currentMedia
+ \brief Current media content.
+*/
+
+/*!
+ \fn QMediaPlaylist::loaded()
+
+ Signal emitted when playlist finished loading.
+*/
+
+/*!
+ \fn QMediaPlaylist::loadFailed()
+
+ Signal emitted if failed to load playlist.
+*/
+
+/*!
+ \enum QMediaPlaylist::Error
+
+ This enum describes the QMediaPlaylist error codes.
+
+ \value NoError No errors.
+ \value FormatError Format error.
+ \value FormatNotSupportedError Format not supported.
+ \value NetworkError Network error.
+ \value AccessDeniedError Access denied error.
+*/
+
+QT_END_NAMESPACE
+
+#include "moc_qmediaplaylist.cpp"
diff --git a/examples/multimedia/player/qmediaplaylist.h b/examples/multimedia/player/qmediaplaylist.h
new file mode 100644
index 000000000..94846d9b7
--- /dev/null
+++ b/examples/multimedia/player/qmediaplaylist.h
@@ -0,0 +1,96 @@
+// 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
+
+#ifndef QMEDIAPLAYLIST_H
+#define QMEDIAPLAYLIST_H
+
+#include <QtCore/qobject.h>
+
+#include <QtMultimedia/qtmultimediaglobal.h>
+#include <QtMultimedia/qmediaenumdebug.h>
+
+
+QT_BEGIN_NAMESPACE
+
+class QMediaPlaylistPrivate;
+class QMediaPlaylist : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QMediaPlaylist::PlaybackMode playbackMode READ playbackMode WRITE setPlaybackMode NOTIFY playbackModeChanged)
+ Q_PROPERTY(QUrl currentMedia READ currentMedia NOTIFY currentMediaChanged)
+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+
+public:
+ enum PlaybackMode { CurrentItemOnce, CurrentItemInLoop, Sequential, Loop };
+ Q_ENUM(PlaybackMode)
+ enum Error { NoError, FormatError, FormatNotSupportedError, NetworkError, AccessDeniedError };
+ Q_ENUM(Error)
+
+ explicit QMediaPlaylist(QObject *parent = nullptr);
+ virtual ~QMediaPlaylist();
+
+ PlaybackMode playbackMode() const;
+ void setPlaybackMode(PlaybackMode mode);
+
+ int currentIndex() const;
+ QUrl currentMedia() const;
+
+ int nextIndex(int steps = 1) const;
+ int previousIndex(int steps = 1) const;
+
+ QUrl media(int index) const;
+
+ int mediaCount() const;
+ bool isEmpty() const;
+
+ void addMedia(const QUrl &content);
+ void addMedia(const QList<QUrl> &items);
+ bool insertMedia(int index, const QUrl &content);
+ bool insertMedia(int index, const QList<QUrl> &items);
+ bool moveMedia(int from, int to);
+ bool removeMedia(int pos);
+ bool removeMedia(int start, int end);
+ void clear();
+
+ void load(const QUrl &location, const char *format = nullptr);
+ void load(QIODevice *device, const char *format = nullptr);
+
+ bool save(const QUrl &location, const char *format = nullptr) const;
+ bool save(QIODevice *device, const char *format) const;
+
+ Error error() const;
+ QString errorString() const;
+
+public Q_SLOTS:
+ void shuffle();
+
+ void next();
+ void previous();
+
+ void setCurrentIndex(int index);
+
+Q_SIGNALS:
+ void currentIndexChanged(int index);
+ void playbackModeChanged(QMediaPlaylist::PlaybackMode mode);
+ void currentMediaChanged(const QUrl&);
+
+ void mediaAboutToBeInserted(int start, int end);
+ void mediaInserted(int start, int end);
+ void mediaAboutToBeRemoved(int start, int end);
+ void mediaRemoved(int start, int end);
+ void mediaChanged(int start, int end);
+
+ void loaded();
+ void loadFailed();
+
+private:
+ QMediaPlaylistPrivate *d_ptr;
+ Q_DECLARE_PRIVATE(QMediaPlaylist)
+};
+
+QT_END_NAMESPACE
+
+Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, PlaybackMode)
+Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, Error)
+
+#endif // QMEDIAPLAYLIST_H
diff --git a/examples/multimedia/player/qmediaplaylist_p.h b/examples/multimedia/player/qmediaplaylist_p.h
new file mode 100644
index 000000000..b0a6609c7
--- /dev/null
+++ b/examples/multimedia/player/qmediaplaylist_p.h
@@ -0,0 +1,112 @@
+// 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
+
+#ifndef QMEDIAPLAYLIST_P_H
+#define QMEDIAPLAYLIST_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 "qmediaplaylist.h"
+#include "qplaylistfileparser_p.h"
+
+#include <QtCore/qdebug.h>
+
+#ifdef Q_MOC_RUN
+# pragma Q_MOC_EXPAND_MACROS
+#endif
+
+QT_BEGIN_NAMESPACE
+
+
+class QMediaPlaylistControl;
+
+class QMediaPlaylistPrivate
+{
+ Q_DECLARE_PUBLIC(QMediaPlaylist)
+public:
+ QMediaPlaylistPrivate()
+ : error(QMediaPlaylist::NoError)
+ {
+ }
+
+ virtual ~QMediaPlaylistPrivate()
+ {
+ if (parser)
+ delete parser;
+ }
+
+ void loadFailed(QMediaPlaylist::Error error, const QString &errorString)
+ {
+ this->error = error;
+ this->errorString = errorString;
+
+ emit q_ptr->loadFailed();
+ }
+
+ void loadFinished()
+ {
+ q_ptr->addMedia(parser->playlist);
+
+ emit q_ptr->loaded();
+ }
+
+ bool checkFormat(const char *format) const
+ {
+ QLatin1String f(format);
+ QPlaylistFileParser::FileType type = format ? QPlaylistFileParser::UNKNOWN : QPlaylistFileParser::M3U8;
+ if (format) {
+ if (f == QLatin1String("m3u") || f == QLatin1String("text/uri-list") ||
+ f == QLatin1String("audio/x-mpegurl") || f == QLatin1String("audio/mpegurl"))
+ type = QPlaylistFileParser::M3U;
+ else if (f == QLatin1String("m3u8") || f == QLatin1String("application/x-mpegURL") ||
+ f == QLatin1String("application/vnd.apple.mpegurl"))
+ type = QPlaylistFileParser::M3U8;
+ }
+
+ if (type == QPlaylistFileParser::UNKNOWN || type == QPlaylistFileParser::PLS) {
+ error = QMediaPlaylist::FormatNotSupportedError;
+ errorString = QMediaPlaylist::tr("This file format is not supported.");
+ return false;
+ }
+ return true;
+ }
+
+ void ensureParser()
+ {
+ if (parser)
+ return;
+
+ parser = new QPlaylistFileParser(q_ptr);
+ QObject::connect(parser, &QPlaylistFileParser::finished, [this]() { loadFinished(); });
+ QObject::connect(parser, &QPlaylistFileParser::error,
+ [this](QMediaPlaylist::Error err, const QString& errorMsg) { loadFailed(err, errorMsg); });
+ }
+
+ int nextPosition(int steps) const;
+ int prevPosition(int steps) const;
+
+ QList<QUrl> playlist;
+
+ int currentPos = -1;
+ QMediaPlaylist::PlaybackMode playbackMode = QMediaPlaylist::Sequential;
+
+ QPlaylistFileParser *parser = nullptr;
+ mutable QMediaPlaylist::Error error;
+ mutable QString errorString;
+
+ QMediaPlaylist *q_ptr;
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QMEDIAPLAYLIST_P_H
diff --git a/examples/multimedia/player/qplaylistfileparser.cpp b/examples/multimedia/player/qplaylistfileparser.cpp
new file mode 100644
index 000000000..698f81ddc
--- /dev/null
+++ b/examples/multimedia/player/qplaylistfileparser.cpp
@@ -0,0 +1,605 @@
+// 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 "qplaylistfileparser_p.h"
+#include <qfileinfo.h>
+#include <QtCore/QDebug>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qpointer.h>
+#include <QtNetwork/QNetworkReply>
+#include <QtNetwork/QNetworkRequest>
+#include "qmediaplayer.h"
+#include "qmediametadata.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+class ParserBase
+{
+public:
+ explicit ParserBase(QPlaylistFileParser *parent)
+ : m_parent(parent)
+ , m_aborted(false)
+ {
+ Q_ASSERT(m_parent);
+ }
+
+ bool parseLine(int lineIndex, const QString& line, const QUrl& root)
+ {
+ if (m_aborted)
+ return false;
+
+ const bool ok = parseLineImpl(lineIndex, line, root);
+ return ok && !m_aborted;
+ }
+
+ virtual void abort() { m_aborted = true; }
+ virtual ~ParserBase() = default;
+
+protected:
+ virtual bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) = 0;
+
+ static QUrl expandToFullPath(const QUrl &root, const QString &line)
+ {
+ // On Linux, backslashes are not converted to forward slashes :/
+ if (line.startsWith(QLatin1String("//")) || line.startsWith(QLatin1String("\\\\"))) {
+ // Network share paths are not resolved
+ return QUrl::fromLocalFile(line);
+ }
+
+ QUrl url(line);
+ if (url.scheme().isEmpty()) {
+ // Resolve it relative to root
+ if (root.isLocalFile())
+ return QUrl::fromUserInput(line, root.adjusted(QUrl::RemoveFilename).toLocalFile(), QUrl::AssumeLocalFile);
+ return root.resolved(url);
+ }
+ if (url.scheme().length() == 1)
+ // Assume it's a drive letter for a Windows path
+ url = QUrl::fromLocalFile(line);
+
+ return url;
+ }
+
+ void newItemFound(const QVariant& content) { Q_EMIT m_parent->newItem(content); }
+
+
+ QPlaylistFileParser *m_parent;
+ bool m_aborted;
+};
+
+class M3UParser : public ParserBase
+{
+public:
+ explicit M3UParser(QPlaylistFileParser *q)
+ : ParserBase(q)
+ , m_extendedFormat(false)
+ {
+ }
+
+ /*
+ *
+ Extended M3U directives
+
+ #EXTM3U - header - must be first line of file
+ #EXTINF - extra info - length (seconds), title
+ #EXTINF - extra info - length (seconds), artist '-' title
+
+ Example
+
+ #EXTM3U
+ #EXTINF:123, Sample artist - Sample title
+ C:\Documents and Settings\I\My Music\Sample.mp3
+ #EXTINF:321,Example Artist - Example title
+ C:\Documents and Settings\I\My Music\Greatest Hits\Example.ogg
+
+ */
+ bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) override
+ {
+ if (line[0] == u'#' ) {
+ if (m_extendedFormat) {
+ if (line.startsWith(QLatin1String("#EXTINF:"))) {
+ m_extraInfo.clear();
+ int artistStart = line.indexOf(QLatin1String(","), 8);
+ bool ok = false;
+ QStringView lineView { line };
+ int length = lineView.mid(8, artistStart < 8 ? -1 : artistStart - 8).trimmed().toInt(&ok);
+ if (ok && length > 0) {
+ //convert from second to milisecond
+ m_extraInfo[QMediaMetaData::Duration] = QVariant(length * 1000);
+ }
+ if (artistStart > 0) {
+ int titleStart = getSplitIndex(line, artistStart);
+ if (titleStart > artistStart) {
+ m_extraInfo[QMediaMetaData::Author] = lineView.mid(artistStart + 1,
+ titleStart - artistStart - 1).trimmed().toString().
+ replace(QLatin1String("--"), QLatin1String("-"));
+ m_extraInfo[QMediaMetaData::Title] = lineView.mid(titleStart + 1).trimmed().toString().
+ replace(QLatin1String("--"), QLatin1String("-"));
+ } else {
+ m_extraInfo[QMediaMetaData::Title] = lineView.mid(artistStart + 1).trimmed().toString().
+ replace(QLatin1String("--"), QLatin1String("-"));
+ }
+ }
+ }
+ } else if (lineIndex == 0 && line.startsWith(QLatin1String("#EXTM3U"))) {
+ m_extendedFormat = true;
+ }
+ } else {
+ QUrl url = expandToFullPath(root, line);
+ m_extraInfo[QMediaMetaData::Url] = url;
+ m_parent->playlist.append(url);
+ newItemFound(QVariant::fromValue(m_extraInfo));
+ m_extraInfo.clear();
+ }
+
+ return true;
+ }
+
+ int getSplitIndex(const QString& line, int startPos)
+ {
+ if (startPos < 0)
+ startPos = 0;
+ const QChar* buf = line.data();
+ for (int i = startPos; i < line.length(); ++i) {
+ if (buf[i] == u'-') {
+ if (i == line.length() - 1)
+ return i;
+ ++i;
+ if (buf[i] != u'-')
+ return i - 1;
+ }
+ }
+ return -1;
+ }
+
+private:
+ QMediaMetaData m_extraInfo;
+ bool m_extendedFormat;
+};
+
+class PLSParser : public ParserBase
+{
+public:
+ explicit PLSParser(QPlaylistFileParser *q)
+ : ParserBase(q)
+ {
+ }
+
+/*
+ *
+The format is essentially that of an INI file structured as follows:
+
+Header
+
+ * [playlist] : This tag indicates that it is a Playlist File
+
+Track Entry
+Assuming track entry #X
+
+ * FileX : Variable defining location of stream.
+ * TitleX : Defines track title.
+ * LengthX : Length in seconds of track. Value of -1 indicates indefinite.
+
+Footer
+
+ * NumberOfEntries : This variable indicates the number of tracks.
+ * Version : Playlist version. Currently only a value of 2 is valid.
+
+[playlist]
+
+File1=Alternative\everclear - SMFTA.mp3
+
+Title1=Everclear - So Much For The Afterglow
+
+Length1=233
+
+File2=http://www.site.com:8000/listen.pls
+
+Title2=My Cool Stream
+
+Length5=-1
+
+NumberOfEntries=2
+
+Version=2
+*/
+ bool parseLineImpl(int, const QString &line, const QUrl &root) override
+ {
+ // We ignore everything but 'File' entries, since that's the only thing we care about.
+ if (!line.startsWith(QLatin1String("File")))
+ return true;
+
+ QString value = getValue(line);
+ if (value.isEmpty())
+ return true;
+
+ QUrl path = expandToFullPath(root, value);
+ m_parent->playlist.append(path);
+ newItemFound(path);
+
+ return true;
+ }
+
+ QString getValue(QStringView line) {
+ int start = line.indexOf(u'=');
+ if (start < 0)
+ return QString();
+ return line.mid(start + 1).trimmed().toString();
+ }
+};
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+class QPlaylistFileParserPrivate
+{
+ Q_DECLARE_PUBLIC(QPlaylistFileParser)
+public:
+ QPlaylistFileParserPrivate(QPlaylistFileParser *q)
+ : q_ptr(q)
+ , m_stream(nullptr)
+ , m_type(QPlaylistFileParser::UNKNOWN)
+ , m_scanIndex(0)
+ , m_lineIndex(-1)
+ , m_utf8(false)
+ , m_aborted(false)
+ {
+ }
+
+ void handleData();
+ void handleParserFinished();
+ void abort();
+ void reset();
+
+ QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> m_source;
+ QScopedPointer<ParserBase> m_currentParser;
+ QByteArray m_buffer;
+ QUrl m_root;
+ QNetworkAccessManager m_mgr;
+ QString m_mimeType;
+ QPlaylistFileParser *q_ptr;
+ QPointer<QIODevice> m_stream;
+ QPlaylistFileParser::FileType m_type;
+ struct ParserJob
+ {
+ QIODevice *m_stream;
+ QUrl m_media;
+ QString m_mimeType;
+ [[nodiscard]] bool isValid() const { return m_stream || !m_media.isEmpty(); }
+ void reset() { m_stream = nullptr; m_media = QUrl(); m_mimeType = QString(); }
+ } m_pendingJob;
+ int m_scanIndex;
+ int m_lineIndex;
+ bool m_utf8;
+ bool m_aborted;
+
+private:
+ bool processLine(int startIndex, int length);
+};
+
+#define LINE_LIMIT 4096
+#define READ_LIMIT 64
+
+bool QPlaylistFileParserPrivate::processLine(int startIndex, int length)
+{
+ Q_Q(QPlaylistFileParser);
+ m_lineIndex++;
+
+ if (!m_currentParser) {
+ const QString urlString = m_root.toString();
+ const QString &suffix = !urlString.isEmpty() ? QFileInfo(urlString).suffix() : urlString;
+ QString mimeType;
+ if (m_source)
+ mimeType = m_source->header(QNetworkRequest::ContentTypeHeader).toString();
+ m_type = QPlaylistFileParser::findPlaylistType(suffix, !mimeType.isEmpty() ? mimeType : m_mimeType, m_buffer.constData(), quint32(m_buffer.size()));
+
+ switch (m_type) {
+ case QPlaylistFileParser::UNKNOWN:
+ emit q->error(QMediaPlaylist::FormatError,
+ QMediaPlaylist::tr("%1 playlist type is unknown").arg(m_root.toString()));
+ q->abort();
+ return false;
+ case QPlaylistFileParser::M3U:
+ m_currentParser.reset(new M3UParser(q));
+ break;
+ case QPlaylistFileParser::M3U8:
+ m_currentParser.reset(new M3UParser(q));
+ m_utf8 = true;
+ break;
+ case QPlaylistFileParser::PLS:
+ m_currentParser.reset(new PLSParser(q));
+ break;
+ }
+
+ Q_ASSERT(!m_currentParser.isNull());
+ }
+
+ QString line;
+
+ if (m_utf8) {
+ line = QString::fromUtf8(m_buffer.constData() + startIndex, length).trimmed();
+ } else {
+ line = QString::fromLatin1(m_buffer.constData() + startIndex, length).trimmed();
+ }
+ if (line.isEmpty())
+ return true;
+
+ Q_ASSERT(m_currentParser);
+ return m_currentParser->parseLine(m_lineIndex, line, m_root);
+}
+
+void QPlaylistFileParserPrivate::handleData()
+{
+ Q_Q(QPlaylistFileParser);
+ while (m_stream->bytesAvailable() && !m_aborted) {
+ int expectedBytes = qMin(READ_LIMIT, int(qMin(m_stream->bytesAvailable(),
+ qint64(LINE_LIMIT - m_buffer.size()))));
+ m_buffer.push_back(m_stream->read(expectedBytes));
+ int processedBytes = 0;
+ while (m_scanIndex < m_buffer.length() && !m_aborted) {
+ char s = m_buffer[m_scanIndex];
+ if (s == '\r' || s == '\n') {
+ int l = m_scanIndex - processedBytes;
+ if (l > 0) {
+ if (!processLine(processedBytes, l))
+ break;
+ }
+ processedBytes = m_scanIndex + 1;
+ if (!m_stream) {
+ //some error happened, so exit parsing
+ return;
+ }
+ }
+ m_scanIndex++;
+ }
+
+ if (m_aborted)
+ break;
+
+ if (m_buffer.length() - processedBytes >= LINE_LIMIT) {
+ emit q->error(QMediaPlaylist::FormatError, QMediaPlaylist::tr("invalid line in playlist file"));
+ q->abort();
+ break;
+ }
+
+ if (!m_stream->bytesAvailable() && (!m_source || !m_source->isFinished())) {
+ //last line
+ processLine(processedBytes, -1);
+ break;
+ }
+
+ Q_ASSERT(m_buffer.length() == m_scanIndex);
+ if (processedBytes == 0)
+ continue;
+
+ int copyLength = m_buffer.length() - processedBytes;
+ if (copyLength > 0) {
+ Q_ASSERT(copyLength <= READ_LIMIT);
+ m_buffer = m_buffer.right(copyLength);
+ } else {
+ m_buffer.clear();
+ }
+ m_scanIndex = 0;
+ }
+
+ handleParserFinished();
+}
+
+QPlaylistFileParser::QPlaylistFileParser(QObject *parent)
+ : QObject(parent)
+ , d_ptr(new QPlaylistFileParserPrivate(this))
+{
+
+}
+
+QPlaylistFileParser::~QPlaylistFileParser() = default;
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findByMimeType(const QString &mime)
+{
+ if (mime == QLatin1String("text/uri-list") || mime == QLatin1String("audio/x-mpegurl") || mime == QLatin1String("audio/mpegurl"))
+ return QPlaylistFileParser::M3U;
+
+ if (mime == QLatin1String("application/x-mpegURL") || mime == QLatin1String("application/vnd.apple.mpegurl"))
+ return QPlaylistFileParser::M3U8;
+
+ if (mime == QLatin1String("audio/x-scpls"))
+ return QPlaylistFileParser::PLS;
+
+ return QPlaylistFileParser::UNKNOWN;
+}
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findBySuffixType(const QString &suffix)
+{
+ const QString &s = suffix.toLower();
+
+ if (s == QLatin1String("m3u"))
+ return QPlaylistFileParser::M3U;
+
+ if (s == QLatin1String("m3u8"))
+ return QPlaylistFileParser::M3U8;
+
+ if (s == QLatin1String("pls"))
+ return QPlaylistFileParser::PLS;
+
+ return QPlaylistFileParser::UNKNOWN;
+}
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findByDataHeader(const char *data, quint32 size)
+{
+ if (!data || size == 0)
+ return QPlaylistFileParser::UNKNOWN;
+
+ if (size >= 7 && strncmp(data, "#EXTM3U", 7) == 0)
+ return QPlaylistFileParser::M3U;
+
+ if (size >= 10 && strncmp(data, "[playlist]", 10) == 0)
+ return QPlaylistFileParser::PLS;
+
+ return QPlaylistFileParser::UNKNOWN;
+}
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findPlaylistType(const QString& suffix,
+ const QString& mime,
+ const char *data,
+ quint32 size)
+{
+
+ FileType dataHeaderType = findByDataHeader(data, size);
+ if (dataHeaderType != UNKNOWN)
+ return dataHeaderType;
+
+ FileType mimeType = findByMimeType(mime);
+ if (mimeType != UNKNOWN)
+ return mimeType;
+
+ mimeType = findBySuffixType(mime);
+ if (mimeType != UNKNOWN)
+ return mimeType;
+
+ FileType suffixType = findBySuffixType(suffix);
+ if (suffixType != UNKNOWN)
+ return suffixType;
+
+ return UNKNOWN;
+}
+
+/*
+ * Delegating
+ */
+void QPlaylistFileParser::start(const QUrl &media, QIODevice *stream, const QString &mimeType)
+{
+ if (stream)
+ start(stream, mimeType);
+ else
+ start(media, mimeType);
+}
+
+void QPlaylistFileParser::start(QIODevice *stream, const QString &mimeType)
+{
+ Q_D(QPlaylistFileParser);
+ const bool validStream = stream ? (stream->isOpen() && stream->isReadable()) : false;
+
+ if (!validStream) {
+ Q_EMIT error(QMediaPlaylist::AccessDeniedError, QMediaPlaylist::tr("Invalid stream"));
+ return;
+ }
+
+ if (!d->m_currentParser.isNull()) {
+ abort();
+ d->m_pendingJob = { stream, QUrl(), mimeType };
+ return;
+ }
+
+ playlist.clear();
+ d->reset();
+ d->m_mimeType = mimeType;
+ d->m_stream = stream;
+ connect(d->m_stream, SIGNAL(readyRead()), this, SLOT(handleData()));
+ d->handleData();
+}
+
+void QPlaylistFileParser::start(const QUrl& request, const QString &mimeType)
+{
+ Q_D(QPlaylistFileParser);
+ const QUrl &url = request.url();
+
+ if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) {
+ emit error(QMediaPlaylist::AccessDeniedError, QString(QMediaPlaylist::tr("%1 does not exist")).arg(url.toString()));
+ return;
+ }
+
+ if (!d->m_currentParser.isNull()) {
+ abort();
+ d->m_pendingJob = { nullptr, request, mimeType };
+ return;
+ }
+
+ d->reset();
+ d->m_root = url;
+ d->m_mimeType = mimeType;
+ d->m_source.reset(d->m_mgr.get(QNetworkRequest(request)));
+ d->m_stream = d->m_source.get();
+ connect(d->m_source.data(), SIGNAL(readyRead()), this, SLOT(handleData()));
+ connect(d->m_source.data(), SIGNAL(finished()), this, SLOT(handleData()));
+ connect(d->m_source.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(handleError()));
+
+ if (url.isLocalFile())
+ d->handleData();
+}
+
+void QPlaylistFileParser::abort()
+{
+ Q_D(QPlaylistFileParser);
+ d->abort();
+
+ if (d->m_source)
+ d->m_source->disconnect();
+
+ if (d->m_stream)
+ disconnect(d->m_stream, SIGNAL(readyRead()), this, SLOT(handleData()));
+
+ playlist.clear();
+}
+
+void QPlaylistFileParser::handleData()
+{
+ Q_D(QPlaylistFileParser);
+ d->handleData();
+}
+
+void QPlaylistFileParserPrivate::handleParserFinished()
+{
+ Q_Q(QPlaylistFileParser);
+ const bool isParserValid = !m_currentParser.isNull();
+ if (!isParserValid && !m_aborted)
+ emit q->error(QMediaPlaylist::FormatNotSupportedError, QMediaPlaylist::tr("Empty file provided"));
+
+ if (isParserValid && !m_aborted) {
+ m_currentParser.reset();
+ emit q->finished();
+ }
+
+ if (!m_aborted)
+ q->abort();
+
+ if (!m_source.isNull())
+ m_source.reset();
+
+ if (m_pendingJob.isValid())
+ q->start(m_pendingJob.m_media, m_pendingJob.m_stream, m_pendingJob.m_mimeType);
+}
+
+void QPlaylistFileParserPrivate::abort()
+{
+ m_aborted = true;
+ if (!m_currentParser.isNull())
+ m_currentParser->abort();
+}
+
+void QPlaylistFileParserPrivate::reset()
+{
+ Q_ASSERT(m_currentParser.isNull());
+ Q_ASSERT(m_source.isNull());
+ m_buffer.clear();
+ m_root.clear();
+ m_mimeType.clear();
+ m_stream = nullptr;
+ m_type = QPlaylistFileParser::UNKNOWN;
+ m_scanIndex = 0;
+ m_lineIndex = -1;
+ m_utf8 = false;
+ m_aborted = false;
+ m_pendingJob.reset();
+}
+
+void QPlaylistFileParser::handleError()
+{
+ Q_D(QPlaylistFileParser);
+ const QString &errorString = d->m_source->errorString();
+ Q_EMIT error(QMediaPlaylist::NetworkError, errorString);
+ abort();
+}
+
+QT_END_NAMESPACE
diff --git a/examples/multimedia/player/qplaylistfileparser_p.h b/examples/multimedia/player/qplaylistfileparser_p.h
new file mode 100644
index 000000000..3d2016736
--- /dev/null
+++ b/examples/multimedia/player/qplaylistfileparser_p.h
@@ -0,0 +1,80 @@
+// 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
+
+#ifndef PLAYLISTFILEPARSER_P_H
+#define PLAYLISTFILEPARSER_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 "qtmultimediaglobal.h"
+#include "qmediaplaylist.h"
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class QIODevice;
+class QUrl;
+class QNetworkRequest;
+
+class QPlaylistFileParserPrivate;
+
+class QPlaylistFileParser : public QObject
+{
+ Q_OBJECT
+public:
+ QPlaylistFileParser(QObject *parent = nullptr);
+ ~QPlaylistFileParser();
+
+ enum FileType
+ {
+ UNKNOWN,
+ M3U,
+ M3U8, // UTF-8 version of M3U
+ PLS
+ };
+
+ void start(const QUrl &media, QIODevice *stream = nullptr, const QString &mimeType = QString());
+ void start(const QUrl &request, const QString &mimeType = QString());
+ void start(QIODevice *stream, const QString &mimeType = QString());
+ void abort();
+
+ QList<QUrl> playlist;
+
+Q_SIGNALS:
+ void newItem(const QVariant& content);
+ void finished();
+ void error(QMediaPlaylist::Error err, const QString& errorMsg);
+
+private Q_SLOTS:
+ void handleData();
+ void handleError();
+
+private:
+
+ static FileType findByMimeType(const QString &mime);
+ static FileType findBySuffixType(const QString &suffix);
+ static FileType findByDataHeader(const char *data, quint32 size);
+ static FileType findPlaylistType(QIODevice *device,
+ const QString& mime);
+ static FileType findPlaylistType(const QString &suffix,
+ const QString& mime,
+ const char *data = nullptr,
+ quint32 size = 0);
+
+ Q_DISABLE_COPY(QPlaylistFileParser)
+ Q_DECLARE_PRIVATE(QPlaylistFileParser)
+ QScopedPointer<QPlaylistFileParserPrivate> d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // PLAYLISTFILEPARSER_P_H
diff --git a/examples/multimedia/player/videowidget.cpp b/examples/multimedia/player/videowidget.cpp
new file mode 100644
index 000000000..e7f906e38
--- /dev/null
+++ b/examples/multimedia/player/videowidget.cpp
@@ -0,0 +1,46 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "videowidget.h"
+
+#include <QKeyEvent>
+#include <QMouseEvent>
+
+VideoWidget::VideoWidget(QWidget *parent)
+ : QVideoWidget(parent)
+{
+ setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
+
+ QPalette p = palette();
+ p.setColor(QPalette::Window, Qt::black);
+ setPalette(p);
+
+#ifndef Q_OS_ANDROID // QTBUG-95723
+ setAttribute(Qt::WA_OpaquePaintEvent);
+#endif
+}
+
+void VideoWidget::keyPressEvent(QKeyEvent *event)
+{
+ if ((event->key() == Qt::Key_Escape || event->key() == Qt::Key_Back) && isFullScreen()) {
+ setFullScreen(false);
+ event->accept();
+ } else if (event->key() == Qt::Key_Enter && event->modifiers() & Qt::Key_Alt) {
+ setFullScreen(!isFullScreen());
+ event->accept();
+ } else {
+ QVideoWidget::keyPressEvent(event);
+ }
+}
+
+void VideoWidget::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ setFullScreen(!isFullScreen());
+ event->accept();
+}
+
+void VideoWidget::mousePressEvent(QMouseEvent *event)
+{
+ QVideoWidget::mousePressEvent(event);
+}
+
diff --git a/examples/multimedia/player/videowidget.h b/examples/multimedia/player/videowidget.h
new file mode 100644
index 000000000..3505a3fb8
--- /dev/null
+++ b/examples/multimedia/player/videowidget.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef VIDEOWIDGET_H
+#define VIDEOWIDGET_H
+
+#include <QVideoWidget>
+
+class VideoWidget : public QVideoWidget
+{
+ Q_OBJECT
+
+public:
+ explicit VideoWidget(QWidget *parent = nullptr);
+
+protected:
+ void keyPressEvent(QKeyEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+};
+
+#endif // VIDEOWIDGET_H
diff --git a/examples/multimedia/videographicsitem/CMakeLists.txt b/examples/multimedia/videographicsitem/CMakeLists.txt
new file mode 100644
index 000000000..653627c25
--- /dev/null
+++ b/examples/multimedia/videographicsitem/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(videographicsitem LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/videographicsitem")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia MultimediaWidgets Widgets)
+
+qt_add_executable(videographicsitem
+ main.cpp
+ videoplayer.cpp videoplayer.h
+)
+
+set_target_properties(videographicsitem PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(videographicsitem PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Multimedia
+ Qt::MultimediaWidgets
+ Qt::Widgets
+)
+
+install(TARGETS videographicsitem
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/multimedia/videographicsitem/doc/images/video-videographicsitem.png b/examples/multimedia/videographicsitem/doc/images/video-videographicsitem.png
new file mode 100644
index 000000000..e333c54a2
--- /dev/null
+++ b/examples/multimedia/videographicsitem/doc/images/video-videographicsitem.png
Binary files differ
diff --git a/examples/multimedia/videographicsitem/doc/src/videographicsitem.qdoc b/examples/multimedia/videographicsitem/doc/src/videographicsitem.qdoc
new file mode 100644
index 000000000..5dcb7143e
--- /dev/null
+++ b/examples/multimedia/videographicsitem/doc/src/videographicsitem.qdoc
@@ -0,0 +1,19 @@
+// Copyright (C) 2015 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example videographicsitem
+ \title Video Graphics Item Example
+ \ingroup multimedia_examples
+ \brief Streaming video on a graphics scene.
+ \meta {tag} {widgets}
+
+ \e{Video Graphics Item} demonstrates how to implement a QGraphicsItem that
+ displays video on a graphics scene using QVideoSink.
+
+ \image video-videographicsitem.png
+
+ \sa {Video Widget Example}
+
+ \include examples-run.qdocinc
+*/
diff --git a/examples/multimedia/videographicsitem/main.cpp b/examples/multimedia/videographicsitem/main.cpp
new file mode 100644
index 000000000..85d1ec92b
--- /dev/null
+++ b/examples/multimedia/videographicsitem/main.cpp
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "videoplayer.h"
+
+#include <QApplication>
+#include <QCommandLineParser>
+#include <QCommandLineOption>
+#include <QDir>
+#include <QUrl>
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ QCoreApplication::setApplicationName("Player Example");
+ QCoreApplication::setOrganizationName("QtProject");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Qt MultiMedia Player QGraphicsView Example");
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument("url", "The URL to open.");
+ parser.process(app);
+
+ VideoPlayer player;
+
+ if (!parser.positionalArguments().isEmpty() && player.isPlayerAvailable()) {
+ const QUrl url =
+ QUrl::fromUserInput(parser.positionalArguments().constFirst(),
+ QDir::currentPath(), QUrl::AssumeLocalFile);
+ player.load(url);
+ }
+
+ player.show();
+
+ return app.exec();
+}
+
diff --git a/examples/multimedia/videographicsitem/videographicsitem.pro b/examples/multimedia/videographicsitem/videographicsitem.pro
new file mode 100644
index 000000000..3415ef64c
--- /dev/null
+++ b/examples/multimedia/videographicsitem/videographicsitem.pro
@@ -0,0 +1,14 @@
+TEMPLATE = app
+TARGET = videographicsitem
+
+QT += multimedia multimediawidgets
+
+HEADERS += videoplayer.h
+
+SOURCES += main.cpp \
+ videoplayer.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/videographicsitem
+INSTALLS += target
+
+QT+=widgets
diff --git a/examples/multimedia/videographicsitem/videoplayer.cpp b/examples/multimedia/videographicsitem/videoplayer.cpp
new file mode 100644
index 000000000..096fabd77
--- /dev/null
+++ b/examples/multimedia/videographicsitem/videoplayer.cpp
@@ -0,0 +1,139 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "videoplayer.h"
+
+#include <QtWidgets>
+#include <QGraphicsVideoItem>
+
+VideoPlayer::VideoPlayer(QWidget *parent)
+ : QWidget(parent)
+{
+ m_mediaPlayer = new QMediaPlayer(this);
+ const QSize screenGeometry = screen()->availableSize();
+ m_videoItem = new QGraphicsVideoItem;
+ m_videoItem->setSize(QSizeF(screenGeometry.width() / 3, screenGeometry.height() / 2));
+
+ QGraphicsScene *scene = new QGraphicsScene(this);
+ QGraphicsView *graphicsView = new QGraphicsView(scene);
+
+ scene->addItem(m_videoItem);
+
+ QSlider *rotateSlider = new QSlider(Qt::Horizontal);
+ rotateSlider->setToolTip(tr("Rotate Video"));
+ rotateSlider->setRange(-180, 180);
+ rotateSlider->setValue(0);
+
+ connect(rotateSlider, &QAbstractSlider::valueChanged,
+ this, &VideoPlayer::rotateVideo);
+
+ QAbstractButton *openButton = new QPushButton(tr("Open..."));
+ connect(openButton, &QAbstractButton::clicked, this, &VideoPlayer::openFile);
+
+ m_playButton = new QPushButton;
+ m_playButton->setEnabled(false);
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
+
+ connect(m_playButton, &QAbstractButton::clicked, this, &VideoPlayer::play);
+
+ m_positionSlider = new QSlider(Qt::Horizontal);
+ m_positionSlider->setRange(0, 0);
+
+ connect(m_positionSlider, &QAbstractSlider::sliderMoved,
+ this, &VideoPlayer::setPosition);
+
+ QBoxLayout *controlLayout = new QHBoxLayout;
+ controlLayout->setContentsMargins(0, 0, 0, 0);
+ controlLayout->addWidget(openButton);
+ controlLayout->addWidget(m_playButton);
+ controlLayout->addWidget(m_positionSlider);
+
+ QBoxLayout *layout = new QVBoxLayout(this);
+ layout->addWidget(graphicsView);
+ layout->addWidget(rotateSlider);
+ layout->addLayout(controlLayout);
+
+ m_mediaPlayer->setVideoOutput(m_videoItem);
+ connect(m_mediaPlayer, &QMediaPlayer::playbackStateChanged,
+ this, &VideoPlayer::mediaStateChanged);
+ connect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, &VideoPlayer::positionChanged);
+ connect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, &VideoPlayer::durationChanged);
+}
+
+VideoPlayer::~VideoPlayer()
+{
+}
+
+QSize VideoPlayer::sizeHint() const
+{
+ return (m_videoItem->size() * qreal(3) / qreal(2)).toSize();
+}
+
+bool VideoPlayer::isPlayerAvailable() const
+{
+ return m_mediaPlayer->isAvailable();
+}
+
+void VideoPlayer::openFile()
+{
+ QFileDialog fileDialog(this);
+ fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
+ fileDialog.setWindowTitle(tr("Open Movie"));
+ fileDialog.setDirectory(QStandardPaths::standardLocations(QStandardPaths::MoviesLocation).value(0, QDir::homePath()));
+ if (fileDialog.exec() == QDialog::Accepted)
+ load(fileDialog.selectedUrls().constFirst());
+}
+
+void VideoPlayer::load(const QUrl &url)
+{
+ m_mediaPlayer->setSource(url);
+ m_playButton->setEnabled(true);
+}
+
+void VideoPlayer::play()
+{
+ switch (m_mediaPlayer->playbackState()) {
+ case QMediaPlayer::PlayingState:
+ m_mediaPlayer->pause();
+ break;
+ default:
+ m_mediaPlayer->play();
+ break;
+ }
+}
+
+void VideoPlayer::mediaStateChanged(QMediaPlayer::PlaybackState state)
+{
+ switch(state) {
+ case QMediaPlayer::PlayingState:
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause));
+ break;
+ default:
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
+ break;
+ }
+}
+
+void VideoPlayer::positionChanged(qint64 position)
+{
+ m_positionSlider->setValue(position);
+}
+
+void VideoPlayer::durationChanged(qint64 duration)
+{
+ m_positionSlider->setRange(0, duration);
+}
+
+void VideoPlayer::setPosition(int position)
+{
+ m_mediaPlayer->setPosition(position);
+}
+
+
+void VideoPlayer::rotateVideo(int angle)
+{
+ //rotate around the center of video element
+ qreal x = m_videoItem->boundingRect().width() / 2.0;
+ qreal y = m_videoItem->boundingRect().height() / 2.0;
+ m_videoItem->setTransform(QTransform().translate(x, y).rotate(angle).translate(-x, -y));
+}
diff --git a/examples/multimedia/videographicsitem/videoplayer.h b/examples/multimedia/videographicsitem/videoplayer.h
new file mode 100644
index 000000000..a5be31efc
--- /dev/null
+++ b/examples/multimedia/videographicsitem/videoplayer.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef VIDEOPLAYER_H
+#define VIDEOPLAYER_H
+
+#include <QMediaPlayer>
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+class QAbstractButton;
+class QSlider;
+class QGraphicsVideoItem;
+QT_END_NAMESPACE
+
+class VideoPlayer : public QWidget
+{
+ Q_OBJECT
+
+public:
+ VideoPlayer(QWidget *parent = nullptr);
+ ~VideoPlayer();
+
+ void load(const QUrl &url);
+ bool isPlayerAvailable() const;
+
+ QSize sizeHint() const override;
+
+public slots:
+ void openFile();
+ void play();
+
+private slots:
+ void mediaStateChanged(QMediaPlayer::PlaybackState state);
+ void positionChanged(qint64 position);
+ void durationChanged(qint64 duration);
+ void setPosition(int position);
+ void rotateVideo(int angle);
+
+private:
+ QMediaPlayer *m_mediaPlayer = nullptr;
+ QGraphicsVideoItem *m_videoItem = nullptr;
+ QAbstractButton *m_playButton = nullptr;
+ QSlider *m_positionSlider = nullptr;
+};
+
+#endif
+
diff --git a/examples/multimedia/videowidget/CMakeLists.txt b/examples/multimedia/videowidget/CMakeLists.txt
new file mode 100644
index 000000000..c33efeb0f
--- /dev/null
+++ b/examples/multimedia/videowidget/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(videowidget LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/videowidget")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia MultimediaWidgets Widgets)
+
+qt_add_executable(videowidget
+ main.cpp
+ videoplayer.cpp videoplayer.h
+)
+
+set_target_properties(videowidget PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(videowidget PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Multimedia
+ Qt::MultimediaWidgets
+ Qt::Widgets
+)
+
+install(TARGETS videowidget
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/multimedia/videowidget/doc/images/video-videowidget.png b/examples/multimedia/videowidget/doc/images/video-videowidget.png
new file mode 100644
index 000000000..a3c7bcb44
--- /dev/null
+++ b/examples/multimedia/videowidget/doc/images/video-videowidget.png
Binary files differ
diff --git a/examples/multimedia/videowidget/doc/src/videowidget.qdoc b/examples/multimedia/videowidget/doc/src/videowidget.qdoc
new file mode 100644
index 000000000..e999cc19c
--- /dev/null
+++ b/examples/multimedia/videowidget/doc/src/videowidget.qdoc
@@ -0,0 +1,17 @@
+// Copyright (C) 2015 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example videowidget
+ \title Video Widget Example
+ \ingroup multimedia_examples
+ \brief Implementing a video player widget.
+ \meta {tag} {widgets,QVideoWidget}
+
+ \e{Video Widget} demonstrates how to implement a simple video player using
+ QVideoWidget.
+
+ \image video-videowidget.png
+
+ \include examples-run.qdocinc
+*/
diff --git a/examples/multimedia/videowidget/main.cpp b/examples/multimedia/videowidget/main.cpp
new file mode 100644
index 000000000..ccf5983dd
--- /dev/null
+++ b/examples/multimedia/videowidget/main.cpp
@@ -0,0 +1,41 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "videoplayer.h"
+
+#include <QtWidgets/QApplication>
+#include <QtCore/QCommandLineParser>
+#include <QtCore/QCommandLineOption>
+#include <QtCore/QDir>
+#include <QtCore/QUrl>
+#include <QScreen>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QCoreApplication::setApplicationName("Video Widget Example");
+ QCoreApplication::setOrganizationName("QtProject");
+ QGuiApplication::setApplicationDisplayName(QCoreApplication::applicationName());
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Qt Video Widget Example");
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument("url", "The URL to open.");
+ parser.process(app);
+
+ VideoPlayer player;
+ if (!parser.positionalArguments().isEmpty()) {
+ const QUrl url =
+ QUrl::fromUserInput(parser.positionalArguments().constFirst(),
+ QDir::currentPath(), QUrl::AssumeLocalFile);
+ player.setUrl(url);
+ }
+
+ const QSize availableGeometry = player.screen()->availableSize();
+ player.resize(availableGeometry.width() / 6, availableGeometry.height() / 4);
+ player.show();
+
+ return app.exec();
+}
diff --git a/examples/multimedia/videowidget/videoplayer.cpp b/examples/multimedia/videowidget/videoplayer.cpp
new file mode 100644
index 000000000..4a34bfded
--- /dev/null
+++ b/examples/multimedia/videowidget/videoplayer.cpp
@@ -0,0 +1,130 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "videoplayer.h"
+
+#include <QtWidgets>
+#include <QVideoWidget>
+
+VideoPlayer::VideoPlayer(QWidget *parent)
+ : QWidget(parent)
+{
+ m_mediaPlayer = new QMediaPlayer(this);
+ QVideoWidget *videoWidget = new QVideoWidget;
+
+ QAbstractButton *openButton = new QPushButton(tr("Open..."));
+ connect(openButton, &QAbstractButton::clicked, this, &VideoPlayer::openFile);
+
+ m_playButton = new QPushButton;
+ m_playButton->setEnabled(false);
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
+
+ connect(m_playButton, &QAbstractButton::clicked,
+ this, &VideoPlayer::play);
+
+ m_positionSlider = new QSlider(Qt::Horizontal);
+ m_positionSlider->setRange(0, 0);
+
+ connect(m_positionSlider, &QAbstractSlider::sliderMoved,
+ this, &VideoPlayer::setPosition);
+
+ m_errorLabel = new QLabel;
+ m_errorLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
+
+ QBoxLayout *controlLayout = new QHBoxLayout;
+ controlLayout->setContentsMargins(0, 0, 0, 0);
+ controlLayout->addWidget(openButton);
+ controlLayout->addWidget(m_playButton);
+ controlLayout->addWidget(m_positionSlider);
+
+ QBoxLayout *layout = new QVBoxLayout;
+ layout->addWidget(videoWidget);
+ layout->addLayout(controlLayout);
+ layout->addWidget(m_errorLabel);
+
+ setLayout(layout);
+
+ m_mediaPlayer->setVideoOutput(videoWidget);
+ connect(m_mediaPlayer, &QMediaPlayer::playbackStateChanged,
+ this, &VideoPlayer::mediaStateChanged);
+ connect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, &VideoPlayer::positionChanged);
+ connect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, &VideoPlayer::durationChanged);
+ connect(m_mediaPlayer, &QMediaPlayer::errorChanged,
+ this, &VideoPlayer::handleError);
+}
+
+VideoPlayer::~VideoPlayer()
+{
+}
+
+void VideoPlayer::openFile()
+{
+ QFileDialog fileDialog(this);
+ fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
+ fileDialog.setWindowTitle(tr("Open Movie"));
+ fileDialog.setDirectory(QStandardPaths::standardLocations(QStandardPaths::MoviesLocation).value(0, QDir::homePath()));
+ if (fileDialog.exec() == QDialog::Accepted)
+ setUrl(fileDialog.selectedUrls().constFirst());
+}
+
+void VideoPlayer::setUrl(const QUrl &url)
+{
+ m_errorLabel->setText(QString());
+ setWindowFilePath(url.isLocalFile() ? url.toLocalFile() : QString());
+ m_mediaPlayer->setSource(url);
+ m_playButton->setEnabled(true);
+}
+
+void VideoPlayer::play()
+{
+ switch (m_mediaPlayer->playbackState()) {
+ case QMediaPlayer::PlayingState:
+ m_mediaPlayer->pause();
+ break;
+ default:
+ m_mediaPlayer->play();
+ break;
+ }
+}
+
+void VideoPlayer::mediaStateChanged(QMediaPlayer::PlaybackState state)
+{
+ switch(state) {
+ case QMediaPlayer::PlayingState:
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause));
+ break;
+ default:
+ m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
+ break;
+ }
+}
+
+void VideoPlayer::positionChanged(qint64 position)
+{
+ m_positionSlider->setValue(position);
+}
+
+void VideoPlayer::durationChanged(qint64 duration)
+{
+ m_positionSlider->setRange(0, duration);
+}
+
+void VideoPlayer::setPosition(int position)
+{
+ m_mediaPlayer->setPosition(position);
+}
+
+void VideoPlayer::handleError()
+{
+ if (m_mediaPlayer->error() == QMediaPlayer::NoError)
+ return;
+
+ m_playButton->setEnabled(false);
+ const QString errorString = m_mediaPlayer->errorString();
+ QString message = "Error: ";
+ if (errorString.isEmpty())
+ message += " #" + QString::number(int(m_mediaPlayer->error()));
+ else
+ message += errorString;
+ m_errorLabel->setText(message);
+}
diff --git a/examples/multimedia/videowidget/videoplayer.h b/examples/multimedia/videowidget/videoplayer.h
new file mode 100644
index 000000000..b06df8280
--- /dev/null
+++ b/examples/multimedia/videowidget/videoplayer.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef VIDEOPLAYER_H
+#define VIDEOPLAYER_H
+
+#include <QMediaPlayer>
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+class QAbstractButton;
+class QSlider;
+class QLabel;
+class QUrl;
+QT_END_NAMESPACE
+
+class VideoPlayer : public QWidget
+{
+ Q_OBJECT
+public:
+ VideoPlayer(QWidget *parent = nullptr);
+ ~VideoPlayer();
+
+ void setUrl(const QUrl &url);
+
+public slots:
+ void openFile();
+ void play();
+
+private slots:
+ void mediaStateChanged(QMediaPlayer::PlaybackState state);
+ void positionChanged(qint64 position);
+ void durationChanged(qint64 duration);
+ void setPosition(int position);
+ void handleError();
+
+private:
+ QMediaPlayer* m_mediaPlayer;
+ QAbstractButton *m_playButton;
+ QSlider *m_positionSlider;
+ QLabel *m_errorLabel;
+};
+
+#endif
diff --git a/examples/multimedia/videowidget/videowidget.pro b/examples/multimedia/videowidget/videowidget.pro
new file mode 100644
index 000000000..56312a028
--- /dev/null
+++ b/examples/multimedia/videowidget/videowidget.pro
@@ -0,0 +1,16 @@
+TEMPLATE = app
+TARGET = videowidget
+
+QT += multimedia multimediawidgets
+
+HEADERS = \
+ videoplayer.h
+
+SOURCES = \
+ main.cpp \
+ videoplayer.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/videowidget
+INSTALLS += target
+
+QT+=widgets