Revision control
++ // 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 <QtMultimedia/qaudiooutput.h>
++ #include <QtCore/qfileinfo.h>
++ #include <QtCore/qglobal.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qmimedatabase.h>
++ #include <QtCore/quuid.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(lcMediaPlayer, "qt.multimedia.wasm.mediaplayer");
++
++ using namespace Qt::Literals;
++
++ 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::VideoDisplay);
++ 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->updateVideoElementGeometry(QRect(0, 0, 1280, 720)); // initial size 720p standard
++
++ connect(m_videoOutput, &QWasmVideoOutput::bufferingChanged, this,
++ &QWasmMediaPlayer::bufferingChanged);
++ connect(m_videoOutput, &QWasmVideoOutput::errorOccured, this,
++ [=](qint32 code, const QString &message ) {
++ error((QMediaPlayer::Error)code, message);
++ });
++
++ 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);
++ connect(m_videoOutput, &QWasmVideoOutput::seekableChanged, this,
++ &QWasmMediaPlayer::seekableMediaChanged);
++
++ setVideoAvailable(true);
++ }
++
++ void QWasmMediaPlayer::initAudio()
++ {
++ if (!m_audioOutput)
++ return;
++
++ 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,
++ [=](qint32 code, const QString &message ) {
++ error((QMediaPlayer::Error)code, message);
++ });
++
++ 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);
++
++ Q_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);
++ Q_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(u"audio"_s)) {
++ 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 {
++ if (mediaContent.isLocalFile()) {
++ QString sourceFile = mediaContent.toLocalFile();
++ if (db.mimeTypeForFile(QFileInfo(sourceFile)).name().contains(u"audio"_s)) {
++ setAudioAvailable(true);
++ m_audioOutput->setSource(mediaContent);
++ } else { // treat octet-stream as video
++ setVideoAvailable(true);
++ m_videoOutput->setSource(mediaContent);
++ }
++ } else {
++ // web url
++ if (!m_videoOutput->currentVideoElement().isNull()) {
++ m_videoOutput->setSource(mediaContent);
++ setVideoAvailable(true);
++ } else {
++ m_audioOutput->setSource(mediaContent);
++ setAudioAvailable(true);
++ }
++ }
++ }
++
++ 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);
++ initAudio();
++ 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 {
++ 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) {
++ 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;
++ };
++
++ Q_EMIT QPlatformMediaPlayer::error(error, errorString);
++ }
++
++ void QWasmMediaPlayer::bufferingChanged(qint32 percent)
++ {
++ m_buffering = percent != 100;
++ m_bufferPercent = percent;
++
++ updateAvailablePlaybackRanges();
++ Q_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:
++ Q_EMIT durationChanged(0);
++ break;
++ case QMediaPlayer::EndOfMedia:
++ setPositionChanged(position());
++ default:
++ break;
++ };
++ }
++
++ void QWasmMediaPlayer::setAudioAvailable(bool available)
++ {
++ if (m_audioAvailable == available)
++ return;
++
++ m_audioAvailable = available;
++ Q_EMIT audioAvailableChanged(m_audioAvailable);
++ }
++
++ void QWasmMediaPlayer::setVideoAvailable(bool available)
++ {
++ if (m_videoAvailable == available)
++ return;
++
++ if (!available)
++ m_videoSize = QSize();
++
++ m_videoAvailable = available;
++ Q_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();
++ }
++
++ void QWasmMediaPlayer::seekableMediaChanged(bool seekable)
++ {
++ seekableChanged(seekable);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qwasmmediaplayer_p.cpp"