/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QtFrameGrabber.hxx" #include "QtPlayer.hxx" #include using namespace ::com::sun::star; namespace { inline QString toQString(const OUString& rStr) { return QString::fromUtf16(rStr.getStr(), rStr.getLength()); } } namespace avmedia::qt { QtPlayer::QtPlayer() : QtPlayer_BASE(m_aMutex) , m_lListener(m_aMutex) , m_pMediaWidgetParent(nullptr) { } bool QtPlayer::create(const OUString& rURL) { const QUrl aQUrl(toQString(rURL)); if (!aQUrl.isValid() || !aQUrl.isLocalFile()) return false; m_xMediaPlayer = std::make_unique(); m_xMediaPlayer->setSource(aQUrl); QAudioOutput* pAudioOutput = new QAudioOutput; pAudioOutput->setVolume(50); m_xMediaPlayer->setAudioOutput(pAudioOutput); return true; } void SAL_CALL QtPlayer::start() { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); m_xMediaPlayer->play(); } void SAL_CALL QtPlayer::stop() { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); // don't use QMediaPlayer::stop because XPlayer::stop should leave the position unchanged m_xMediaPlayer->pause(); } sal_Bool SAL_CALL QtPlayer::isPlaying() { osl::MutexGuard aGuard(m_aMutex); #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) assert(m_xMediaPlayer); return m_xMediaPlayer->isPlaying(); #else return false; #endif } double SAL_CALL QtPlayer::getDuration() { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); return m_xMediaPlayer->duration() / 1000.0; } void SAL_CALL QtPlayer::setMediaTime(double fTime) { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); m_xMediaPlayer->setPosition(fTime * 1000); } double SAL_CALL QtPlayer::getMediaTime() { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); return m_xMediaPlayer->position() / 1000.0; } void SAL_CALL QtPlayer::setPlaybackLoop(sal_Bool bSet) { assert(m_xMediaPlayer); const int nLoops = bSet ? QMediaPlayer::Infinite : QMediaPlayer::Once; m_xMediaPlayer->setLoops(nLoops); } sal_Bool SAL_CALL QtPlayer::isPlaybackLoop() { assert(m_xMediaPlayer); return m_xMediaPlayer->loops() == QMediaPlayer::Infinite; } void SAL_CALL QtPlayer::setVolumeDB(sal_Int16 nVolumeDB) { osl::MutexGuard aGuard(m_aMutex); // range is -40 for silence to 0 for full volume const sal_Int16 nVolume = std::clamp(nVolumeDB, -40, 0); double fValue = (nVolume + 40) / 40.0; assert(m_xMediaPlayer); QAudioOutput* pAudioOutput = m_xMediaPlayer->audioOutput(); assert(pAudioOutput); pAudioOutput->setVolume(fValue); } sal_Int16 SAL_CALL QtPlayer::getVolumeDB() { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); QAudioOutput* pAudioOutput = m_xMediaPlayer->audioOutput(); assert(pAudioOutput); double fVolume = pAudioOutput->volume(); return (fVolume * 40) - 40; } void SAL_CALL QtPlayer::setMute(sal_Bool bSet) { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); QAudioOutput* pAudioOutput = m_xMediaPlayer->audioOutput(); assert(pAudioOutput); pAudioOutput->setMuted(bSet); } sal_Bool SAL_CALL QtPlayer::isMute() { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); QAudioOutput* pAudioOutput = m_xMediaPlayer->audioOutput(); assert(pAudioOutput); return pAudioOutput->isMuted(); } awt::Size SAL_CALL QtPlayer::getPreferredPlayerWindowSize() { osl::MutexGuard aGuard(m_aMutex); assert(m_xMediaPlayer); const QMediaMetaData aMetaData = m_xMediaPlayer->metaData(); const QVariant aResolutionVariant = aMetaData.value(QMediaMetaData::Resolution); if (aResolutionVariant.canConvert()) { const QSize aResolution = aResolutionVariant.value(); return awt::Size(aResolution.width(), aResolution.height()); } return awt::Size(0, 0); } uno::Reference<::media::XPlayerWindow> SAL_CALL QtPlayer::createPlayerWindow(const uno::Sequence& rArguments) { osl::MutexGuard aGuard(m_aMutex); if (rArguments.getLength() > 1) rArguments[1] >>= m_aPlayerWidgetRect; if (rArguments.getLength() <= 2) { uno::Reference<::media::XPlayerWindow> xRet = new ::avmedia::gstreamer::Window; return xRet; } sal_IntPtr pIntPtr = 0; rArguments[2] >>= pIntPtr; SystemChildWindow* pParentWindow = reinterpret_cast(pIntPtr); if (!pParentWindow) return nullptr; const SystemEnvData* pParentEnvData = pParentWindow->GetSystemData(); if (!pParentEnvData) return nullptr; m_pMediaWidgetParent = static_cast(pParentEnvData->pWidget); assert(m_pMediaWidgetParent); // while media is loading, QMediaPlayer::hasVideo doesn't yet return // whether media actually has video; defer creating audio/video widget if (m_xMediaPlayer->mediaStatus() == QMediaPlayer::LoadingMedia) { connect(m_xMediaPlayer.get(), &QMediaPlayer::mediaStatusChanged, this, &QtPlayer::createMediaPlayerWidget, Qt::SingleShotConnection); } else { createMediaPlayerWidget(); } uno::Reference<::media::XPlayerWindow> xRet = new ::avmedia::gstreamer::Window; return xRet; } uno::Reference SAL_CALL QtPlayer::createFrameGrabber() { osl::MutexGuard aGuard(m_aMutex); rtl::Reference xFrameGrabber = new QtFrameGrabber(m_xMediaPlayer->source()); return xFrameGrabber; } void SAL_CALL QtPlayer::addPlayerListener(const css::uno::Reference& rListener) { m_lListener.addInterface(cppu::UnoType::get(), rListener); if (isReadyToPlay()) { css::lang::EventObject aEvent; aEvent.Source = getXWeak(); rListener->preferredPlayerWindowSizeAvailable(aEvent); } else { installNotify(); } } void SAL_CALL QtPlayer::removePlayerListener(const css::uno::Reference& rListener) { m_lListener.removeInterface(cppu::UnoType::get(), rListener); } OUString SAL_CALL QtPlayer::getImplementationName() { return u"com.sun.star.comp.avmedia.Player_Qt"_ustr; } sal_Bool SAL_CALL QtPlayer::supportsService(const OUString& rServiceName) { return cppu::supportsService(this, rServiceName); } uno::Sequence SAL_CALL QtPlayer::getSupportedServiceNames() { return { u"com.sun.star.media.Player_Qt"_ustr }; } void SAL_CALL QtPlayer::disposing() { osl::MutexGuard aGuard(m_aMutex); stop(); QtPlayer_BASE::disposing(); } QtPlayer::~QtPlayer() { // ensure output objects get deleted as QMediaPlayer doesn't take ownership of them std::unique_ptr xVideoWidget(m_xMediaPlayer->videoOutput()); std::unique_ptr xAudioOutput(m_xMediaPlayer->audioOutput()); m_xMediaPlayer.reset(); } bool QtPlayer::isReadyToPlay() { assert(m_xMediaPlayer); QMediaPlayer::MediaStatus eStatus = m_xMediaPlayer->mediaStatus(); return eStatus == QMediaPlayer::BufferingMedia || eStatus == QMediaPlayer::BufferedMedia || eStatus == QMediaPlayer::LoadedMedia || eStatus == QMediaPlayer::EndOfMedia; } void QtPlayer::installNotify() { connect(m_xMediaPlayer.get(), &QMediaPlayer::mediaStatusChanged, this, &QtPlayer::notifyIfReady); } void QtPlayer::uninstallNotify() { disconnect(m_xMediaPlayer.get(), &QMediaPlayer::mediaStatusChanged, this, &QtPlayer::notifyIfReady); } void QtPlayer::notifyIfReady(QMediaPlayer::MediaStatus) { if (isReadyToPlay()) { rtl::Reference xThis(this); xThis->notifyListeners(); xThis->uninstallNotify(); } } void QtPlayer::createMediaPlayerWidget() { assert(m_xMediaPlayer); assert(m_xMediaPlayer->mediaStatus() != QMediaPlayer::LoadingMedia && "Media is still loading, detecting video availability not possible."); assert(m_pMediaWidgetParent && "Parent for media widget not set"); // if media contains video, show the video output, // otherwise show an audio icon as a placeholder QWidget* pWidget; if (m_xMediaPlayer->hasVideo()) { QVideoWidget* pVideoWidget = new QVideoWidget(m_pMediaWidgetParent); pVideoWidget->setAspectRatioMode(Qt::IgnoreAspectRatio); assert(!m_xMediaPlayer->videoOutput() && "Video output already set."); m_xMediaPlayer->setVideoOutput(pVideoWidget); pWidget = pVideoWidget; } else { BitmapEx aPlaceholderIcon(u"avmedia/res/avaudiologo.png"_ustr); SvMemoryStream aMemoryStream; vcl::PngImageWriter aWriter(aMemoryStream); aWriter.write(aPlaceholderIcon); QPixmap aAudioPixmap; aAudioPixmap.loadFromData(static_cast(aMemoryStream.GetData()), aMemoryStream.TellEnd()); assert(!aAudioPixmap.isNull() && "Failed to load audio logo"); aAudioPixmap = aAudioPixmap.scaled(QSize(m_aPlayerWidgetRect.Width, m_aPlayerWidgetRect.Height)); QLabel* pLabel = new QLabel; pLabel->setPixmap(aAudioPixmap); pWidget = pLabel; } assert(pWidget); pWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // retrieve the layout (which is set in the QtObjectWidget ctor) QLayout* pLayout = m_pMediaWidgetParent->layout(); assert(pLayout); assert(pLayout->count() == 0 && "Layout already has a widget set"); pLayout->addWidget(pWidget); } void QtPlayer::notifyListeners() { comphelper::OInterfaceContainerHelper2* pContainer = m_lListener.getContainer(cppu::UnoType::get()); if (!pContainer) return; css::lang::EventObject aEvent; aEvent.Source = getXWeak(); comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); while (pIterator.hasMoreElements()) { css::uno::Reference xListener( static_cast(pIterator.next())); xListener->preferredPlayerWindowSizeAvailable(aEvent); } } } // namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */