From ff2ec90494c3b0ae3368a1972fa00bb4423fc3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20K=C3=B6hne?= Date: Tue, 30 Aug 2022 20:30:26 +0200 Subject: Let examples show up in Qt Creator again Fix an issue where the relative paths in the generated examples-manifest.xml did miss the parent directory, effectively blocking the examples from being shown in the Qt Creator Welcome screen. This broke in commit c403e775f60a, where the exampledirs path was changed from "../../../examples" to "../../../examples/multimedia" and "../../../examples/multimediawidgets". This made qdoc miss the "multimedia" and "multimediawidgets" directories in the generated paths. To fix this, the patch * moves all the multimediawidgets examples to multimedia * sets examplesinstallpath to "multimedia" The unification of directories is needed because there can be only one examplesinstallpath per qdoc project. Fixes: QTBUG-104943 Change-Id: I4d1b1f857563ec23b4d60028ca08d0470ba96298 Reviewed-by: Nicholas Bennett Reviewed-by: Lars Knoll (cherry picked from commit c3081f86f4fc53509d853a2b88aff88df8c55d87) Reviewed-by: Kai Koehne Reviewed-by: Qt CI Bot --- examples/CMakeLists.txt | 1 - examples/examples.pro | 2 +- examples/multimedia/CMakeLists.txt | 10 +- examples/multimedia/camera/CMakeLists.txt | 81 +++ .../multimedia/camera/android/AndroidManifest.xml | 68 +++ examples/multimedia/camera/camera.cpp | 387 ++++++++++++ examples/multimedia/camera/camera.h | 101 ++++ examples/multimedia/camera/camera.pro | 40 ++ examples/multimedia/camera/camera.qrc | 5 + examples/multimedia/camera/camera.ui | 488 +++++++++++++++ examples/multimedia/camera/camera_mobile.ui | 504 ++++++++++++++++ .../camera/doc/images/camera-example.png | Bin 0 -> 13647 bytes examples/multimedia/camera/doc/src/camera.qdoc | 58 ++ examples/multimedia/camera/images/shutter.svg | 21 + examples/multimedia/camera/imagesettings.cpp | 83 +++ examples/multimedia/camera/imagesettings.h | 39 ++ examples/multimedia/camera/imagesettings.ui | 123 ++++ examples/multimedia/camera/ios/Info.plist.in | 50 ++ examples/multimedia/camera/macos/Info.plist.in | 49 ++ examples/multimedia/camera/main.cpp | 16 + examples/multimedia/camera/metadatadialog.cpp | 80 +++ examples/multimedia/camera/metadatadialog.h | 31 + examples/multimedia/camera/videosettings.cpp | 201 +++++++ examples/multimedia/camera/videosettings.h | 38 ++ examples/multimedia/camera/videosettings.ui | 213 +++++++ examples/multimedia/camera/videosettings_mobile.ui | 207 +++++++ examples/multimedia/multimedia.pro | 10 +- examples/multimedia/player/CMakeLists.txt | 41 ++ .../multimedia/player/doc/images/mediaplayerex.jpg | Bin 0 -> 28825 bytes examples/multimedia/player/doc/src/player.qdoc | 48 ++ examples/multimedia/player/main.cpp | 37 ++ examples/multimedia/player/player.cpp | 506 ++++++++++++++++ examples/multimedia/player/player.h | 99 ++++ examples/multimedia/player/player.pro | 27 + examples/multimedia/player/playercontrols.cpp | 172 ++++++ examples/multimedia/player/playercontrols.h | 62 ++ examples/multimedia/player/playlistmodel.cpp | 102 ++++ examples/multimedia/player/playlistmodel.h | 52 ++ examples/multimedia/player/qmediaplaylist.cpp | 653 +++++++++++++++++++++ examples/multimedia/player/qmediaplaylist.h | 96 +++ examples/multimedia/player/qmediaplaylist_p.h | 112 ++++ examples/multimedia/player/qplaylistfileparser.cpp | 605 +++++++++++++++++++ examples/multimedia/player/qplaylistfileparser_p.h | 80 +++ examples/multimedia/player/videowidget.cpp | 46 ++ examples/multimedia/player/videowidget.h | 22 + .../multimedia/videographicsitem/CMakeLists.txt | 39 ++ .../doc/images/video-videographicsitem.png | Bin 0 -> 54436 bytes .../doc/src/videographicsitem.qdoc | 19 + examples/multimedia/videographicsitem/main.cpp | 39 ++ .../videographicsitem/videographicsitem.pro | 14 + .../multimedia/videographicsitem/videoplayer.cpp | 139 +++++ .../multimedia/videographicsitem/videoplayer.h | 48 ++ examples/multimedia/videowidget/CMakeLists.txt | 39 ++ .../videowidget/doc/images/video-videowidget.png | Bin 0 -> 54199 bytes .../videowidget/doc/src/videowidget.qdoc | 17 + examples/multimedia/videowidget/main.cpp | 41 ++ examples/multimedia/videowidget/videoplayer.cpp | 130 ++++ examples/multimedia/videowidget/videoplayer.h | 44 ++ examples/multimedia/videowidget/videowidget.pro | 16 + examples/multimediawidgets/CMakeLists.txt | 6 - examples/multimediawidgets/camera/CMakeLists.txt | 78 --- .../camera/android/AndroidManifest.xml | 68 --- examples/multimediawidgets/camera/camera.cpp | 387 ------------ examples/multimediawidgets/camera/camera.h | 101 ---- examples/multimediawidgets/camera/camera.pro | 40 -- examples/multimediawidgets/camera/camera.qrc | 5 - examples/multimediawidgets/camera/camera.ui | 488 --------------- examples/multimediawidgets/camera/camera_mobile.ui | 504 ---------------- .../camera/doc/images/camera-example.png | Bin 13647 -> 0 bytes .../multimediawidgets/camera/doc/src/camera.qdoc | 58 -- .../multimediawidgets/camera/images/shutter.svg | 21 - .../multimediawidgets/camera/imagesettings.cpp | 83 --- examples/multimediawidgets/camera/imagesettings.h | 39 -- examples/multimediawidgets/camera/imagesettings.ui | 123 ---- .../multimediawidgets/camera/ios/Info.plist.in | 50 -- .../multimediawidgets/camera/macos/Info.plist.in | 49 -- examples/multimediawidgets/camera/main.cpp | 16 - .../multimediawidgets/camera/metadatadialog.cpp | 80 --- examples/multimediawidgets/camera/metadatadialog.h | 31 - .../multimediawidgets/camera/videosettings.cpp | 201 ------- examples/multimediawidgets/camera/videosettings.h | 38 -- examples/multimediawidgets/camera/videosettings.ui | 213 ------- .../camera/videosettings_mobile.ui | 207 ------- examples/multimediawidgets/multimediawidgets.pro | 10 - examples/multimediawidgets/player/CMakeLists.txt | 38 -- .../player/doc/images/mediaplayerex.jpg | Bin 28825 -> 0 bytes .../multimediawidgets/player/doc/src/player.qdoc | 48 -- examples/multimediawidgets/player/main.cpp | 37 -- examples/multimediawidgets/player/player.cpp | 506 ---------------- examples/multimediawidgets/player/player.h | 99 ---- examples/multimediawidgets/player/player.pro | 27 - .../multimediawidgets/player/playercontrols.cpp | 172 ------ examples/multimediawidgets/player/playercontrols.h | 62 -- .../multimediawidgets/player/playlistmodel.cpp | 102 ---- examples/multimediawidgets/player/playlistmodel.h | 52 -- .../multimediawidgets/player/qmediaplaylist.cpp | 653 --------------------- examples/multimediawidgets/player/qmediaplaylist.h | 96 --- .../multimediawidgets/player/qmediaplaylist_p.h | 112 ---- .../player/qplaylistfileparser.cpp | 605 ------------------- .../player/qplaylistfileparser_p.h | 80 --- examples/multimediawidgets/player/videowidget.cpp | 46 -- examples/multimediawidgets/player/videowidget.h | 22 - .../videographicsitem/CMakeLists.txt | 36 -- .../doc/images/video-videographicsitem.png | Bin 54436 -> 0 bytes .../doc/src/videographicsitem.qdoc | 19 - .../multimediawidgets/videographicsitem/main.cpp | 39 -- .../videographicsitem/videographicsitem.pro | 14 - .../videographicsitem/videoplayer.cpp | 139 ----- .../videographicsitem/videoplayer.h | 48 -- .../multimediawidgets/videowidget/CMakeLists.txt | 36 -- .../videowidget/doc/images/video-videowidget.png | Bin 54199 -> 0 bytes .../videowidget/doc/src/videowidget.qdoc | 17 - examples/multimediawidgets/videowidget/main.cpp | 41 -- .../multimediawidgets/videowidget/videoplayer.cpp | 130 ---- .../multimediawidgets/videowidget/videoplayer.h | 44 -- .../multimediawidgets/videowidget/videowidget.pro | 16 - src/multimedia/doc/qtmultimedia.qdocconf | 2 + 117 files changed, 6245 insertions(+), 6240 deletions(-) create mode 100644 examples/multimedia/camera/CMakeLists.txt create mode 100644 examples/multimedia/camera/android/AndroidManifest.xml create mode 100644 examples/multimedia/camera/camera.cpp create mode 100644 examples/multimedia/camera/camera.h create mode 100644 examples/multimedia/camera/camera.pro create mode 100644 examples/multimedia/camera/camera.qrc create mode 100644 examples/multimedia/camera/camera.ui create mode 100644 examples/multimedia/camera/camera_mobile.ui create mode 100644 examples/multimedia/camera/doc/images/camera-example.png create mode 100644 examples/multimedia/camera/doc/src/camera.qdoc create mode 100644 examples/multimedia/camera/images/shutter.svg create mode 100644 examples/multimedia/camera/imagesettings.cpp create mode 100644 examples/multimedia/camera/imagesettings.h create mode 100644 examples/multimedia/camera/imagesettings.ui create mode 100644 examples/multimedia/camera/ios/Info.plist.in create mode 100644 examples/multimedia/camera/macos/Info.plist.in create mode 100644 examples/multimedia/camera/main.cpp create mode 100644 examples/multimedia/camera/metadatadialog.cpp create mode 100644 examples/multimedia/camera/metadatadialog.h create mode 100644 examples/multimedia/camera/videosettings.cpp create mode 100644 examples/multimedia/camera/videosettings.h create mode 100644 examples/multimedia/camera/videosettings.ui create mode 100644 examples/multimedia/camera/videosettings_mobile.ui create mode 100644 examples/multimedia/player/CMakeLists.txt create mode 100644 examples/multimedia/player/doc/images/mediaplayerex.jpg create mode 100644 examples/multimedia/player/doc/src/player.qdoc create mode 100644 examples/multimedia/player/main.cpp create mode 100644 examples/multimedia/player/player.cpp create mode 100644 examples/multimedia/player/player.h create mode 100644 examples/multimedia/player/player.pro create mode 100644 examples/multimedia/player/playercontrols.cpp create mode 100644 examples/multimedia/player/playercontrols.h create mode 100644 examples/multimedia/player/playlistmodel.cpp create mode 100644 examples/multimedia/player/playlistmodel.h create mode 100644 examples/multimedia/player/qmediaplaylist.cpp create mode 100644 examples/multimedia/player/qmediaplaylist.h create mode 100644 examples/multimedia/player/qmediaplaylist_p.h create mode 100644 examples/multimedia/player/qplaylistfileparser.cpp create mode 100644 examples/multimedia/player/qplaylistfileparser_p.h create mode 100644 examples/multimedia/player/videowidget.cpp create mode 100644 examples/multimedia/player/videowidget.h create mode 100644 examples/multimedia/videographicsitem/CMakeLists.txt create mode 100644 examples/multimedia/videographicsitem/doc/images/video-videographicsitem.png create mode 100644 examples/multimedia/videographicsitem/doc/src/videographicsitem.qdoc create mode 100644 examples/multimedia/videographicsitem/main.cpp create mode 100644 examples/multimedia/videographicsitem/videographicsitem.pro create mode 100644 examples/multimedia/videographicsitem/videoplayer.cpp create mode 100644 examples/multimedia/videographicsitem/videoplayer.h create mode 100644 examples/multimedia/videowidget/CMakeLists.txt create mode 100644 examples/multimedia/videowidget/doc/images/video-videowidget.png create mode 100644 examples/multimedia/videowidget/doc/src/videowidget.qdoc create mode 100644 examples/multimedia/videowidget/main.cpp create mode 100644 examples/multimedia/videowidget/videoplayer.cpp create mode 100644 examples/multimedia/videowidget/videoplayer.h create mode 100644 examples/multimedia/videowidget/videowidget.pro delete mode 100644 examples/multimediawidgets/CMakeLists.txt delete mode 100644 examples/multimediawidgets/camera/CMakeLists.txt delete mode 100644 examples/multimediawidgets/camera/android/AndroidManifest.xml delete mode 100644 examples/multimediawidgets/camera/camera.cpp delete mode 100644 examples/multimediawidgets/camera/camera.h delete mode 100644 examples/multimediawidgets/camera/camera.pro delete mode 100644 examples/multimediawidgets/camera/camera.qrc delete mode 100644 examples/multimediawidgets/camera/camera.ui delete mode 100644 examples/multimediawidgets/camera/camera_mobile.ui delete mode 100644 examples/multimediawidgets/camera/doc/images/camera-example.png delete mode 100644 examples/multimediawidgets/camera/doc/src/camera.qdoc delete mode 100644 examples/multimediawidgets/camera/images/shutter.svg delete mode 100644 examples/multimediawidgets/camera/imagesettings.cpp delete mode 100644 examples/multimediawidgets/camera/imagesettings.h delete mode 100644 examples/multimediawidgets/camera/imagesettings.ui delete mode 100644 examples/multimediawidgets/camera/ios/Info.plist.in delete mode 100644 examples/multimediawidgets/camera/macos/Info.plist.in delete mode 100644 examples/multimediawidgets/camera/main.cpp delete mode 100644 examples/multimediawidgets/camera/metadatadialog.cpp delete mode 100644 examples/multimediawidgets/camera/metadatadialog.h delete mode 100644 examples/multimediawidgets/camera/videosettings.cpp delete mode 100644 examples/multimediawidgets/camera/videosettings.h delete mode 100644 examples/multimediawidgets/camera/videosettings.ui delete mode 100644 examples/multimediawidgets/camera/videosettings_mobile.ui delete mode 100644 examples/multimediawidgets/multimediawidgets.pro delete mode 100644 examples/multimediawidgets/player/CMakeLists.txt delete mode 100644 examples/multimediawidgets/player/doc/images/mediaplayerex.jpg delete mode 100644 examples/multimediawidgets/player/doc/src/player.qdoc delete mode 100644 examples/multimediawidgets/player/main.cpp delete mode 100644 examples/multimediawidgets/player/player.cpp delete mode 100644 examples/multimediawidgets/player/player.h delete mode 100644 examples/multimediawidgets/player/player.pro delete mode 100644 examples/multimediawidgets/player/playercontrols.cpp delete mode 100644 examples/multimediawidgets/player/playercontrols.h delete mode 100644 examples/multimediawidgets/player/playlistmodel.cpp delete mode 100644 examples/multimediawidgets/player/playlistmodel.h delete mode 100644 examples/multimediawidgets/player/qmediaplaylist.cpp delete mode 100644 examples/multimediawidgets/player/qmediaplaylist.h delete mode 100644 examples/multimediawidgets/player/qmediaplaylist_p.h delete mode 100644 examples/multimediawidgets/player/qplaylistfileparser.cpp delete mode 100644 examples/multimediawidgets/player/qplaylistfileparser_p.h delete mode 100644 examples/multimediawidgets/player/videowidget.cpp delete mode 100644 examples/multimediawidgets/player/videowidget.h delete mode 100644 examples/multimediawidgets/videographicsitem/CMakeLists.txt delete mode 100644 examples/multimediawidgets/videographicsitem/doc/images/video-videographicsitem.png delete mode 100644 examples/multimediawidgets/videographicsitem/doc/src/videographicsitem.qdoc delete mode 100644 examples/multimediawidgets/videographicsitem/main.cpp delete mode 100644 examples/multimediawidgets/videographicsitem/videographicsitem.pro delete mode 100644 examples/multimediawidgets/videographicsitem/videoplayer.cpp delete mode 100644 examples/multimediawidgets/videographicsitem/videoplayer.h delete mode 100644 examples/multimediawidgets/videowidget/CMakeLists.txt delete mode 100644 examples/multimediawidgets/videowidget/doc/images/video-videowidget.png delete mode 100644 examples/multimediawidgets/videowidget/doc/src/videowidget.qdoc delete mode 100644 examples/multimediawidgets/videowidget/main.cpp delete mode 100644 examples/multimediawidgets/videowidget/videoplayer.cpp delete mode 100644 examples/multimediawidgets/videowidget/videoplayer.h delete mode 100644 examples/multimediawidgets/videowidget/videowidget.pro diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7c3b8b2dc..638f2b0ef 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -4,6 +4,5 @@ add_subdirectory(multimedia) if (QT_FEATURE_spatialaudio) add_subdirectory(spatialaudio) endif() -add_subdirectory(multimediawidgets) qt_examples_build_end() diff --git a/examples/examples.pro b/examples/examples.pro index 5d1718718..3cda36046 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -1,3 +1,3 @@ TEMPLATE = subdirs -SUBDIRS += multimedia multimediawidgets spatialaudio +SUBDIRS += multimedia spatialaudio 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +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(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 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(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 +#include +#include +#include +#include +#include +#include +#include + +#include + +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 m_camera; + QScopedPointer m_audioInput; + QImageCapture *m_imageCapture; + QScopedPointer 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 @@ + + + images/shutter.svg + + 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 @@ + + + Camera + + + + 0 + 0 + 668 + 429 + + + + Camera + + + + + + + 0 + + + + Image + + + + + + Qt::Vertical + + + + 20 + 161 + + + + + + + + false + + + Capture Photo + + + + :/images/shutter.svg:/images/shutter.svg + + + + + + + -4 + + + 4 + + + 2 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + Exposure Compensation: + + + + + + + + Video + + + + + + Record + + + + + + + Pause + + + + + + + Stop + + + + + + + Qt::Vertical + + + + 20 + 76 + + + + + + + + Mute + + + true + + + + + + + Set metadata + + + true + + + + + + + + + + + + 1 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 145 + 145 + 145 + + + + + + + + + 255 + 255 + 255 + + + + + + + 145 + 145 + 145 + + + + + + + + + 145 + 145 + 145 + + + + + + + 145 + 145 + 145 + + + + + + + + 0 + + + + + + + + + + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + + + + + + 0 + 0 + 668 + 28 + + + + + File + + + + + + + + + + + Devices + + + + + + + + + Close + + + + + Start Camera + + + + + Stop Camera + + + + + Change Settings + + + + + + QVideoWidget + QWidget +
qvideowidget.h
+ 1 +
+
+ + + + + + recordButton + clicked() + Camera + record() + + + 647 + 149 + + + 61 + 238 + + + + + stopButton + clicked() + Camera + stop() + + + 647 + 225 + + + 140 + 236 + + + + + pauseButton + clicked() + Camera + pause() + + + 647 + 187 + + + 234 + 237 + + + + + actionExit + triggered() + Camera + close() + + + -1 + -1 + + + 154 + 130 + + + + + takeImageButton + clicked() + Camera + takeImage() + + + 625 + 132 + + + 603 + 169 + + + + + muteButton + toggled(bool) + Camera + setMuted(bool) + + + 647 + 377 + + + 5 + 280 + + + + + exposureCompensation + valueChanged(int) + Camera + setExposureCompensation(int) + + + 559 + 367 + + + 665 + 365 + + + + + actionSettings + triggered() + Camera + configureCaptureSettings() + + + -1 + -1 + + + 333 + 210 + + + + + actionStartCamera + triggered() + Camera + startCamera() + + + -1 + -1 + + + 333 + 210 + + + + + actionStopCamera + triggered() + Camera + stopCamera() + + + -1 + -1 + + + 333 + 210 + + + + + + record() + pause() + stop() + enablePreview(bool) + configureCaptureSettings() + takeImage() + startCamera() + toggleLock() + setMuted(bool) + stopCamera() + setExposureCompensation(int) + +
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 @@ + + + Camera + + + + 0 + 0 + 668 + 429 + + + + Camera + + + + + + + + 0 + 0 + + + + 0 + + + + Image + + + + + + -4 + + + 4 + + + 2 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + + 0 + 0 + + + + Exposure Compensation: + + + + + + + false + + + Capture Photo + + + + :/images/shutter.svg:/images/shutter.svg + + + + + + + + Video + + + + + + + + + + Record + + + + + + + Pause + + + + + + + Stop + + + + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + Mute + + + true + + + + + + + Set metadata + + + true + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 145 + 145 + 145 + + + + + + + + + 255 + 255 + 255 + + + + + + + 145 + 145 + 145 + + + + + + + + + 145 + 145 + 145 + + + + + + + 145 + 145 + 145 + + + + + + + + 0 + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + + + + + + + + + + 0 + 0 + 668 + 22 + + + + + File + + + + + + + + + + + Devices + + + + + + + + + Close + + + + + Start Camera + + + + + Stop Camera + + + + + Change Settings + + + + + + QVideoWidget + QWidget +
qvideowidget.h
+ 1 +
+
+ + + + recordButton + clicked() + Camera + record() + + + 647 + 149 + + + 61 + 238 + + + + + stopButton + clicked() + Camera + stop() + + + 647 + 225 + + + 140 + 236 + + + + + pauseButton + clicked() + Camera + pause() + + + 647 + 187 + + + 234 + 237 + + + + + actionExit + triggered() + Camera + close() + + + -1 + -1 + + + 154 + 130 + + + + + takeImageButton + clicked() + Camera + takeImage() + + + 625 + 132 + + + 603 + 169 + + + + + muteButton + toggled(bool) + Camera + setMuted(bool) + + + 647 + 377 + + + 5 + 280 + + + + + exposureCompensation + valueChanged(int) + Camera + setExposureCompensation(int) + + + 559 + 367 + + + 665 + 365 + + + + + actionSettings + triggered() + Camera + configureCaptureSettings() + + + -1 + -1 + + + 333 + 210 + + + + + actionStartCamera + triggered() + Camera + startCamera() + + + -1 + -1 + + + 333 + 210 + + + + + actionStopCamera + triggered() + Camera + stopCamera() + + + -1 + -1 + + + 333 + 210 + + + + + + record() + pause() + stop() + enablePreview(bool) + configureCaptureSettings() + takeImage() + startCamera() + toggleLock() + setMuted(bool) + stopCamera() + setExposureCompensation(int) + +
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 Binary files /dev/null and b/examples/multimedia/camera/doc/images/camera-example.png 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 @@ + + + + + + + + + + + + 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 +#include +#include +#include +#include + +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 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()); + 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 + +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 @@ + + + ImageSettingsUi + + + + 0 + 0 + 332 + 270 + + + + Image Settings + + + + + + Image + + + + + + Resolution: + + + + + + + + + + Image Format: + + + + + + + + + + Quality: + + + + + + + 4 + + + Qt::Horizontal + + + + + + + + + + Qt::Vertical + + + + 20 + 14 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ImageSettingsUi + accept() + + + 322 + 272 + + + 44 + 230 + + + + + buttonBox + rejected() + ImageSettingsUi + reject() + + + 405 + 262 + + + 364 + 227 + + + + + 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 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + + CFBundleDevelopmentRegion + English + + LSRequiresIPhoneOS + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + NSCameraUsageDescription + Qt Multimedia Example + NSMicrophoneUsageDescription + Qt Multimedia Example + + + 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 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + + LSMinimumSystemVersion + ${CMAKE_OSX_DEPLOYMENT_TARGET} + + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + + CFBundleDevelopmentRegion + English + + NSPrincipalClass + NSApplication + + NSCameraUsageDescription + Qt Multimedia Example + NSMicrophoneUsageDescription + Qt Multimedia Example + + NSSupportsAutomaticGraphicsSwitching + + + 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 + +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 +#include +#include + +MetaDataDialog::MetaDataDialog(QWidget *parent) + : QDialog(parent) +{ + QFormLayout *metaDataLayout = new QFormLayout; + for (int key = 0; key < QMediaMetaData::NumMetaData; key++) { + QString label = QMediaMetaData::metaDataKeyToString(static_cast(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 +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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(); + 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()); + format.setAudioCodec(boxValue(ui->audioCodecBox).value()); + format.setVideoCodec(boxValue(ui->videoCodecBox).value()); + + mediaRecorder->setMediaFormat(format); + mediaRecorder->setQuality(QMediaRecorder::Quality(ui->qualitySlider->value())); + mediaRecorder->setAudioSampleRate(ui->audioSampleRateBox->value()); + + const auto &cameraFormat = boxValue(ui->videoFormatBox).value(); + 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()); + if (ui->audioCodecBox->count()) + format.setAudioCodec(boxValue(ui->audioCodecBox).value()); + if (ui->videoCodecBox->count()) + format.setVideoCodec(boxValue(ui->videoCodecBox).value()); + + 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 + +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 @@ + + + VideoSettingsUi + + + + 0 + 0 + 686 + 499 + + + + Video Settings + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Video + + + + + + Camera Format + + + + + + + + + + Framerate: + + + + + + + Video Codec: + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Audio + + + + + + Audio Codec: + + + + + + + + + + Sample Rate: + + + + + + + + + + + + + + + + Quality: + + + + + + + 4 + + + Qt::Horizontal + + + + + + + File Format: + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + buttonBox + accepted() + VideoSettingsUi + accept() + + + 322 + 272 + + + 44 + 230 + + + + + buttonBox + rejected() + VideoSettingsUi + reject() + + + 405 + 262 + + + 364 + 227 + + + + + 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 @@ + + + VideoSettingsUi + + + + 0 + 0 + 329 + 591 + + + + Video Settings + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Audio + + + + + + Audio Codec: + + + + + + + + + + Sample Rate: + + + + + + + + + + + + + + + + Quality: + + + + + + + 4 + + + Qt::Horizontal + + + + + + + File Format: + + + + + + + + + + + + + + + + Video + + + + + + Frames per second: + + + + + + + + + + Camera Format: + + + + + + + Video Codec: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + 8 + + + 30 + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + buttonBox + accepted() + VideoSettingsUi + accept() + + + 322 + 272 + + + 44 + 230 + + + + + buttonBox + rejected() + VideoSettingsUi + reject() + + + 405 + 262 + + + 364 + 227 + + + + + 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 Binary files /dev/null and b/examples/multimedia/player/doc/images/mediaplayerex.jpg 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 +#include +#include +#include +#include + +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 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 +#include +#include +#include +#include +#include + +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::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(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 &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(m_metaDataFields[i])) + field->clear(); + else if (QLabel* label = qobject_cast(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(m_metaDataFields[key])) { + QImage coverImage = v.value(); + cover->setPixmap(QPixmap::fromImage(coverImage)); + } + } else if (key == QMediaMetaData::ThumbnailImage) { + QVariant v = metaData.value(key); + if (QLabel *thumbnail = qobject_cast(m_metaDataFields[key])) { + QImage thumbnailImage = v.value(); + thumbnail->setPixmap(QPixmap::fromImage(thumbnailImage)); + } + } else if (QLineEdit *field = qobject_cast(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(); + + 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(); + 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 +#include +#include + +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 &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 +#include +#include +#include +#include +#include + +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::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 +#include + +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 +#include + +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 +#include + +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 m_playlist; + QMap 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 +#include +#include +#include +#include +#include + +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 &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 &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 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 + +#include +#include + + +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 &items); + bool insertMedia(int index, const QUrl &content); + bool insertMedia(int index, const QList &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 + +#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 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 +#include +#include +#include +#include +#include +#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 m_source; + QScopedPointer m_currentParser; + QByteArray m_buffer; + QUrl m_root; + QNetworkAccessManager m_mgr; + QString m_mimeType; + QPlaylistFileParser *q_ptr; + QPointer 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 + +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 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 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 +#include + +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 + +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 Binary files /dev/null and b/examples/multimedia/videographicsitem/doc/images/video-videographicsitem.png 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 +#include +#include +#include +#include + +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 +#include + +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 +#include + +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 Binary files /dev/null and b/examples/multimedia/videowidget/doc/images/video-videowidget.png 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 +#include +#include +#include +#include +#include + +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 +#include + +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 +#include + +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 diff --git a/examples/multimediawidgets/CMakeLists.txt b/examples/multimediawidgets/CMakeLists.txt deleted file mode 100644 index 8d16458c2..000000000 --- a/examples/multimediawidgets/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -if(TARGET Qt::Widgets) - qt_internal_add_example(camera) - qt_internal_add_example(videographicsitem) - qt_internal_add_example(videowidget) - qt_internal_add_example(player) -endif() diff --git a/examples/multimediawidgets/camera/CMakeLists.txt b/examples/multimediawidgets/camera/CMakeLists.txt deleted file mode 100644 index 530126ba5..000000000 --- a/examples/multimediawidgets/camera/CMakeLists.txt +++ /dev/null @@ -1,78 +0,0 @@ -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}/multimediawidgets/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/multimediawidgets/camera/android/AndroidManifest.xml b/examples/multimediawidgets/camera/android/AndroidManifest.xml deleted file mode 100644 index 29c4672cf..000000000 --- a/examples/multimediawidgets/camera/android/AndroidManifest.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/multimediawidgets/camera/camera.cpp b/examples/multimediawidgets/camera/camera.cpp deleted file mode 100644 index 201e6e985..000000000 --- a/examples/multimediawidgets/camera/camera.cpp +++ /dev/null @@ -1,387 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -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(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 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(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/multimediawidgets/camera/camera.h b/examples/multimediawidgets/camera/camera.h deleted file mode 100644 index ae8eb5919..000000000 --- a/examples/multimediawidgets/camera/camera.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef CAMERA_H -#define CAMERA_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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 m_camera; - QScopedPointer m_audioInput; - QImageCapture *m_imageCapture; - QScopedPointer m_mediaRecorder; - - bool m_isCapturingImage = false; - bool m_applicationExiting = false; - bool m_doImageCapture = true; - - MetaDataDialog *m_metaDataDialog = nullptr; -}; - -#endif diff --git a/examples/multimediawidgets/camera/camera.pro b/examples/multimediawidgets/camera/camera.pro deleted file mode 100644 index e1d98c06e..000000000 --- a/examples/multimediawidgets/camera/camera.pro +++ /dev/null @@ -1,40 +0,0 @@ -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]/multimediawidgets/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/multimediawidgets/camera/camera.qrc b/examples/multimediawidgets/camera/camera.qrc deleted file mode 100644 index a915eb596..000000000 --- a/examples/multimediawidgets/camera/camera.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - images/shutter.svg - - diff --git a/examples/multimediawidgets/camera/camera.ui b/examples/multimediawidgets/camera/camera.ui deleted file mode 100644 index 560ee7fed..000000000 --- a/examples/multimediawidgets/camera/camera.ui +++ /dev/null @@ -1,488 +0,0 @@ - - - Camera - - - - 0 - 0 - 668 - 429 - - - - Camera - - - - - - - 0 - - - - Image - - - - - - Qt::Vertical - - - - 20 - 161 - - - - - - - - false - - - Capture Photo - - - - :/images/shutter.svg:/images/shutter.svg - - - - - - - -4 - - - 4 - - - 2 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - - - - - Exposure Compensation: - - - - - - - - Video - - - - - - Record - - - - - - - Pause - - - - - - - Stop - - - - - - - Qt::Vertical - - - - 20 - 76 - - - - - - - - Mute - - - true - - - - - - - Set metadata - - - true - - - - - - - - - - - - 1 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 145 - 145 - 145 - - - - - - - - - 255 - 255 - 255 - - - - - - - 145 - 145 - 145 - - - - - - - - - 145 - 145 - 145 - - - - - - - 145 - 145 - 145 - - - - - - - - 0 - - - - - - - - - - - - - - - 0 - 0 - - - - QFrame::Box - - - - - - - - - - - - - - - - 0 - 0 - 668 - 28 - - - - - File - - - - - - - - - - - Devices - - - - - - - - - Close - - - - - Start Camera - - - - - Stop Camera - - - - - Change Settings - - - - - - QVideoWidget - QWidget -
qvideowidget.h
- 1 -
-
- - - - - - recordButton - clicked() - Camera - record() - - - 647 - 149 - - - 61 - 238 - - - - - stopButton - clicked() - Camera - stop() - - - 647 - 225 - - - 140 - 236 - - - - - pauseButton - clicked() - Camera - pause() - - - 647 - 187 - - - 234 - 237 - - - - - actionExit - triggered() - Camera - close() - - - -1 - -1 - - - 154 - 130 - - - - - takeImageButton - clicked() - Camera - takeImage() - - - 625 - 132 - - - 603 - 169 - - - - - muteButton - toggled(bool) - Camera - setMuted(bool) - - - 647 - 377 - - - 5 - 280 - - - - - exposureCompensation - valueChanged(int) - Camera - setExposureCompensation(int) - - - 559 - 367 - - - 665 - 365 - - - - - actionSettings - triggered() - Camera - configureCaptureSettings() - - - -1 - -1 - - - 333 - 210 - - - - - actionStartCamera - triggered() - Camera - startCamera() - - - -1 - -1 - - - 333 - 210 - - - - - actionStopCamera - triggered() - Camera - stopCamera() - - - -1 - -1 - - - 333 - 210 - - - - - - record() - pause() - stop() - enablePreview(bool) - configureCaptureSettings() - takeImage() - startCamera() - toggleLock() - setMuted(bool) - stopCamera() - setExposureCompensation(int) - -
diff --git a/examples/multimediawidgets/camera/camera_mobile.ui b/examples/multimediawidgets/camera/camera_mobile.ui deleted file mode 100644 index 7f269b17b..000000000 --- a/examples/multimediawidgets/camera/camera_mobile.ui +++ /dev/null @@ -1,504 +0,0 @@ - - - Camera - - - - 0 - 0 - 668 - 429 - - - - Camera - - - - - - - - 0 - 0 - - - - 0 - - - - Image - - - - - - -4 - - - 4 - - - 2 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - - - - - - 0 - 0 - - - - Exposure Compensation: - - - - - - - false - - - Capture Photo - - - - :/images/shutter.svg:/images/shutter.svg - - - - - - - - Video - - - - - - - - - - Record - - - - - - - Pause - - - - - - - Stop - - - - - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - Mute - - - true - - - - - - - Set metadata - - - true - - - - - - - - - - - - - - - - 1 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - 145 - 145 - 145 - - - - - - - - - 255 - 255 - 255 - - - - - - - 145 - 145 - 145 - - - - - - - - - 145 - 145 - 145 - - - - - - - 145 - 145 - 145 - - - - - - - - 0 - - - - - - - - 0 - 0 - - - - - - - - - - - - - 0 - 0 - - - - QFrame::Box - - - - - - - - - - - - - - - - 0 - 0 - 668 - 22 - - - - - File - - - - - - - - - - - Devices - - - - - - - - - Close - - - - - Start Camera - - - - - Stop Camera - - - - - Change Settings - - - - - - QVideoWidget - QWidget -
qvideowidget.h
- 1 -
-
- - - - recordButton - clicked() - Camera - record() - - - 647 - 149 - - - 61 - 238 - - - - - stopButton - clicked() - Camera - stop() - - - 647 - 225 - - - 140 - 236 - - - - - pauseButton - clicked() - Camera - pause() - - - 647 - 187 - - - 234 - 237 - - - - - actionExit - triggered() - Camera - close() - - - -1 - -1 - - - 154 - 130 - - - - - takeImageButton - clicked() - Camera - takeImage() - - - 625 - 132 - - - 603 - 169 - - - - - muteButton - toggled(bool) - Camera - setMuted(bool) - - - 647 - 377 - - - 5 - 280 - - - - - exposureCompensation - valueChanged(int) - Camera - setExposureCompensation(int) - - - 559 - 367 - - - 665 - 365 - - - - - actionSettings - triggered() - Camera - configureCaptureSettings() - - - -1 - -1 - - - 333 - 210 - - - - - actionStartCamera - triggered() - Camera - startCamera() - - - -1 - -1 - - - 333 - 210 - - - - - actionStopCamera - triggered() - Camera - stopCamera() - - - -1 - -1 - - - 333 - 210 - - - - - - record() - pause() - stop() - enablePreview(bool) - configureCaptureSettings() - takeImage() - startCamera() - toggleLock() - setMuted(bool) - stopCamera() - setExposureCompensation(int) - -
diff --git a/examples/multimediawidgets/camera/doc/images/camera-example.png b/examples/multimediawidgets/camera/doc/images/camera-example.png deleted file mode 100644 index 12e1b5728..000000000 Binary files a/examples/multimediawidgets/camera/doc/images/camera-example.png and /dev/null differ diff --git a/examples/multimediawidgets/camera/doc/src/camera.qdoc b/examples/multimediawidgets/camera/doc/src/camera.qdoc deleted file mode 100644 index 7a3b88d1b..000000000 --- a/examples/multimediawidgets/camera/doc/src/camera.qdoc +++ /dev/null @@ -1,58 +0,0 @@ -// 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/multimediawidgets/camera/images/shutter.svg b/examples/multimediawidgets/camera/images/shutter.svg deleted file mode 100644 index 18493361d..000000000 --- a/examples/multimediawidgets/camera/images/shutter.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/multimediawidgets/camera/imagesettings.cpp b/examples/multimediawidgets/camera/imagesettings.cpp deleted file mode 100644 index a107cc62d..000000000 --- a/examples/multimediawidgets/camera/imagesettings.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// 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 -#include -#include -#include -#include - -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 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()); - 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/multimediawidgets/camera/imagesettings.h b/examples/multimediawidgets/camera/imagesettings.h deleted file mode 100644 index 13bd6dc4a..000000000 --- a/examples/multimediawidgets/camera/imagesettings.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef IMAGESETTINGS_H -#define IMAGESETTINGS_H - -#include - -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/multimediawidgets/camera/imagesettings.ui b/examples/multimediawidgets/camera/imagesettings.ui deleted file mode 100644 index 8c59ca01d..000000000 --- a/examples/multimediawidgets/camera/imagesettings.ui +++ /dev/null @@ -1,123 +0,0 @@ - - - ImageSettingsUi - - - - 0 - 0 - 332 - 270 - - - - Image Settings - - - - - - Image - - - - - - Resolution: - - - - - - - - - - Image Format: - - - - - - - - - - Quality: - - - - - - - 4 - - - Qt::Horizontal - - - - - - - - - - Qt::Vertical - - - - 20 - 14 - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - ImageSettingsUi - accept() - - - 322 - 272 - - - 44 - 230 - - - - - buttonBox - rejected() - ImageSettingsUi - reject() - - - 405 - 262 - - - 364 - 227 - - - - - diff --git a/examples/multimediawidgets/camera/ios/Info.plist.in b/examples/multimediawidgets/camera/ios/Info.plist.in deleted file mode 100644 index 6a6b8db11..000000000 --- a/examples/multimediawidgets/camera/ios/Info.plist.in +++ /dev/null @@ -1,50 +0,0 @@ - - - - - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - - CFBundleName - ${MACOSX_BUNDLE_BUNDLE_NAME} - CFBundleIdentifier - ${MACOSX_BUNDLE_GUI_IDENTIFIER} - CFBundleExecutable - ${MACOSX_BUNDLE_EXECUTABLE_NAME} - - CFBundleVersion - ${MACOSX_BUNDLE_BUNDLE_VERSION} - CFBundleShortVersionString - ${MACOSX_BUNDLE_SHORT_VERSION_STRING} - - CFBundleGetInfoString - ${MACOSX_BUNDLE_INFO_STRING} - NSHumanReadableCopyright - ${MACOSX_BUNDLE_COPYRIGHT} - - CFBundleIconFile - ${MACOSX_BUNDLE_ICON_FILE} - - CFBundleDevelopmentRegion - English - - LSRequiresIPhoneOS - - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - NSCameraUsageDescription - Qt Multimedia Example - NSMicrophoneUsageDescription - Qt Multimedia Example - - - diff --git a/examples/multimediawidgets/camera/macos/Info.plist.in b/examples/multimediawidgets/camera/macos/Info.plist.in deleted file mode 100644 index ae2d945f1..000000000 --- a/examples/multimediawidgets/camera/macos/Info.plist.in +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - - CFBundleName - ${MACOSX_BUNDLE_BUNDLE_NAME} - CFBundleIdentifier - ${MACOSX_BUNDLE_GUI_IDENTIFIER} - CFBundleExecutable - ${MACOSX_BUNDLE_EXECUTABLE_NAME} - - CFBundleVersion - ${MACOSX_BUNDLE_BUNDLE_VERSION} - CFBundleShortVersionString - ${MACOSX_BUNDLE_SHORT_VERSION_STRING} - CFBundleLongVersionString - ${MACOSX_BUNDLE_LONG_VERSION_STRING} - - LSMinimumSystemVersion - ${CMAKE_OSX_DEPLOYMENT_TARGET} - - CFBundleGetInfoString - ${MACOSX_BUNDLE_INFO_STRING} - NSHumanReadableCopyright - ${MACOSX_BUNDLE_COPYRIGHT} - - CFBundleIconFile - ${MACOSX_BUNDLE_ICON_FILE} - - CFBundleDevelopmentRegion - English - - NSPrincipalClass - NSApplication - - NSCameraUsageDescription - Qt Multimedia Example - NSMicrophoneUsageDescription - Qt Multimedia Example - - NSSupportsAutomaticGraphicsSwitching - - - diff --git a/examples/multimediawidgets/camera/main.cpp b/examples/multimediawidgets/camera/main.cpp deleted file mode 100644 index 50b411e4e..000000000 --- a/examples/multimediawidgets/camera/main.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "camera.h" - -#include - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - - Camera camera; - camera.show(); - - return app.exec(); -}; diff --git a/examples/multimediawidgets/camera/metadatadialog.cpp b/examples/multimediawidgets/camera/metadatadialog.cpp deleted file mode 100644 index 096217014..000000000 --- a/examples/multimediawidgets/camera/metadatadialog.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "metadatadialog.h" -#include "camera.h" - -#include -#include -#include - -MetaDataDialog::MetaDataDialog(QWidget *parent) - : QDialog(parent) -{ - QFormLayout *metaDataLayout = new QFormLayout; - for (int key = 0; key < QMediaMetaData::NumMetaData; key++) { - QString label = QMediaMetaData::metaDataKeyToString(static_cast(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/multimediawidgets/camera/metadatadialog.h b/examples/multimediawidgets/camera/metadatadialog.h deleted file mode 100644 index 5bb5a4b0b..000000000 --- a/examples/multimediawidgets/camera/metadatadialog.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef DIALOG_H -#define DIALOG_H - -#include -#include - -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/multimediawidgets/camera/videosettings.cpp b/examples/multimediawidgets/camera/videosettings.cpp deleted file mode 100644 index b2c62bafc..000000000 --- a/examples/multimediawidgets/camera/videosettings.cpp +++ /dev/null @@ -1,201 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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(); - 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()); - format.setAudioCodec(boxValue(ui->audioCodecBox).value()); - format.setVideoCodec(boxValue(ui->videoCodecBox).value()); - - mediaRecorder->setMediaFormat(format); - mediaRecorder->setQuality(QMediaRecorder::Quality(ui->qualitySlider->value())); - mediaRecorder->setAudioSampleRate(ui->audioSampleRateBox->value()); - - const auto &cameraFormat = boxValue(ui->videoFormatBox).value(); - 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()); - if (ui->audioCodecBox->count()) - format.setAudioCodec(boxValue(ui->audioCodecBox).value()); - if (ui->videoCodecBox->count()) - format.setVideoCodec(boxValue(ui->videoCodecBox).value()); - - 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/multimediawidgets/camera/videosettings.h b/examples/multimediawidgets/camera/videosettings.h deleted file mode 100644 index 2f356d90f..000000000 --- a/examples/multimediawidgets/camera/videosettings.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef VIDEOSETTINGS_H -#define VIDEOSETTINGS_H - -#include - -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/multimediawidgets/camera/videosettings.ui b/examples/multimediawidgets/camera/videosettings.ui deleted file mode 100644 index 3c1f71f11..000000000 --- a/examples/multimediawidgets/camera/videosettings.ui +++ /dev/null @@ -1,213 +0,0 @@ - - - VideoSettingsUi - - - - 0 - 0 - 686 - 499 - - - - Video Settings - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - Video - - - - - - Camera Format - - - - - - - - - - Framerate: - - - - - - - Video Codec: - - - - - - - - - - - - - - - Qt::Horizontal - - - - - - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Audio - - - - - - Audio Codec: - - - - - - - - - - Sample Rate: - - - - - - - - - - - - - - - - Quality: - - - - - - - 4 - - - Qt::Horizontal - - - - - - - File Format: - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - buttonBox - accepted() - VideoSettingsUi - accept() - - - 322 - 272 - - - 44 - 230 - - - - - buttonBox - rejected() - VideoSettingsUi - reject() - - - 405 - 262 - - - 364 - 227 - - - - - diff --git a/examples/multimediawidgets/camera/videosettings_mobile.ui b/examples/multimediawidgets/camera/videosettings_mobile.ui deleted file mode 100644 index 6584f07f9..000000000 --- a/examples/multimediawidgets/camera/videosettings_mobile.ui +++ /dev/null @@ -1,207 +0,0 @@ - - - VideoSettingsUi - - - - 0 - 0 - 329 - 591 - - - - Video Settings - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Audio - - - - - - Audio Codec: - - - - - - - - - - Sample Rate: - - - - - - - - - - - - - - - - Quality: - - - - - - - 4 - - - Qt::Horizontal - - - - - - - File Format: - - - - - - - - - - - - - - - - Video - - - - - - Frames per second: - - - - - - - - - - Camera Format: - - - - - - - Video Codec: - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - 8 - - - 30 - - - - - - - Qt::Horizontal - - - - - - - - - - - - - - buttonBox - accepted() - VideoSettingsUi - accept() - - - 322 - 272 - - - 44 - 230 - - - - - buttonBox - rejected() - VideoSettingsUi - reject() - - - 405 - 262 - - - 364 - 227 - - - - - diff --git a/examples/multimediawidgets/multimediawidgets.pro b/examples/multimediawidgets/multimediawidgets.pro deleted file mode 100644 index aa74b00f0..000000000 --- a/examples/multimediawidgets/multimediawidgets.pro +++ /dev/null @@ -1,10 +0,0 @@ -TEMPLATE = subdirs - -# These examples all need widgets for now (using creator templates that use widgets) -qtHaveModule(widgets) { - SUBDIRS += \ - camera \ - videographicsitem \ - videowidget \ - player -} diff --git a/examples/multimediawidgets/player/CMakeLists.txt b/examples/multimediawidgets/player/CMakeLists.txt deleted file mode 100644 index 7c0304eaf..000000000 --- a/examples/multimediawidgets/player/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -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}/multimediawidgets/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/multimediawidgets/player/doc/images/mediaplayerex.jpg b/examples/multimediawidgets/player/doc/images/mediaplayerex.jpg deleted file mode 100644 index e875bd134..000000000 Binary files a/examples/multimediawidgets/player/doc/images/mediaplayerex.jpg and /dev/null differ diff --git a/examples/multimediawidgets/player/doc/src/player.qdoc b/examples/multimediawidgets/player/doc/src/player.qdoc deleted file mode 100644 index d63b99ae3..000000000 --- a/examples/multimediawidgets/player/doc/src/player.qdoc +++ /dev/null @@ -1,48 +0,0 @@ -// 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/multimediawidgets/player/main.cpp b/examples/multimediawidgets/player/main.cpp deleted file mode 100644 index befe1d561..000000000 --- a/examples/multimediawidgets/player/main.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "player.h" - -#include -#include -#include -#include -#include - -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 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/multimediawidgets/player/player.cpp b/examples/multimediawidgets/player/player.cpp deleted file mode 100644 index 22146d7e9..000000000 --- a/examples/multimediawidgets/player/player.cpp +++ /dev/null @@ -1,506 +0,0 @@ -// 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 -#include -#include -#include -#include -#include - -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::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(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 &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(m_metaDataFields[i])) - field->clear(); - else if (QLabel* label = qobject_cast(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(m_metaDataFields[key])) { - QImage coverImage = v.value(); - cover->setPixmap(QPixmap::fromImage(coverImage)); - } - } else if (key == QMediaMetaData::ThumbnailImage) { - QVariant v = metaData.value(key); - if (QLabel *thumbnail = qobject_cast(m_metaDataFields[key])) { - QImage thumbnailImage = v.value(); - thumbnail->setPixmap(QPixmap::fromImage(thumbnailImage)); - } - } else if (QLineEdit *field = qobject_cast(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(); - - 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(); - m_player->audioOutput()->setDevice(device); -} diff --git a/examples/multimediawidgets/player/player.h b/examples/multimediawidgets/player/player.h deleted file mode 100644 index 1d328d307..000000000 --- a/examples/multimediawidgets/player/player.h +++ /dev/null @@ -1,99 +0,0 @@ -// 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 -#include -#include - -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 &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/multimediawidgets/player/player.pro b/examples/multimediawidgets/player/player.pro deleted file mode 100644 index 950fe8e0d..000000000 --- a/examples/multimediawidgets/player/player.pro +++ /dev/null @@ -1,27 +0,0 @@ -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]/multimediawidgets/player -INSTALLS += target diff --git a/examples/multimediawidgets/player/playercontrols.cpp b/examples/multimediawidgets/player/playercontrols.cpp deleted file mode 100644 index 0a6827326..000000000 --- a/examples/multimediawidgets/player/playercontrols.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "playercontrols.h" - -#include -#include -#include -#include -#include -#include - -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::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/multimediawidgets/player/playercontrols.h b/examples/multimediawidgets/player/playercontrols.h deleted file mode 100644 index 72dddd68f..000000000 --- a/examples/multimediawidgets/player/playercontrols.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef PLAYERCONTROLS_H -#define PLAYERCONTROLS_H - -#include -#include - -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/multimediawidgets/player/playlistmodel.cpp b/examples/multimediawidgets/player/playlistmodel.cpp deleted file mode 100644 index 871aed0b8..000000000 --- a/examples/multimediawidgets/player/playlistmodel.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "playlistmodel.h" -#include "qmediaplaylist.h" - -#include -#include - -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/multimediawidgets/player/playlistmodel.h b/examples/multimediawidgets/player/playlistmodel.h deleted file mode 100644 index 6c20cc1d6..000000000 --- a/examples/multimediawidgets/player/playlistmodel.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef PLAYLISTMODEL_H -#define PLAYLISTMODEL_H - -#include -#include - -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 m_playlist; - QMap m_data; -}; - -#endif // PLAYLISTMODEL_H diff --git a/examples/multimediawidgets/player/qmediaplaylist.cpp b/examples/multimediawidgets/player/qmediaplaylist.cpp deleted file mode 100644 index 529720808..000000000 --- a/examples/multimediawidgets/player/qmediaplaylist.cpp +++ /dev/null @@ -1,653 +0,0 @@ -// 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 -#include -#include -#include -#include -#include - -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 &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 &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 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/multimediawidgets/player/qmediaplaylist.h b/examples/multimediawidgets/player/qmediaplaylist.h deleted file mode 100644 index 94846d9b7..000000000 --- a/examples/multimediawidgets/player/qmediaplaylist.h +++ /dev/null @@ -1,96 +0,0 @@ -// 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 - -#include -#include - - -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 &items); - bool insertMedia(int index, const QUrl &content); - bool insertMedia(int index, const QList &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/multimediawidgets/player/qmediaplaylist_p.h b/examples/multimediawidgets/player/qmediaplaylist_p.h deleted file mode 100644 index b0a6609c7..000000000 --- a/examples/multimediawidgets/player/qmediaplaylist_p.h +++ /dev/null @@ -1,112 +0,0 @@ -// 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 - -#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 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/multimediawidgets/player/qplaylistfileparser.cpp b/examples/multimediawidgets/player/qplaylistfileparser.cpp deleted file mode 100644 index 698f81ddc..000000000 --- a/examples/multimediawidgets/player/qplaylistfileparser.cpp +++ /dev/null @@ -1,605 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#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 m_source; - QScopedPointer m_currentParser; - QByteArray m_buffer; - QUrl m_root; - QNetworkAccessManager m_mgr; - QString m_mimeType; - QPlaylistFileParser *q_ptr; - QPointer 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/multimediawidgets/player/qplaylistfileparser_p.h b/examples/multimediawidgets/player/qplaylistfileparser_p.h deleted file mode 100644 index 3d2016736..000000000 --- a/examples/multimediawidgets/player/qplaylistfileparser_p.h +++ /dev/null @@ -1,80 +0,0 @@ -// 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 - -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 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 d_ptr; -}; - -QT_END_NAMESPACE - -#endif // PLAYLISTFILEPARSER_P_H diff --git a/examples/multimediawidgets/player/videowidget.cpp b/examples/multimediawidgets/player/videowidget.cpp deleted file mode 100644 index e7f906e38..000000000 --- a/examples/multimediawidgets/player/videowidget.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "videowidget.h" - -#include -#include - -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/multimediawidgets/player/videowidget.h b/examples/multimediawidgets/player/videowidget.h deleted file mode 100644 index 3505a3fb8..000000000 --- a/examples/multimediawidgets/player/videowidget.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef VIDEOWIDGET_H -#define VIDEOWIDGET_H - -#include - -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/multimediawidgets/videographicsitem/CMakeLists.txt b/examples/multimediawidgets/videographicsitem/CMakeLists.txt deleted file mode 100644 index fb8f4d857..000000000 --- a/examples/multimediawidgets/videographicsitem/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -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}/multimediawidgets/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/multimediawidgets/videographicsitem/doc/images/video-videographicsitem.png b/examples/multimediawidgets/videographicsitem/doc/images/video-videographicsitem.png deleted file mode 100644 index e333c54a2..000000000 Binary files a/examples/multimediawidgets/videographicsitem/doc/images/video-videographicsitem.png and /dev/null differ diff --git a/examples/multimediawidgets/videographicsitem/doc/src/videographicsitem.qdoc b/examples/multimediawidgets/videographicsitem/doc/src/videographicsitem.qdoc deleted file mode 100644 index 5dcb7143e..000000000 --- a/examples/multimediawidgets/videographicsitem/doc/src/videographicsitem.qdoc +++ /dev/null @@ -1,19 +0,0 @@ -// 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/multimediawidgets/videographicsitem/main.cpp b/examples/multimediawidgets/videographicsitem/main.cpp deleted file mode 100644 index 85d1ec92b..000000000 --- a/examples/multimediawidgets/videographicsitem/main.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "videoplayer.h" - -#include -#include -#include -#include -#include - -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/multimediawidgets/videographicsitem/videographicsitem.pro b/examples/multimediawidgets/videographicsitem/videographicsitem.pro deleted file mode 100644 index 30a9b6f21..000000000 --- a/examples/multimediawidgets/videographicsitem/videographicsitem.pro +++ /dev/null @@ -1,14 +0,0 @@ -TEMPLATE = app -TARGET = videographicsitem - -QT += multimedia multimediawidgets - -HEADERS += videoplayer.h - -SOURCES += main.cpp \ - videoplayer.cpp - -target.path = $$[QT_INSTALL_EXAMPLES]/multimediawidgets/videographicsitem -INSTALLS += target - -QT+=widgets diff --git a/examples/multimediawidgets/videographicsitem/videoplayer.cpp b/examples/multimediawidgets/videographicsitem/videoplayer.cpp deleted file mode 100644 index 096fabd77..000000000 --- a/examples/multimediawidgets/videographicsitem/videoplayer.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "videoplayer.h" - -#include -#include - -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/multimediawidgets/videographicsitem/videoplayer.h b/examples/multimediawidgets/videographicsitem/videoplayer.h deleted file mode 100644 index a5be31efc..000000000 --- a/examples/multimediawidgets/videographicsitem/videoplayer.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef VIDEOPLAYER_H -#define VIDEOPLAYER_H - -#include -#include - -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/multimediawidgets/videowidget/CMakeLists.txt b/examples/multimediawidgets/videowidget/CMakeLists.txt deleted file mode 100644 index fbfaba50b..000000000 --- a/examples/multimediawidgets/videowidget/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -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}/multimediawidgets/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/multimediawidgets/videowidget/doc/images/video-videowidget.png b/examples/multimediawidgets/videowidget/doc/images/video-videowidget.png deleted file mode 100644 index a3c7bcb44..000000000 Binary files a/examples/multimediawidgets/videowidget/doc/images/video-videowidget.png and /dev/null differ diff --git a/examples/multimediawidgets/videowidget/doc/src/videowidget.qdoc b/examples/multimediawidgets/videowidget/doc/src/videowidget.qdoc deleted file mode 100644 index e999cc19c..000000000 --- a/examples/multimediawidgets/videowidget/doc/src/videowidget.qdoc +++ /dev/null @@ -1,17 +0,0 @@ -// 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/multimediawidgets/videowidget/main.cpp b/examples/multimediawidgets/videowidget/main.cpp deleted file mode 100644 index ccf5983dd..000000000 --- a/examples/multimediawidgets/videowidget/main.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "videoplayer.h" - -#include -#include -#include -#include -#include -#include - -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/multimediawidgets/videowidget/videoplayer.cpp b/examples/multimediawidgets/videowidget/videoplayer.cpp deleted file mode 100644 index 4a34bfded..000000000 --- a/examples/multimediawidgets/videowidget/videoplayer.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "videoplayer.h" - -#include -#include - -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/multimediawidgets/videowidget/videoplayer.h b/examples/multimediawidgets/videowidget/videoplayer.h deleted file mode 100644 index b06df8280..000000000 --- a/examples/multimediawidgets/videowidget/videoplayer.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef VIDEOPLAYER_H -#define VIDEOPLAYER_H - -#include -#include - -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/multimediawidgets/videowidget/videowidget.pro b/examples/multimediawidgets/videowidget/videowidget.pro deleted file mode 100644 index 603996b2e..000000000 --- a/examples/multimediawidgets/videowidget/videowidget.pro +++ /dev/null @@ -1,16 +0,0 @@ -TEMPLATE = app -TARGET = videowidget - -QT += multimedia multimediawidgets - -HEADERS = \ - videoplayer.h - -SOURCES = \ - main.cpp \ - videoplayer.cpp - -target.path = $$[QT_INSTALL_EXAMPLES]/multimediawidgets/videowidget -INSTALLS += target - -QT+=widgets diff --git a/src/multimedia/doc/qtmultimedia.qdocconf b/src/multimedia/doc/qtmultimedia.qdocconf index ba1ea34c6..d1e59d4a7 100644 --- a/src/multimedia/doc/qtmultimedia.qdocconf +++ b/src/multimedia/doc/qtmultimedia.qdocconf @@ -9,6 +9,8 @@ version = $QT_VERSION moduleheader = QtMultimedia includepaths += . +examplesinstallpath = multimedia + # The following parameters are for creating a qhp file, the qhelpgenerator # program can convert the qhp file into a qch file which can be opened in # Qt Assistant and/or Qt Creator. -- cgit v1.2.1