diff options
Diffstat (limited to 'src/plugins/multimedia/wasm/mediaplayer')
4 files changed, 665 insertions, 0 deletions
diff --git a/src/plugins/multimedia/wasm/mediaplayer/qwasmmediaplayer.cpp b/src/plugins/multimedia/wasm/mediaplayer/qwasmmediaplayer.cpp new file mode 100644 index 000000000..75886b7c2 --- /dev/null +++ b/src/plugins/multimedia/wasm/mediaplayer/qwasmmediaplayer.cpp @@ -0,0 +1,475 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwasmmediaplayer_p.h" +#include <common/qwasmvideooutput_p.h> +#include <common/qwasmaudiooutput_p.h> +#include "qaudiooutput.h" + +#include <QtCore/qloggingcategory.h> +#include <QUuid> +#include <QtGlobal> +#include <QMimeDatabase> +#include <QFileInfo> + +QT_BEGIN_NAMESPACE + +static Q_LOGGING_CATEGORY(lcMediaPlayer, "qt.multimedia.wasm.mediaplayer"); + +QWasmMediaPlayer::QWasmMediaPlayer(QMediaPlayer *parent) + : QPlatformMediaPlayer(parent), + m_videoOutput(new QWasmVideoOutput), + m_State(QWasmMediaPlayer::Idle) +{ + qCDebug(lcMediaPlayer) << Q_FUNC_INFO << this; + +} + +QWasmMediaPlayer::~QWasmMediaPlayer() +{ + delete m_videoOutput; +} + +void QWasmMediaPlayer::initVideo() +{ + m_videoOutput->setVideoMode(QWasmVideoOutput::VideoOutput); + QUuid videoElementId = QUuid::createUuid(); + qCDebug(lcMediaPlayer) << Q_FUNC_INFO << "videoElementId"<< videoElementId << this; + + m_videoOutput->createVideoElement(videoElementId.toString(QUuid::WithoutBraces).toStdString()); + m_videoOutput->doElementCallbacks(); + m_videoOutput->createOffscreenElement(QSize(1280, 720)); + m_videoOutput->updateVideoElementGeometry(QRect(0, 0, 1280, 720)); // initial size 720p standard + + connect(m_videoOutput, &QWasmVideoOutput::bufferingChanged, this, + &QWasmMediaPlayer::bufferingChanged); + connect(m_videoOutput, &QWasmVideoOutput::errorOccured, this, + &QWasmMediaPlayer::errorOccured); + connect(m_videoOutput, &QWasmVideoOutput::stateChanged, this, + &QWasmMediaPlayer::mediaStateChanged); + connect(m_videoOutput, &QWasmVideoOutput::progressChanged, this, + &QWasmMediaPlayer::setPositionChanged); + connect(m_videoOutput, &QWasmVideoOutput::durationChanged, this, + &QWasmMediaPlayer::setDurationChanged); + connect(m_videoOutput, &QWasmVideoOutput::sizeChange, this, + &QWasmMediaPlayer::videoSizeChanged); + connect(m_videoOutput, &QWasmVideoOutput::readyChanged, this, + &QWasmMediaPlayer::videoOutputReady); + connect(m_videoOutput, &QWasmVideoOutput::statusChanged, this, + &QWasmMediaPlayer::onMediaStatusChanged); + connect(m_videoOutput, &QWasmVideoOutput::metaDataLoaded, this, + &QWasmMediaPlayer::videoMetaDataChanged); + + setVideoAvailable(true); +} + +void QWasmMediaPlayer::initAudio() +{ + connect(m_audioOutput->q, &QAudioOutput::deviceChanged, + this, &QWasmMediaPlayer::updateAudioDevice); + connect(m_audioOutput->q, &QAudioOutput::volumeChanged, + this, &QWasmMediaPlayer::volumeChanged); + connect(m_audioOutput->q, &QAudioOutput::mutedChanged, + this, &QWasmMediaPlayer::mutedChanged); + + connect(m_audioOutput, &QWasmAudioOutput::bufferingChanged, this, + &QWasmMediaPlayer::bufferingChanged); + connect(m_audioOutput, &QWasmAudioOutput::errorOccured, this, + &QWasmMediaPlayer::errorOccured); + connect(m_audioOutput, &QWasmAudioOutput::progressChanged, this, + &QWasmMediaPlayer::setPositionChanged); + connect(m_audioOutput, &QWasmAudioOutput::durationChanged, this, + &QWasmMediaPlayer::setDurationChanged); + connect(m_audioOutput, &QWasmAudioOutput::statusChanged, this, + &QWasmMediaPlayer::onMediaStatusChanged); + connect(m_audioOutput, &QWasmAudioOutput::stateChanged, this, + &QWasmMediaPlayer::mediaStateChanged); + setAudioAvailable(true); +} + +qint64 QWasmMediaPlayer::duration() const +{ + return m_videoOutput->getDuration(); +} + +qint64 QWasmMediaPlayer::position() const +{ + if (mediaStatus() == QMediaPlayer::EndOfMedia) + return duration(); + + if (m_videoAvailable) + return m_videoOutput->getCurrentPosition(); + + return 0; +} + +void QWasmMediaPlayer::setPosition(qint64 position) +{ + if (!isSeekable()) + return; + + const int seekPosition = (position > INT_MAX) ? INT_MAX : position; + + if (seekPosition == this->position()) + return; + + if (mediaStatus() == QMediaPlayer::EndOfMedia) + setMediaStatus(QMediaPlayer::LoadedMedia); + + if (m_videoAvailable) + return m_videoOutput->seekTo(position); + + emit positionChanged(seekPosition); +} + +void QWasmMediaPlayer::volumeChanged(float gain) +{ + if (m_State != QWasmMediaPlayer::Started) + return; + + if (m_videoAvailable) + m_videoOutput->setVolume(gain); +} + +void QWasmMediaPlayer::mutedChanged(bool muted) +{ + if (m_State != QWasmMediaPlayer::Started) + return; + + if (m_videoAvailable) + m_videoOutput->setMuted(muted); +} + +float QWasmMediaPlayer::bufferProgress() const +{ + return qBound(0.0, (m_bufferPercent * .01), 1.0); +} + +bool QWasmMediaPlayer::isAudioAvailable() const +{ + return m_audioAvailable; +} + +bool QWasmMediaPlayer::isVideoAvailable() const +{ + return m_videoAvailable; +} + +QMediaTimeRange QWasmMediaPlayer::availablePlaybackRanges() const +{ + return m_availablePlaybackRange; +} + +void QWasmMediaPlayer::updateAvailablePlaybackRanges() +{ + if (m_buffering) { + const qint64 pos = position(); + const qint64 end = (duration() / 100) * m_bufferPercent; + m_availablePlaybackRange.addInterval(pos, end); + } else if (isSeekable()) { + m_availablePlaybackRange = QMediaTimeRange(0, duration()); + } else { + m_availablePlaybackRange = QMediaTimeRange(); + } +} + +qreal QWasmMediaPlayer::playbackRate() const +{ + if (m_State != QWasmMediaPlayer::Started) + return 0; + + if (isVideoAvailable()) + return m_videoOutput->playbackRate(); + return 0; +} + +void QWasmMediaPlayer::setPlaybackRate(qreal rate) +{ + if (m_State != QWasmMediaPlayer::Started || !isVideoAvailable()) + return; + + m_videoOutput->setPlaybackRate(rate); + emit playbackRateChanged(rate); +} + +QUrl QWasmMediaPlayer::media() const +{ + return m_mediaContent; +} + +const QIODevice *QWasmMediaPlayer::mediaStream() const +{ + return m_mediaStream; +} + +void QWasmMediaPlayer::setMedia(const QUrl &mediaContent, QIODevice *stream) +{ + qCDebug(lcMediaPlayer) << Q_FUNC_INFO << mediaContent << stream; + QMimeDatabase db; + + if (mediaContent.isEmpty()) { + if (stream) { + m_mediaStream = stream; + qCDebug(lcMediaPlayer) << db.mimeTypeForData(stream).name(); + if (db.mimeTypeForData(stream).name().contains("audio")) { + setAudioAvailable(true); + m_audioOutput->setSource(m_mediaStream); + } else { // treat octet-stream as video + setVideoAvailable(true); + m_videoOutput->setSource(m_mediaStream); + } + } else { + + setMediaStatus(QMediaPlayer::NoMedia); + } + } else { + QString sourceFile = mediaContent.toLocalFile(); + qCDebug(lcMediaPlayer) << db.mimeTypeForFile(QFileInfo(sourceFile)).name(); + if (db.mimeTypeForFile(QFileInfo(sourceFile)).name().contains("audio")) { + setAudioAvailable(true); + m_audioOutput->setSource(mediaContent); + } else { // treat octet-stream as video + setVideoAvailable(true); + m_videoOutput->setSource(mediaContent); + } + } + + resetBufferingProgress(); +} + +void QWasmMediaPlayer::setVideoSink(QVideoSink *sink) +{ + if (m_videoSink == sink) + return; + + m_videoSink = sink; + + if (!m_videoSink) + return; + + initVideo(); + m_videoOutput->setSurface(sink); + setVideoAvailable(true); + if (isAudioAvailable() && m_audioOutput) + m_audioOutput->setVideoElement(m_videoOutput->currentVideoElement()); +} + +void QWasmMediaPlayer::setAudioOutput(QPlatformAudioOutput *output) +{ + if (m_audioOutput == output) + return; + + if (m_audioOutput) + m_audioOutput->q->disconnect(this); + m_audioOutput = static_cast<QWasmAudioOutput *>(output); + setAudioAvailable(true); +} + +void QWasmMediaPlayer::updateAudioDevice() +{ + if (m_audioOutput) { + m_audioOutput->setAudioDevice(m_audioOutput->q->device()); + } +} + +void QWasmMediaPlayer::play() +{ + resetCurrentLoop(); + + if (isVideoAvailable()) { + m_videoOutput->start(); + m_playWhenReady = true; + } else { + initAudio(); + if (isAudioAvailable()) { + m_audioOutput->start(); + } + } + +#ifdef DEBUG_AUDIOENGINE + QAudioEnginePrivate::checkNoError("play"); +#endif +} + +void QWasmMediaPlayer::pause() +{ + if ((m_State + & (QWasmMediaPlayer::Started | QWasmMediaPlayer::Paused + | QWasmMediaPlayer::PlaybackCompleted)) == 0) { + return; + } + if (isVideoAvailable()) { + m_videoOutput->pause(); + } else { + m_audioOutput->pause(); + stateChanged(QMediaPlayer::PausedState); + } +} + +void QWasmMediaPlayer::stop() +{ + m_playWhenReady = false; + + if (m_State == QWasmMediaPlayer::Idle || m_State == QWasmMediaPlayer::PlaybackCompleted + || m_State == QWasmMediaPlayer::Stopped) { + qWarning() << Q_FUNC_INFO << __LINE__; + return; + } + + if (isVideoAvailable()) { + m_videoOutput->stop(); + } else { + m_audioOutput->stop(); + } + +} + +bool QWasmMediaPlayer::isSeekable() const +{ + return isVideoAvailable() && m_videoOutput->isVideoSeekable(); +} + +void QWasmMediaPlayer::errorOccured(qint32 code, const QString &message) +{ + QString errorString; + QMediaPlayer::Error error = QMediaPlayer::ResourceError; + + switch (code) { + case QWasmMediaNetworkState::NetworkEmpty: // no data + break; + case QWasmMediaNetworkState::NetworkIdle: + break; + case QWasmMediaNetworkState::NetworkLoading: + break; + case QWasmMediaNetworkState::NetworkNoSource: // no source + error = QMediaPlayer::ResourceError; + errorString = message; + break; + }; + + emit QPlatformMediaPlayer::error(error, errorString); +} + +void QWasmMediaPlayer::bufferingChanged(qint32 percent) +{ + m_buffering = percent != 100; + m_bufferPercent = percent; + + updateAvailablePlaybackRanges(); + emit bufferProgressChanged(bufferProgress()); +} + +void QWasmMediaPlayer::videoSizeChanged(qint32 width, qint32 height) +{ + QSize newSize(width, height); + + if (width == 0 || height == 0 || newSize == m_videoSize) + return; + + m_videoSize = newSize; +} + +void QWasmMediaPlayer::mediaStateChanged(QWasmMediaPlayer::QWasmMediaPlayerState state) +{ + m_State = state; + QMediaPlayer::PlaybackState m_mediaPlayerState; + switch (m_State) { + case QWasmMediaPlayer::Started: + m_mediaPlayerState = QMediaPlayer::PlayingState; + break; + case QWasmMediaPlayer::Paused: + m_mediaPlayerState = QMediaPlayer::PausedState; + break; + case QWasmMediaPlayer::Stopped: + m_mediaPlayerState = QMediaPlayer::StoppedState; + break; + default: + m_mediaPlayerState = QMediaPlayer::StoppedState; + break; + }; + + QPlatformMediaPlayer::stateChanged(m_mediaPlayerState); +} + +int QWasmMediaPlayer::trackCount(TrackType trackType) +{ + Q_UNUSED(trackType) + // TODO QTBUG-108517 + return 0; // tracks.count(); +} + +void QWasmMediaPlayer::setPositionChanged(qint64 position) +{ + QPlatformMediaPlayer::positionChanged(position); +} + +void QWasmMediaPlayer::setDurationChanged(qint64 duration) +{ + QPlatformMediaPlayer::durationChanged(duration); +} + +void QWasmMediaPlayer::videoOutputReady(bool ready) +{ + setVideoAvailable(ready); + + if (m_playWhenReady && m_videoOutput->isReady()) + play(); +} + +void QWasmMediaPlayer::setMediaStatus(QMediaPlayer::MediaStatus status) +{ + mediaStatusChanged(status); + + switch (status) { + case QMediaPlayer::NoMedia: + case QMediaPlayer::InvalidMedia: + emit durationChanged(0); + break; + case QMediaPlayer::EndOfMedia: + setPositionChanged(position()); + default: + break; + }; +} + +void QWasmMediaPlayer::setAudioAvailable(bool available) +{ + if (m_audioAvailable == available) + return; + + m_audioAvailable = available; + emit audioAvailableChanged(m_audioAvailable); +} + +void QWasmMediaPlayer::setVideoAvailable(bool available) +{ + if (m_videoAvailable == available) + return; + + if (!available) + m_videoSize = QSize(); + + m_videoAvailable = available; + emit videoAvailableChanged(m_videoAvailable); +} + +void QWasmMediaPlayer::resetBufferingProgress() +{ + m_buffering = false; + m_bufferPercent = 0; + m_availablePlaybackRange = QMediaTimeRange(); +} + +void QWasmMediaPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus status) +{ + setMediaStatus(status); +} + +void QWasmMediaPlayer::videoMetaDataChanged() +{ + metaDataChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qwasmmediaplayer_p.cpp" diff --git a/src/plugins/multimedia/wasm/mediaplayer/qwasmmediaplayer_p.h b/src/plugins/multimedia/wasm/mediaplayer/qwasmmediaplayer_p.h new file mode 100644 index 000000000..9269ecdb6 --- /dev/null +++ b/src/plugins/multimedia/wasm/mediaplayer/qwasmmediaplayer_p.h @@ -0,0 +1,124 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWASMMEDIAPLAYER_H +#define QWASMMEDIAPLAYER_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 <qglobal.h> +#include <private/qplatformmediaplayer_p.h> +#include <qsize.h> +#include <qurl.h> +#include <QtCore/qpointer.h> + +QT_BEGIN_NAMESPACE + +class QWasmAudioOutput; +class QWasmVideoOutput; + +class QWasmMediaPlayer : public QObject, public QPlatformMediaPlayer +{ + Q_OBJECT + +public: + explicit QWasmMediaPlayer(QMediaPlayer *parent = 0); + ~QWasmMediaPlayer() override; + + enum QWasmMediaPlayerState { + Error, + Idle, + Uninitialized, + Preparing, + Prepared, + Started, + Paused, + Stopped, + PlaybackCompleted + }; + Q_ENUM(QWasmMediaPlayerState) + + enum QWasmMediaNetworkState { NetworkEmpty = 0, NetworkIdle, NetworkLoading, NetworkNoSource }; + Q_ENUM(QWasmMediaNetworkState) + + qint64 duration() const override; + qint64 position() const override; + float bufferProgress() const override; + bool isAudioAvailable() const override; + bool isVideoAvailable() const override; + QMediaTimeRange availablePlaybackRanges() const override; + qreal playbackRate() const override; + void setPlaybackRate(qreal rate) override; + QUrl media() const override; + const QIODevice *mediaStream() const override; + void setMedia(const QUrl &mediaContent, QIODevice *stream) override; + void setVideoSink(QVideoSink *surface) override; + void setAudioOutput(QPlatformAudioOutput *output) override; + void setPosition(qint64 position) override; + void play() override; + void pause() override; + void stop() override; + bool isSeekable() const override; + int trackCount(TrackType trackType) override; + + void updateAudioDevice(); + +private Q_SLOTS: + void volumeChanged(float volume); + void mutedChanged(bool muted); + void videoOutputReady(bool ready); + void errorOccured(qint32 code, const QString &message); + void bufferingChanged(qint32 percent); + void videoSizeChanged(qint32 width, qint32 height); + void mediaStateChanged(QWasmMediaPlayer::QWasmMediaPlayerState state); + void setPositionChanged(qint64 position); + void setDurationChanged(qint64 duration); + void videoMetaDataChanged(); + + void onMediaStatusChanged(QMediaPlayer::MediaStatus status); + +private: + void setMediaStatus(QMediaPlayer::MediaStatus status); + void setAudioAvailable(bool available); + void setVideoAvailable(bool available); + void updateAvailablePlaybackRanges(); + void resetBufferingProgress(); + + void setSubtitle(QString subtitle); + void disableTrack(TrackType trackType); + void initVideo(); + void initAudio(); + + friend class StateChangeNotifier; + + QPointer<QWasmVideoOutput> m_videoOutput; + QWasmAudioOutput *m_audioOutput = nullptr; + + QUrl m_mediaContent; + QIODevice *m_mediaStream = nullptr; + + QVideoSink *m_videoSink = nullptr; + int m_bufferPercent = -1; + bool m_audioAvailable = false; + bool m_videoAvailable = false; + QSize m_videoSize; + bool m_buffering = false; + QMediaTimeRange m_availablePlaybackRange; + int m_State; + + bool m_playWhenReady = false; + +}; + +QT_END_NAMESPACE + +#endif // QWASMMEDIAPLAYER_H diff --git a/src/plugins/multimedia/wasm/mediaplayer/qwasmvideosink.cpp b/src/plugins/multimedia/wasm/mediaplayer/qwasmvideosink.cpp new file mode 100644 index 000000000..b6fe0e8e0 --- /dev/null +++ b/src/plugins/multimedia/wasm/mediaplayer/qwasmvideosink.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwasmvideosink_p.h" + +#include <QtGui/rhi/qrhi.h> + +QT_BEGIN_NAMESPACE + +QWasmVideoSink::QWasmVideoSink(QVideoSink *parent) + : QPlatformVideoSink(parent) +{ +} + +void QWasmVideoSink::setRhi(QRhi *rhi) +{ + if (rhi && rhi->backend() != QRhi::OpenGLES2) + rhi = nullptr; + if (m_rhi == rhi) + return; + m_rhi = rhi; +} + +QT_END_NAMESPACE + +#include "moc_qwasmvideosink_p.cpp" diff --git a/src/plugins/multimedia/wasm/mediaplayer/qwasmvideosink_p.h b/src/plugins/multimedia/wasm/mediaplayer/qwasmvideosink_p.h new file mode 100644 index 000000000..5f2885249 --- /dev/null +++ b/src/plugins/multimedia/wasm/mediaplayer/qwasmvideosink_p.h @@ -0,0 +1,40 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QWASMVIDEOSINK_H +#define QWASMVIDEOSINK_H + +#include <private/qplatformvideosink_p.h> + +QT_BEGIN_NAMESPACE + +class QVideoSink; +class QRhi; + +class QWasmVideoSink : public QPlatformVideoSink +{ + Q_OBJECT + +public: + explicit QWasmVideoSink(QVideoSink *parent = 0); + + void setRhi(QRhi *) override; + +private: + QRhi *m_rhi = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QWASMVIDEOSINK_H |