Revision control

++ // Copyright (C) 2026 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 "qohosmediaplayer_p.h"
++
++ #include "qohosglobal_p.h"
++ #include "common/qohosvideooutput_p.h"
++ #include "common/qohosvideosink_p.h"
++
++ #include <private/qplatformaudiooutput_p.h>
++
++ #include <QtMultimedia/qaudiooutput.h>
++ #include <QtMultimedia/qmediaplayer.h>
++ #include <QtMultimedia/qmediametadata.h>
++ #include <QtMultimedia/qmediatimerange.h>
++ #include <QtMultimedia/qvideosink.h>
++
++ #include <QtCore/qfile.h>
++ #include <QtCore/qtimer.h>
++ #include <QtCore/qfileinfo.h>
++ #include <QtCore/qtemporaryfile.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qmetaobject.h>
++ #include <QtCore/qpointer.h>
++
++ #include <multimedia/player_framework/native_averrors.h>
++
++ QT_BEGIN_NAMESPACE
++
++ static constexpr int surfaceWaitTimeoutMs = 500;
++
++ namespace {
++
++ QMediaPlayer::MediaStatus mediaStatusFor(AVPlayerState state, bool atEnd)
++ {
++ if (atEnd)
++ return QMediaPlayer::EndOfMedia;
++
++ switch (state) {
++ case AV_IDLE:
++ return QMediaPlayer::NoMedia;
++ case AV_INITIALIZED:
++ return QMediaPlayer::LoadingMedia;
++ case AV_PREPARED:
++ case AV_STOPPED:
++ return QMediaPlayer::LoadedMedia;
++ case AV_PLAYING:
++ case AV_PAUSED:
++ return QMediaPlayer::BufferedMedia;
++ case AV_COMPLETED:
++ return QMediaPlayer::EndOfMedia;
++ case AV_RELEASED:
++ case AV_ERROR:
++ return QMediaPlayer::InvalidMedia;
++ }
++ return QMediaPlayer::NoMedia;
++ }
++
++ QMediaPlayer::PlaybackState playbackStateFor(AVPlayerState state)
++ {
++ switch (state) {
++ case AV_PLAYING:
++ return QMediaPlayer::PlayingState;
++ case AV_PAUSED:
++ return QMediaPlayer::PausedState;
++ case AV_IDLE:
++ case AV_INITIALIZED:
++ case AV_PREPARED:
++ case AV_STOPPED:
++ case AV_COMPLETED:
++ case AV_RELEASED:
++ case AV_ERROR:
++ break;
++ }
++ return QMediaPlayer::StoppedState;
++ }
++
++ AVPlaybackSpeed playbackSpeedFor(qreal rate)
++ {
++ // Map onto the closest discrete OH speed enum (SetPlaybackRate is API 20+).
++ if (rate <= 0.1875)
++ return AV_SPEED_FORWARD_0_125_X;
++ if (rate <= 0.375)
++ return AV_SPEED_FORWARD_0_25_X;
++ if (rate <= 0.625)
++ return AV_SPEED_FORWARD_0_50_X;
++ if (rate <= 0.875)
++ return AV_SPEED_FORWARD_0_75_X;
++ if (rate < 1.125)
++ return AV_SPEED_FORWARD_1_00_X;
++ if (rate < 1.375)
++ return AV_SPEED_FORWARD_1_25_X;
++ if (rate < 1.625)
++ return AV_SPEED_FORWARD_1_50_X;
++ if (rate < 1.875)
++ return AV_SPEED_FORWARD_1_75_X;
++ if (rate < 2.5)
++ return AV_SPEED_FORWARD_2_00_X;
++ return AV_SPEED_FORWARD_3_00_X;
++ }
++
++ } // namespace
++
++ QOhosMediaPlayer::QOhosMediaPlayer(QMediaPlayer *parent)
++ : QObject(parent), QPlatformMediaPlayer(parent)
++ {
++ }
++
++ QOhosMediaPlayer::~QOhosMediaPlayer()
++ {
++ releasePlayer();
++ }
++
++ qint64 QOhosMediaPlayer::duration() const
++ {
++ return m_duration;
++ }
++
++ qint64 QOhosMediaPlayer::position() const
++ {
++ return m_position.load();
++ }
++
++ void QOhosMediaPlayer::setPosition(qint64 position)
++ {
++ if (!m_player)
++ return;
++ OH_AVPlayer_Seek(m_player, static_cast<int32_t>(position), AV_SEEK_NEXT_SYNC);
++ }
++
++ float QOhosMediaPlayer::bufferProgress() const
++ {
++ return m_bufferProgress;
++ }
++
++ bool QOhosMediaPlayer::isSeekable() const
++ {
++ return m_seekable;
++ }
++
++ QMediaTimeRange QOhosMediaPlayer::availablePlaybackRanges() const
++ {
++ if (m_duration <= 0)
++ return {};
++ return QMediaTimeRange{ 0, m_duration };
++ }
++
++ qreal QOhosMediaPlayer::playbackRate() const
++ {
++ return m_playbackRate;
++ }
++
++ void QOhosMediaPlayer::setPlaybackRate(qreal rate)
++ {
++ if (qFuzzyCompare(m_playbackRate, rate))
++ return;
++ m_playbackRate = rate;
++ if (m_player)
++ OH_AVPlayer_SetPlaybackSpeed(m_player, playbackSpeedFor(rate));
++ playbackRateChanged(rate);
++ }
++
++ QUrl QOhosMediaPlayer::media() const
++ {
++ return m_media;
++ }
++
++ const QIODevice *QOhosMediaPlayer::mediaStream() const
++ {
++ return m_stream;
++ }
++
++ void QOhosMediaPlayer::setMedia(const QUrl &media, QIODevice *stream)
++ {
++ m_media = media;
++ m_stream = stream;
++
++ if (m_player)
++ OH_AVPlayer_Reset(m_player);
++ clearSource();
++
++ if (media.isEmpty()) {
++ m_duration = 0;
++ m_position.store(0);
++ m_pendingSetMedia = false;
++ mediaStatusChanged(QMediaPlayer::NoMedia);
++ stateChanged(QMediaPlayer::StoppedState);
++ return;
++ }
++
++ // Reject obviously bad file-like sources synchronously so the test
++ // QMediaPlayer-style "invalid file" cases see ResourceError immediately
++ // instead of waiting forever on the deferred surface. QMediaPlayer's
++ // observable contract is "Loading -> Invalid", so emit Loading first even
++ // when the failure is immediate.
++ const QString scheme = media.scheme();
++ if (media.isLocalFile() || scheme.isEmpty() || scheme == QLatin1String("qrc")) {
++ QString openPath;
++ if (media.isLocalFile())
++ openPath = media.toLocalFile();
++ else if (scheme == QLatin1String("qrc"))
++ openPath = QLatin1Char(':') + media.path();
++ else
++ openPath = media.toString();
++ QFile probe(openPath);
++ if (!probe.open(QIODevice::ReadOnly)) {
++ mediaStatusChanged(QMediaPlayer::LoadingMedia);
++ setInvalidMediaWithError(QMediaPlayer::ResourceError,
++ QStringLiteral("Cannot open '%1'").arg(openPath));
++ return;
++ }
++ }
++
++ m_pendingSetMedia = false;
++ m_sourceApplied = false;
++ m_surfaceWaitStarted = false;
++ // Apply on the next event-loop cycle so a videoOutput assigned right after
++ // the source is in place before we pick a prepare path.
++ QMetaObject::invokeMethod(this, &QOhosMediaPlayer::tryApplyPendingSource,
++ Qt::QueuedConnection);
++ }
++
++ void QOhosMediaPlayer::tryApplyPendingSource()
++ {
++ if (m_sourceApplied || m_media.isEmpty())
++ return;
++
++ // The surface can only be attached between SetSource and Prepare, so wait
++ // for it. A sink with no renderer never gets an RHI: give up and prepare
++ // without video rather than stall.
++ if (m_videoOutput && m_videoSink && !m_videoSink->rhi()) {
++ if (!m_surfaceWaitStarted) {
++ m_surfaceWaitStarted = true;
++ QTimer::singleShot(surfaceWaitTimeoutMs, this, [this] {
++ if (!m_sourceApplied && m_pendingSetMedia && !m_media.isEmpty()) {
++ m_pendingSetMedia = false;
++ m_sourceApplied = true;
++ applyPendingSource();
++ }
++ });
++ }
++ m_pendingSetMedia = true;
++ mediaStatusChanged(QMediaPlayer::LoadingMedia);
++ return;
++ }
++
++ m_sourceApplied = true;
++ applyPendingSource();
++ }
++
++ void QOhosMediaPlayer::applyPendingSource()
++ {
++ if (!ensurePlayer()) {
++ setInvalidMediaWithError(QMediaPlayer::ResourceError,
++ QStringLiteral("Failed to create OH_AVPlayer"));
++ return;
++ }
++
++ OH_AVPlayer_SetAudioRendererInfo(m_player, AUDIOSTREAM_USAGE_MOVIE);
++
++ m_videoSurfaceAttached = false;
++
++ // Surface the LoadingMedia status before we hand the source to OH_AVPlayer
++ // so any synchronous SetURLSource/SetFDSource rejection still produces the
++ // observable Loading -> Invalid transition QMediaPlayer guarantees.
++ mediaStatusChanged(QMediaPlayer::LoadingMedia);
++
++ OH_AVErrCode result = AV_ERR_OK;
++ const QString scheme = m_media.scheme();
++ const bool isQrc = scheme == QLatin1String("qrc");
++ const bool isFileLike = m_media.isLocalFile() || scheme.isEmpty() || isQrc;
++ if (isFileLike) {
++ QString openPath;
++ if (m_media.isLocalFile()) {
++ openPath = m_media.toLocalFile();
++ } else if (isQrc) {
++ // QFile understands ":/path" for embedded resources.
++ openPath = QLatin1Char(':') + m_media.path();
++ } else {
++ // Bare relative paths.
++ openPath = m_media.toString();
++ }
++ auto file = std::make_unique<QFile>(openPath);
++ if (!file->open(QIODevice::ReadOnly)) {
++ setInvalidMediaWithError(QMediaPlayer::ResourceError,
++ QStringLiteral("Cannot open '%1'").arg(openPath));
++ return;
++ }
++ const int fd = file->handle();
++ const qint64 size = file->size();
++ if (fd < 0) {
++ // Resource-backed QFile is mapped, not file-descriptor-backed —
++ // AVPlayer needs a real fd, so spool to a temp file before handing
++ // it off.
++ auto temp = std::make_unique<QTemporaryFile>();
++ if (!temp->open() || temp->write(file->readAll()) < 0) {
++ setInvalidMediaWithError(QMediaPlayer::ResourceError,
++ QStringLiteral("Cannot stage '%1'").arg(openPath));
++ return;
++ }
++ temp->flush();
++ temp->seek(0);
++ const int tempFd = temp->handle();
++ const qint64 tempSize = temp->size();
++ result = OH_AVPlayer_SetFDSource(m_player, tempFd, 0, tempSize);
++ m_sourceFile = std::move(temp);
++ } else {
++ result = OH_AVPlayer_SetFDSource(m_player, fd, 0, size);
++ m_sourceFile = std::move(file);
++ }
++ } else {
++ const QByteArray url = m_media.toString(QUrl::FullyEncoded).toUtf8();
++ result = OH_AVPlayer_SetURLSource(m_player, url.constData());
++ }
++
++ if (result != AV_ERR_OK) {
++ qCWarning(qLcOhosMediaPlugin) << "Source rejected, error" << int(result);
++ setInvalidMediaWithError(QMediaPlayer::ResourceError,
++ QStringLiteral("OH_AVPlayer rejected the source"));
++ clearSource();
++ return;
++ }
++
++ if (m_videoOutput) {
++ if (auto *window = m_videoOutput->nativeWindow()) {
++ if (OH_AVPlayer_SetVideoSurface(m_player, window) == AV_ERR_OK)
++ m_videoSurfaceAttached = true;
++ else
++ qCWarning(qLcOhosMediaPlugin) << "OH_AVPlayer_SetVideoSurface failed";
++ }
++ }
++
++ result = OH_AVPlayer_Prepare(m_player);
++ if (result != AV_ERR_OK) {
++ qCWarning(qLcOhosMediaPlugin) << "Prepare failed, error" << int(result);
++ setInvalidMediaWithError(QMediaPlayer::ResourceError,
++ QStringLiteral("OH_AVPlayer_Prepare failed"));
++ clearSource();
++ }
++ }
++
++ void QOhosMediaPlayer::play()
++ {
++ if (!m_player) {
++ m_pendingPlay = !m_media.isEmpty();
++ return;
++ }
++
++ if (m_avState == AV_PREPARED || m_avState == AV_PAUSED || m_avState == AV_COMPLETED
++ || m_avState == AV_STOPPED) {
++ m_pendingPlay = false;
++ OH_AVPlayer_Play(m_player);
++ } else {
++ // Prepare hasn't finished yet — defer play to AV_INFO_TYPE_STATE_CHANGE
++ m_pendingPlay = true;
++ }
++ }
++
++ void QOhosMediaPlayer::pause()
++ {
++ m_pendingPlay = false;
++ if (m_player && m_avState == AV_PLAYING)
++ OH_AVPlayer_Pause(m_player);
++ }
++
++ void QOhosMediaPlayer::stop()
++ {
++ m_pendingPlay = false;
++ if (!m_player)
++ return;
++ if (m_avState == AV_PLAYING || m_avState == AV_PAUSED || m_avState == AV_PREPARED
++ || m_avState == AV_COMPLETED)
++ OH_AVPlayer_Stop(m_player);
++ }
++
++ void QOhosMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
++ {
++ m_audioOutput = output;
++ applyVolume();
++ }
++
++ void QOhosMediaPlayer::setVideoSink(QVideoSink *sink)
++ {
++ m_videoSink = sink;
++
++ if (!sink) {
++ m_videoOutput.reset();
++ return;
++ }
++
++ if (!m_videoOutput) {
++ m_videoOutput = std::make_unique<QOhosVideoOutput>(
++ sink, QOhosVideoOutput::ContentSource::MediaPlayer, this);
++ connect(m_videoOutput.get(), &QOhosVideoOutput::surfaceReady, this,
++ &QOhosMediaPlayer::onVideoSurfaceReady);
++ }
++ }
++
++ int QOhosMediaPlayer::trackCount(TrackType type)
++ {
++ // OH_AVPlayer doesn't enumerate per-stream metadata up front; expose the
++ // detected presence of audio/video as a single default track so QMediaPlayer
++ // consumers can ask for activeTrack(0) and get a sensible answer.
++ switch (type) {
++ case TrackType::VideoStream:
++ return m_hasVideoTrack ? 1 : 0;
++ case TrackType::AudioStream:
++ return m_hasAudioTrack ? 1 : 0;
++ case TrackType::SubtitleStream:
++ break;
++ }
++ return 0;
++ }
++
++ QMediaMetaData QOhosMediaPlayer::trackMetaData(TrackType type, int streamNumber)
++ {
++ if (streamNumber != 0 || trackCount(type) == 0)
++ return {};
++ QMediaMetaData md;
++ md.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::AnyLanguage));
++ return md;
++ }
++
++ QMediaMetaData QOhosMediaPlayer::metaData() const
++ {
++ QMediaMetaData md;
++ if (m_duration > 0)
++ md.insert(QMediaMetaData::Duration, m_duration);
++ if (m_videoWidth > 0 && m_videoHeight > 0)
++ md.insert(QMediaMetaData::Resolution, QSize{ m_videoWidth, m_videoHeight });
++ return md;
++ }
++
++ int QOhosMediaPlayer::activeTrack(TrackType type)
++ {
++ return trackCount(type) > 0 ? 0 : -1;
++ }
++
++ void QOhosMediaPlayer::onVideoSurfaceReady()
++ {
++ if (m_pendingSetMedia && !m_media.isEmpty()) {
++ m_pendingSetMedia = false;
++ m_sourceApplied = true;
++ applyPendingSource();
++ }
++ }
++
++ void QOhosMediaPlayer::handleStateChange(AVPlayerState newState)
++ {
++ if (m_avState == newState)
++ return;
++ m_avState = newState;
++
++ // AV_IDLE is a transient state that OH_AVPlayer enters during Reset() as
++ // setMedia() is replacing the source. The user-facing NoMedia status is
++ // already emitted from setMedia(QUrl{}); leaking another NoMedia between a
++ // LoadingMedia/Invalid transition breaks observable status sequences.
++ if (newState == AV_IDLE)
++ return;
++
++ if (newState == AV_PREPARED) {
++ int32_t durationMs = 0;
++ if (OH_AVPlayer_GetDuration(m_player, &durationMs) == AV_ERR_OK) {
++ if (m_duration != durationMs) {
++ m_duration = durationMs;
++ durationChanged(m_duration);
++ }
++ }
++ m_seekable = m_duration > 0;
++ seekableChanged(m_seekable);
++
++ int32_t videoWidth = 0;
++ int32_t videoHeight = 0;
++ OH_AVPlayer_GetVideoWidth(m_player, &videoWidth);
++ OH_AVPlayer_GetVideoHeight(m_player, &videoHeight);
++ const bool hasVideo = videoWidth > 0 && videoHeight > 0;
++ if (hasVideo && m_videoOutput)
++ m_videoOutput->setVideoSize(QSize{ videoWidth, videoHeight });
++ videoAvailableChanged(hasVideo);
++ audioAvailableChanged(true);
++ const bool prevHasVideo = m_hasVideoTrack;
++ const bool prevHasAudio = m_hasAudioTrack;
++ m_hasVideoTrack = hasVideo;
++ m_hasAudioTrack = true;
++ if (prevHasVideo != m_hasVideoTrack || prevHasAudio != m_hasAudioTrack) {
++ tracksChanged();
++ activeTracksChanged();
++ }
++ m_videoWidth = videoWidth;
++ m_videoHeight = videoHeight;
++ metaDataChanged();
++ }
++
++ mediaStatusChanged(mediaStatusFor(newState, false));
++ stateChanged(playbackStateFor(newState));
++
++ if (newState == AV_PREPARED && m_pendingPlay) {
++ m_pendingPlay = false;
++ OH_AVPlayer_Play(m_player);
++ }
++ }
++
++ void QOhosMediaPlayer::handleEndOfStream()
++ {
++ mediaStatusChanged(QMediaPlayer::EndOfMedia);
++ stateChanged(QMediaPlayer::StoppedState);
++ }
++
++ void QOhosMediaPlayer::handleSeekDone()
++ {
++ // Position will refresh via AV_INFO_TYPE_POSITION_UPDATE.
++ }
++
++ void QOhosMediaPlayer::handleResolutionChange()
++ {
++ if (!m_player)
++ return;
++ int32_t width = 0;
++ int32_t height = 0;
++ OH_AVPlayer_GetVideoWidth(m_player, &width);
++ OH_AVPlayer_GetVideoHeight(m_player, &height);
++ m_videoWidth = width;
++ m_videoHeight = height;
++ if (m_videoOutput && width > 0 && height > 0)
++ m_videoOutput->setVideoSize(QSize{ width, height });
++ videoAvailableChanged(width > 0 && height > 0);
++ }
++
++ void QOhosMediaPlayer::handleBufferingUpdate(int bufferingPercent)
++ {
++ const float progress = std::clamp(bufferingPercent / 100.0f, 0.0f, 1.0f);
++ if (qFuzzyCompare(m_bufferProgress, progress))
++ return;
++ m_bufferProgress = progress;
++ bufferProgressChanged(progress);
++ }
++
++ void QOhosMediaPlayer::handleError(int32_t errorCode, const QString &errorMsg)
++ {
++ qCWarning(qLcOhosMediaPlugin) << "OH_AVPlayer error" << errorCode << errorMsg;
++ setInvalidMediaWithError(QMediaPlayer::ResourceError,
++ errorMsg.isEmpty() ? QStringLiteral("AVPlayer error %1").arg(errorCode)
++ : errorMsg);
++ }
++
++ void QOhosMediaPlayer::releasePlayer()
++ {
++ if (!m_player)
++ return;
++ OH_AVPlayer_SetOnInfoCallback(m_player, nullptr, nullptr);
++ OH_AVPlayer_SetOnErrorCallback(m_player, nullptr, nullptr);
++ OH_AVPlayer_ReleaseSync(m_player);
++ m_player = nullptr;
++ clearSource();
++ }
++
++ void QOhosMediaPlayer::clearSource()
++ {
++ m_sourceFile.reset();
++ const bool changed = m_hasVideoTrack || m_hasAudioTrack;
++ m_hasVideoTrack = false;
++ m_hasAudioTrack = false;
++ const bool hadMetadata = m_duration > 0 || m_videoWidth > 0 || m_videoHeight > 0;
++ m_videoWidth = 0;
++ m_videoHeight = 0;
++ if (changed) {
++ tracksChanged();
++ activeTracksChanged();
++ }
++ if (hadMetadata)
++ metaDataChanged();
++ }
++
++ bool QOhosMediaPlayer::ensurePlayer()
++ {
++ if (m_player)
++ return true;
++ m_player = OH_AVPlayer_Create();
++ if (!m_player) {
++ qCWarning(qLcOhosMediaPlugin) << "OH_AVPlayer_Create failed";
++ return false;
++ }
++ OH_AVPlayer_SetOnInfoCallback(m_player, &QOhosMediaPlayer::onInfoTrampoline, this);
++ OH_AVPlayer_SetOnErrorCallback(m_player, &QOhosMediaPlayer::onErrorTrampoline, this);
++ return true;
++ }
++
++ void QOhosMediaPlayer::applyVolume()
++ {
++ if (!m_player)
++ return;
++
++ float volume = 1.0f;
++ if (m_audioOutput)
++ volume = m_audioOutput->muted ? 0.0f : m_audioOutput->volume;
++
++ OH_AVPlayer_SetVolume(m_player, volume, volume);
++ }
++
++ void QOhosMediaPlayer::onInfoTrampoline(OH_AVPlayer *player, AVPlayerOnInfoType type,
++ OH_AVFormat *body, void *userData)
++ {
++ Q_UNUSED(body)
++ Q_UNUSED(player)
++ auto *self = reinterpret_cast<QOhosMediaPlayer *>(userData);
++ if (!self)
++ return;
++
++ switch (type) {
++ case AV_INFO_TYPE_STATE_CHANGE: {
++ AVPlayerState state{ AV_IDLE };
++ OH_AVPlayer_GetState(player, &state);
++ QMetaObject::invokeMethod(
++ self, [self, state]() { self->handleStateChange(state); }, Qt::QueuedConnection);
++ break;
++ }
++ case AV_INFO_TYPE_POSITION_UPDATE: {
++ int32_t pos = 0;
++ OH_AVPlayer_GetCurrentTime(player, &pos);
++ self->m_position.store(pos);
++ QMetaObject::invokeMethod(
++ self, [self, pos]() { self->positionChanged(qint64{ pos }); },
++ Qt::QueuedConnection);
++ break;
++ }
++ case AV_INFO_TYPE_DURATION_UPDATE: {
++ int32_t dur = 0;
++ OH_AVPlayer_GetDuration(player, &dur);
++ QMetaObject::invokeMethod(
++ self,
++ [self, dur]() {
++ if (self->m_duration == dur)
++ return;
++ self->m_duration = dur;
++ self->durationChanged(qint64{ dur });
++ },
++ Qt::QueuedConnection);
++ break;
++ }
++ case AV_INFO_TYPE_EOS:
++ QMetaObject::invokeMethod(
++ self, [self]() { self->handleEndOfStream(); }, Qt::QueuedConnection);
++ break;
++ case AV_INFO_TYPE_SEEKDONE:
++ QMetaObject::invokeMethod(
++ self, [self]() { self->handleSeekDone(); }, Qt::QueuedConnection);
++ break;
++ case AV_INFO_TYPE_RESOLUTION_CHANGE:
++ QMetaObject::invokeMethod(
++ self, [self]() { self->handleResolutionChange(); }, Qt::QueuedConnection);
++ break;
++ default:
++ break;
++ }
++ }
++
++ void QOhosMediaPlayer::onErrorTrampoline(OH_AVPlayer *, int32_t errorCode, const char *errorMsg,
++ void *userData)
++ {
++ auto *self = reinterpret_cast<QOhosMediaPlayer *>(userData);
++ if (!self)
++ return;
++ QString message = errorMsg ? QString::fromUtf8(errorMsg) : QString{};
++ QMetaObject::invokeMethod(
++ self, [self, errorCode, message]() { self->handleError(errorCode, message); },
++ Qt::QueuedConnection);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qohosmediaplayer_p.cpp"