diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /avmedia/source | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'avmedia/source')
50 files changed, 8764 insertions, 0 deletions
diff --git a/avmedia/source/framework/MediaControlBase.cxx b/avmedia/source/framework/MediaControlBase.cxx new file mode 100644 index 000000000..534c27c2c --- /dev/null +++ b/avmedia/source/framework/MediaControlBase.cxx @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include <avmedia/MediaControlBase.hxx> +#include <avmedia/mediaplayer.hxx> +#include <avmedia/mediaitem.hxx> +#include <tools/time.hxx> +#include <unotools/localedatawrapper.hxx> +#include <strings.hrc> +#include <helpids.h> +#include <mediamisc.hxx> + +constexpr sal_Int32 AVMEDIA_DB_RANGE = -40; +constexpr double AVMEDIA_LINEINCREMENT = 1.0; +constexpr double AVMEDIA_PAGEINCREMENT = 10.0; + +namespace avmedia { + +MediaControlBase::MediaControlBase() + : mbCurrentlySettingZoom(false) +{ +} + +void MediaControlBase::UpdateTimeField( MediaItem const & aMediaItem, double fTime ) +{ + if( aMediaItem.getURL().isEmpty()) + return; + + OUString aTimeString; + + SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData(); + + aTimeString += rLocaleData.getDuration( tools::Time( 0, 0, static_cast< sal_uInt32 >( floor( fTime ) ) ) ) + + " / " + + rLocaleData.getDuration( tools::Time( 0, 0, static_cast< sal_uInt32 >( floor( aMediaItem.getDuration() ) )) ); + + if( mxTimeEdit->get_text() != aTimeString ) + mxTimeEdit->set_text( aTimeString ); +} + +void MediaControlBase::UpdateVolumeSlider( MediaItem const & aMediaItem ) +{ + if( aMediaItem.getURL().isEmpty() ) + mxVolumeSlider->set_sensitive(false); + else + { + mxVolumeSlider->set_sensitive(true); + const sal_Int32 nVolumeDB = aMediaItem.getVolumeDB(); + mxVolumeSlider->set_value( std::clamp( nVolumeDB, AVMEDIA_DB_RANGE, sal_Int32(0)) ); + } +} + +void MediaControlBase::UpdateTimeSlider( MediaItem const & aMediaItem ) +{ + if( aMediaItem.getURL().isEmpty() ) + mxTimeSlider->set_sensitive(false); + else + { + mxTimeSlider->set_sensitive(true); + + const double fDuration = aMediaItem.getDuration(); + + if( fDuration > 0.0 ) + { + const double fTime = std::min( aMediaItem.getTime(), fDuration ); + + bool bChanged(false); + int nStep(0), nPage(0); + mxTimeSlider->get_increments(nStep, nPage); + if (!nStep) + { + nStep = AVMEDIA_TIME_RANGE * AVMEDIA_LINEINCREMENT / fDuration; + bChanged = true; + } + if (!nPage) + { + nPage = AVMEDIA_TIME_RANGE * AVMEDIA_PAGEINCREMENT / fDuration; + bChanged = true; + } + if (bChanged) + mxTimeSlider->set_increments(nStep, nPage); + + mxTimeSlider->set_value(fTime / fDuration * AVMEDIA_TIME_RANGE); + } + } +} + +void MediaControlBase::InitializeWidgets() +{ + mxPlayToolBox->set_item_help_id("play", HID_AVMEDIA_TOOLBOXITEM_PLAY); + mxPlayToolBox->set_item_help_id("pause", HID_AVMEDIA_TOOLBOXITEM_PAUSE); + mxPlayToolBox->set_item_help_id("stop", HID_AVMEDIA_TOOLBOXITEM_STOP); + mxPlayToolBox->set_item_help_id("loop", HID_AVMEDIA_TOOLBOXITEM_LOOP); + mxMuteToolBox->set_item_help_id("mute", HID_AVMEDIA_TOOLBOXITEM_MUTE); + + mxZoomListBox->append(OUString::number(AVMEDIA_ZOOMLEVEL_50), AvmResId( AVMEDIA_STR_ZOOM_50 )); + mxZoomListBox->append(OUString::number(AVMEDIA_ZOOMLEVEL_100), AvmResId( AVMEDIA_STR_ZOOM_100 )); + mxZoomListBox->append(OUString::number(AVMEDIA_ZOOMLEVEL_200), AvmResId( AVMEDIA_STR_ZOOM_200 )); + mxZoomListBox->append(OUString::number(AVMEDIA_ZOOMLEVEL_FIT), AvmResId( AVMEDIA_STR_ZOOM_FIT )); + mxZoomListBox->set_help_id( HID_AVMEDIA_ZOOMLISTBOX ); + mxZoomListBox->set_tooltip_text(AvmResId( AVMEDIA_STR_ZOOM_TOOLTIP )); + + mxTimeEdit->set_text( " 00:00:00/00:00:00 " ); + mxTimeEdit->set_help_id( HID_AVMEDIA_TIMEEDIT ); + mxTimeEdit->set_sensitive(false); + + mxVolumeSlider->set_range(AVMEDIA_DB_RANGE, 0); + mxVolumeSlider->set_tooltip_text( AvmResId( AVMEDIA_STR_VOLUME )); + mxVolumeSlider->set_help_id( HID_AVMEDIA_VOLUMESLIDER ); + + mxTimeSlider->set_range( 0, AVMEDIA_TIME_RANGE ); + mxTimeSlider->set_tooltip_text( AvmResId( AVMEDIA_STR_POSITION )); +} + +void MediaControlBase::UpdatePlayState(const MediaItem& rMediaItem) +{ + if (rMediaItem.getState() == MediaState::Play) + { + mxPlayToolBox->set_item_active("play", true); + mxPlayToolBox->set_item_active("pause", false); + mxPlayToolBox->set_item_active("stop", false); + } + else if( rMediaItem.getState() == MediaState::Pause ) + { + mxPlayToolBox->set_item_active("play", false); + mxPlayToolBox->set_item_active("pause", true); + mxPlayToolBox->set_item_active("stop", false); + } + else + { + mxPlayToolBox->set_item_active("play", false); + mxPlayToolBox->set_item_active("pause", false); + mxPlayToolBox->set_item_active("stop", true); + } +} + +void MediaControlBase::UpdateToolBoxes(const MediaItem& rMediaItem) +{ + const bool bValidURL = !rMediaItem.getURL().isEmpty(); + mxPlayToolBox->set_item_sensitive("play", bValidURL); + mxPlayToolBox->set_item_sensitive("pause", bValidURL); + mxPlayToolBox->set_item_sensitive("stop", bValidURL); + mxPlayToolBox->set_item_sensitive("loop", bValidURL); + mxMuteToolBox->set_item_sensitive("mute", bValidURL); + if( !bValidURL ) + { + mxZoomListBox->set_sensitive(false); + mxMuteToolBox->set_sensitive(false); + } + else + { + mxPlayToolBox->set_sensitive(true); + mxMuteToolBox->set_sensitive(true); + UpdatePlayState(rMediaItem); + mxPlayToolBox->set_item_active("loop", rMediaItem.isLoop()); + mxMuteToolBox->set_item_active("mute", rMediaItem.isMute()); + if (!mbCurrentlySettingZoom) + { + sal_uInt16 nSelectEntryPos ; + + switch( rMediaItem.getZoom() ) + { + case css::media::ZoomLevel_ZOOM_1_TO_2: + nSelectEntryPos = AVMEDIA_ZOOMLEVEL_50; + break; + case css::media::ZoomLevel_ORIGINAL: + nSelectEntryPos = AVMEDIA_ZOOMLEVEL_100; + break; + case css::media::ZoomLevel_ZOOM_2_TO_1: + nSelectEntryPos = AVMEDIA_ZOOMLEVEL_200; + break; + case css::media::ZoomLevel_FIT_TO_WINDOW_FIXED_ASPECT: + nSelectEntryPos = AVMEDIA_ZOOMLEVEL_FIT; + break; + case css::media::ZoomLevel_FIT_TO_WINDOW: + nSelectEntryPos = AVMEDIA_ZOOMLEVEL_SCALED; + break; + + default: + nSelectEntryPos = AVMEDIA_ZOOMLEVEL_INVALID; + break; + } + + if( nSelectEntryPos != AVMEDIA_ZOOMLEVEL_INVALID ) + { + mxZoomListBox->show(); + mxZoomListBox->set_sensitive(true); + mxZoomListBox->set_active(nSelectEntryPos); + } + else + mxZoomListBox->set_sensitive(false); + } + } +} + +void MediaControlBase::SelectPlayToolBoxItem( MediaItem& aExecItem, MediaItem const & aItem, std::string_view rId) +{ + if (rId == "apply") + { + MediaFloater* pFloater = avmedia::getMediaFloater(); + + if( pFloater ) + pFloater->dispatchCurrentURL(); + } + else if (rId == "play") + { + aExecItem.setState( MediaState::Play ); + + if( aItem.getTime() == aItem.getDuration() ) + aExecItem.setTime( 0.0 ); + else + aExecItem.setTime( aItem.getTime() ); + + UpdatePlayState(aExecItem); + } + else if (rId == "pause") + { + aExecItem.setState( MediaState::Pause ); + + UpdatePlayState(aExecItem); + } + else if (rId == "stop") + { + aExecItem.setState( MediaState::Stop ); + aExecItem.setTime( 0.0 ); + + UpdatePlayState(aExecItem); + } + else if (rId == "mute") + { + aExecItem.setMute( mxMuteToolBox->get_item_active("mute") ); + } + else if (rId == "loop") + { + aExecItem.setLoop( mxPlayToolBox->get_item_active("loop") ); + } +} + +void MediaControlBase::disposeWidgets() +{ + mxZoomListBox.reset(); + mxTimeEdit.reset(); + mxVolumeSlider.reset(); + mxMuteToolBox.reset(); + mxTimeSlider.reset(); + mxPlayToolBox.reset(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/framework/mediacontrol.cxx b/avmedia/source/framework/mediacontrol.cxx new file mode 100644 index 000000000..23d700595 --- /dev/null +++ b/avmedia/source/framework/mediacontrol.cxx @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <mediacontrol.hxx> +#include <strings.hrc> +#include <mediamisc.hxx> +#include <avmedia/mediawindow.hxx> +#include <helpids.h> +#include <vcl/weld.hxx> +#include <avmedia/MediaControlBase.hxx> + +namespace avmedia +{ + +MediaControl::MediaControl( vcl::Window* pParent, MediaControlStyle eControlStyle ) : + // MediaControlStyle::MultiLine is the normal docking windows of tools->media player + // MediaControlStyle::SingleLine is the toolbar of view->toolbar->media playback + InterimItemWindow(pParent, eControlStyle == MediaControlStyle::MultiLine ? + OUString("svx/ui/mediawindow.ui") : + OUString("svx/ui/medialine.ui"), + "MediaWindow"), + maIdle( "avmedia MediaControl Idle" ), + maChangeTimeIdle( "avmedia MediaControl Change Time Idle" ), + maItem( 0, AVMediaSetMask::ALL ), + mbLocked( false ), + meControlStyle( eControlStyle ) +{ + mxPlayToolBox = m_xBuilder->weld_toolbar("playtoolbox"); + mxTimeSlider = m_xBuilder->weld_scale("timeslider"); + mxMuteToolBox = m_xBuilder->weld_toolbar("mutetoolbox"); + mxVolumeSlider = m_xBuilder->weld_scale("volumeslider"); + mxZoomListBox = m_xBuilder->weld_combo_box("zoombox"); + mxTimeEdit = m_xBuilder->weld_entry("timeedit"); + mxMediaPath = m_xBuilder->weld_label("url"); + + InitializeWidgets(); + + mxPlayToolBox->connect_clicked( LINK( this, MediaControl, implSelectHdl ) ); + + mxTimeSlider->connect_value_changed( LINK( this, MediaControl, implTimeHdl ) ); + // when changing the time, use this to do the time change after active scrolling + // has stopped for a little which + maChangeTimeIdle.SetPriority( TaskPriority::LOWEST ); + maChangeTimeIdle.SetInvokeHandler( LINK( this, MediaControl, implTimeEndHdl ) ); + + mxTimeEdit->set_text(" 00:00:00/00:00:00 "); + Size aTextSize = mxTimeEdit->get_preferred_size(); + mxTimeEdit->set_size_request(aTextSize.Width(), aTextSize.Height()); + mxTimeEdit->set_text(OUString()); + + mxMuteToolBox->connect_clicked( LINK( this, MediaControl, implSelectHdl ) ); + mxVolumeSlider->connect_value_changed( LINK( this, MediaControl, implVolumeHdl ) ); + + mxZoomListBox->connect_changed( LINK( this, MediaControl, implZoomSelectHdl ) ); + mxZoomListBox->set_help_id(HID_AVMEDIA_ZOOMLISTBOX); + + const OUString aMediaPath( AvmResId( AVMEDIA_MEDIA_PATH_DEFAULT ) ); + mxMediaPath->set_label(aMediaPath); + if (meControlStyle == MediaControlStyle::SingleLine) + mxMediaPath->set_size_request(mxMediaPath->get_preferred_size().Width() + 400, -1); // maybe extend the no. 400 to span the screen width + + // we want time field + progress slider to update as the media plays + // give this task a lower prio than REPAINT so that UI updates are not starved + maIdle.SetPriority( TaskPriority::POST_PAINT ); + maIdle.SetInvokeHandler( LINK( this, MediaControl, implTimeoutHdl ) ); +} + +void MediaControl::InitializeWidgets() +{ + if( meControlStyle != MediaControlStyle::SingleLine ) + { + mxPlayToolBox->set_item_help_id("open", HID_AVMEDIA_TOOLBOXITEM_OPEN); + mxPlayToolBox->set_item_help_id("apply", HID_AVMEDIA_TOOLBOXITEM_INSERT); + } + avmedia::MediaControlBase::InitializeWidgets(); +} + +MediaControl::~MediaControl() +{ + disposeOnce(); +} + +void MediaControl::dispose() +{ + disposeWidgets(); + mxMediaPath.reset(); + InterimItemWindow::dispose(); +} + +void MediaControl::UpdateURLField(MediaItem const & tempItem) +{ + const OUString aURL( AvmResId(AVMEDIA_MEDIA_PATH) + ": " + tempItem.getURL() ) ; + mxMediaPath->set_label(aURL); +} + +void MediaControl::setState( const MediaItem& rItem ) +{ + if (mbLocked) + return; + bool bChanged = maItem.merge(rItem); + if (bChanged) + { + if( rItem.getURL().isEmpty() && meControlStyle == MediaControlStyle::SingleLine ) + mxPlayToolBox->set_sensitive(false); + UpdateToolBoxes( maItem ); + UpdateTimeSlider( maItem ); + UpdateVolumeSlider( maItem ); + UpdateTimeField( maItem, maItem.getTime() ); + UpdateURLField(maItem); + } +} + +IMPL_LINK( MediaControl, implTimeHdl, weld::Scale&, rSlider, void ) +{ + mbLocked = true; + maIdle.Stop(); + UpdateTimeField(maItem, rSlider.get_value() * maItem.getDuration() / AVMEDIA_TIME_RANGE); + maChangeTimeIdle.Start(); +} + +IMPL_LINK_NOARG(MediaControl, implTimeEndHdl, Timer*, void) +{ + MediaItem aExecItem; + + aExecItem.setTime( mxTimeSlider->get_value() * maItem.getDuration() / AVMEDIA_TIME_RANGE ); + // keep state (if the media was playing, keep it playing) + aExecItem.setState(maItem.getState()); + execute( aExecItem ); + update(); + maIdle.Start(); + mbLocked = false; +} + +IMPL_LINK( MediaControl, implVolumeHdl, weld::Scale&, rSlider, void ) +{ + MediaItem aExecItem; + + aExecItem.setVolumeDB(rSlider.get_value()); + execute( aExecItem ); + update(); +} + +IMPL_LINK( MediaControl, implSelectHdl, const OString&, rIdent, void ) +{ + MediaItem aExecItem; + if (rIdent == "open") + { + OUString aURL; + if (MediaWindow::executeMediaURLDialog(GetFrameWeld(), aURL, nullptr)) + { + if( !MediaWindow::isMediaURL( aURL, ""/*TODO?*/, true ) ) + MediaWindow::executeFormatErrorBox(GetFrameWeld()); + else + { + aExecItem.setURL( aURL, "", ""/*TODO?*/ ); + aExecItem.setState( MediaState::Play ); + } + } + } + else + SelectPlayToolBoxItem( aExecItem, maItem, rIdent ); + + if (aExecItem.getState() == MediaState::Play) + maIdle.Start(); + else if (aExecItem.getState() == MediaState::Pause || + aExecItem.getState() == MediaState::Stop) + maIdle.Stop(); + + if( aExecItem.getMaskSet() != AVMediaSetMask::NONE ) + execute( aExecItem ); + + update(); +} + +IMPL_LINK( MediaControl, implZoomSelectHdl, weld::ComboBox&, rBox, void ) +{ + bool bCurrentlySettingZoom = mbCurrentlySettingZoom; + mbCurrentlySettingZoom = true; + + MediaItem aExecItem; + css::media::ZoomLevel eLevel; + + switch (rBox.get_active()) + { + case AVMEDIA_ZOOMLEVEL_50: eLevel = css::media::ZoomLevel_ZOOM_1_TO_2; break; + case AVMEDIA_ZOOMLEVEL_100: eLevel = css::media::ZoomLevel_ORIGINAL; break; + case AVMEDIA_ZOOMLEVEL_200: eLevel = css::media::ZoomLevel_ZOOM_2_TO_1; break; + case AVMEDIA_ZOOMLEVEL_FIT: eLevel = css::media::ZoomLevel_FIT_TO_WINDOW_FIXED_ASPECT; break; + case AVMEDIA_ZOOMLEVEL_SCALED: eLevel = css::media::ZoomLevel_FIT_TO_WINDOW; break; + + default: eLevel = css::media::ZoomLevel_NOT_AVAILABLE; break; + } + + aExecItem.setZoom( eLevel ); + execute( aExecItem ); + update(); + + mbCurrentlySettingZoom = bCurrentlySettingZoom; +} + +IMPL_LINK_NOARG(MediaControl, implTimeoutHdl, Timer *, void) +{ + update(); + maIdle.Start(); +} + +} // namespace avmedia + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/framework/mediaitem.cxx b/avmedia/source/framework/mediaitem.cxx new file mode 100644 index 000000000..ac8c09b79 --- /dev/null +++ b/avmedia/source/framework/mediaitem.cxx @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <avmedia/mediaitem.hxx> + +#include <com/sun/star/uno/Sequence.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XUriReference.hpp> +#include <com/sun/star/uri/XUriReferenceFactory.hpp> +#include <com/sun/star/text/GraphicCrop.hpp> + +#include <sal/log.hxx> + +#include <ucbhelper/content.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/storagehelper.hxx> +#include <mediamisc.hxx> +#include <osl/file.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/graph.hxx> + +using namespace ::com::sun::star; + +namespace avmedia +{ + +SfxPoolItem* MediaItem::CreateDefault() { return new MediaItem; } + +struct MediaItem::Impl +{ + OUString m_URL; + OUString m_TempFileURL; + OUString m_Referer; + OUString m_sMimeType; + AVMediaSetMask m_nMaskSet; + MediaState m_eState; + double m_fTime; + double m_fDuration; + sal_Int16 m_nVolumeDB; + bool m_bLoop; + bool m_bMute; + css::media::ZoomLevel m_eZoom; + Graphic m_aGraphic; + text::GraphicCrop m_aCrop; + + explicit Impl(AVMediaSetMask nMaskSet) + : m_nMaskSet( nMaskSet ) + , m_eState( MediaState::Stop ) + , m_fTime( 0.0 ) + , m_fDuration( 0.0 ) + , m_nVolumeDB( 0 ) + , m_bLoop( false ) + , m_bMute( false ) + , m_eZoom( css::media::ZoomLevel_NOT_AVAILABLE ) + { + } +}; + + +MediaItem::MediaItem( sal_uInt16 i_nWhich, AVMediaSetMask nMaskSet ) + : SfxPoolItem( i_nWhich ) + , m_pImpl( new Impl(nMaskSet) ) +{ +} + + +MediaItem::MediaItem( const MediaItem& rItem ) + : SfxPoolItem( rItem ) + , m_pImpl( new Impl(*rItem.m_pImpl) ) +{ +} + + +MediaItem::~MediaItem() +{ +} + + +bool MediaItem::operator==( const SfxPoolItem& rItem ) const +{ + assert( SfxPoolItem::operator==(rItem)); + MediaItem const& rOther(static_cast< const MediaItem& >(rItem)); + return m_pImpl->m_nMaskSet == rOther.m_pImpl->m_nMaskSet + && m_pImpl->m_URL == rOther.m_pImpl->m_URL + && m_pImpl->m_Referer == rOther.m_pImpl->m_Referer + && m_pImpl->m_sMimeType == rOther.m_pImpl->m_sMimeType + && m_pImpl->m_aGraphic == rOther.m_pImpl->m_aGraphic + && m_pImpl->m_aCrop == rOther.m_pImpl->m_aCrop + && m_pImpl->m_eState == rOther.m_pImpl->m_eState + && m_pImpl->m_fDuration == rOther.m_pImpl->m_fDuration + && m_pImpl->m_fTime == rOther.m_pImpl->m_fTime + && m_pImpl->m_nVolumeDB == rOther.m_pImpl->m_nVolumeDB + && m_pImpl->m_bLoop == rOther.m_pImpl->m_bLoop + && m_pImpl->m_bMute == rOther.m_pImpl->m_bMute + && m_pImpl->m_eZoom == rOther.m_pImpl->m_eZoom; +} + +MediaItem* MediaItem::Clone( SfxItemPool* ) const +{ + return new MediaItem( *this ); +} + +bool MediaItem::GetPresentation( SfxItemPresentation, + MapUnit, + MapUnit, + OUString& rText, + const IntlWrapper& ) const +{ + rText.clear(); + return false; +} + +bool MediaItem::QueryValue( css::uno::Any& rVal, sal_uInt8 ) const +{ + uno::Sequence< uno::Any > aSeq{ uno::Any(m_pImpl->m_URL), + uno::Any(static_cast<sal_uInt32>(m_pImpl->m_nMaskSet)), + uno::Any(static_cast< sal_Int32 >( m_pImpl->m_eState )), + uno::Any(m_pImpl->m_fTime), + uno::Any(m_pImpl->m_fDuration), + uno::Any(m_pImpl->m_nVolumeDB), + uno::Any(m_pImpl->m_bLoop), + uno::Any(m_pImpl->m_bMute), + uno::Any(m_pImpl->m_eZoom), + uno::Any(m_pImpl->m_sMimeType) }; + + rVal <<= aSeq; + + return true; +} + + +bool MediaItem::PutValue( const css::uno::Any& rVal, sal_uInt8 ) +{ + uno::Sequence< uno::Any > aSeq; + bool bRet = false; + + if( ( rVal >>= aSeq ) && ( aSeq.getLength() == 10 ) ) + { + sal_Int32 nInt32 = 0; + + aSeq[ 0 ] >>= m_pImpl->m_URL; + aSeq[ 1 ] >>= nInt32; + m_pImpl->m_nMaskSet = static_cast<AVMediaSetMask>(nInt32); + aSeq[ 2 ] >>= nInt32; + m_pImpl->m_eState = static_cast< MediaState >( nInt32 ); + aSeq[ 3 ] >>= m_pImpl->m_fTime; + aSeq[ 4 ] >>= m_pImpl->m_fDuration; + aSeq[ 5 ] >>= m_pImpl->m_nVolumeDB; + aSeq[ 6 ] >>= m_pImpl->m_bLoop; + aSeq[ 7 ] >>= m_pImpl->m_bMute; + aSeq[ 8 ] >>= m_pImpl->m_eZoom; + aSeq[ 9 ] >>= m_pImpl->m_sMimeType; + + bRet = true; + } + + return bRet; +} + +bool MediaItem::merge(const MediaItem& rMediaItem) +{ + bool bChanged = false; + + const AVMediaSetMask nMaskSet = rMediaItem.getMaskSet(); + + if( AVMediaSetMask::URL & nMaskSet ) + bChanged |= setURL(rMediaItem.getURL(), rMediaItem.getTempURL(), rMediaItem.getReferer()); + + if( AVMediaSetMask::MIME_TYPE & nMaskSet ) + bChanged |= setMimeType(rMediaItem.getMimeType()); + + if (nMaskSet & AVMediaSetMask::GRAPHIC) + bChanged |= setGraphic(rMediaItem.getGraphic()); + + if (nMaskSet & AVMediaSetMask::CROP) + bChanged |= setCrop(rMediaItem.getCrop()); + + if( AVMediaSetMask::STATE & nMaskSet ) + bChanged |= setState( rMediaItem.getState() ); + + if( AVMediaSetMask::DURATION & nMaskSet ) + bChanged |= setDuration(rMediaItem.getDuration()); + + if( AVMediaSetMask::TIME & nMaskSet ) + bChanged |= setTime(rMediaItem.getTime()); + + if( AVMediaSetMask::LOOP & nMaskSet ) + bChanged |= setLoop(rMediaItem.isLoop()); + + if( AVMediaSetMask::MUTE & nMaskSet ) + bChanged |= setMute(rMediaItem.isMute()); + + if( AVMediaSetMask::VOLUMEDB & nMaskSet ) + bChanged |= setVolumeDB(rMediaItem.getVolumeDB()); + + if( AVMediaSetMask::ZOOM & nMaskSet ) + bChanged |= setZoom(rMediaItem.getZoom()); + + return bChanged; +} + +AVMediaSetMask MediaItem::getMaskSet() const +{ + return m_pImpl->m_nMaskSet; +} + +bool MediaItem::setURL(const OUString& rURL, const OUString& rTempURL, const OUString& rReferer) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::URL; + bool bChanged = rURL != m_pImpl->m_URL || rTempURL != m_pImpl->m_TempFileURL || rReferer != m_pImpl->m_Referer; + if (bChanged) + { + m_pImpl->m_URL = rURL; + m_pImpl->m_TempFileURL = rTempURL; + m_pImpl->m_Referer = rReferer; + } + return bChanged; +} + +const OUString& MediaItem::getURL() const +{ + return m_pImpl->m_URL; +} + +const OUString& MediaItem::getTempURL() const +{ + return m_pImpl->m_TempFileURL; +} + +const OUString& MediaItem::getReferer() const +{ + return m_pImpl->m_Referer; +} + +bool MediaItem::setMimeType(const OUString& rMimeType) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::MIME_TYPE; + bool bChanged = rMimeType != m_pImpl->m_sMimeType; + if (bChanged) + m_pImpl->m_sMimeType = rMimeType; + return bChanged; +} + +OUString MediaItem::getMimeType() const +{ + return !m_pImpl->m_sMimeType.isEmpty() ? m_pImpl->m_sMimeType : AVMEDIA_MIMETYPE_COMMON; +} + +bool MediaItem::setGraphic(const Graphic& rGraphic) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::GRAPHIC; + bool bChanged = rGraphic != m_pImpl->m_aGraphic; + if (bChanged) + m_pImpl->m_aGraphic = rGraphic; + return bChanged; +} + +const Graphic & MediaItem::getGraphic() const { return m_pImpl->m_aGraphic; } + +bool MediaItem::setCrop(const text::GraphicCrop& rCrop) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::CROP; + bool bChanged = rCrop != m_pImpl->m_aCrop; + if (bChanged) + m_pImpl->m_aCrop = rCrop; + return bChanged; +} + +const text::GraphicCrop& MediaItem::getCrop() const { return m_pImpl->m_aCrop; } + +bool MediaItem::setState(MediaState eState) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::STATE; + bool bChanged = eState != m_pImpl->m_eState; + if (bChanged) + m_pImpl->m_eState = eState; + return bChanged; +} + +MediaState MediaItem::getState() const +{ + return m_pImpl->m_eState; +} + +bool MediaItem::setDuration(double fDuration) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::DURATION; + bool bChanged = fDuration != m_pImpl->m_fDuration; + if (bChanged) + m_pImpl->m_fDuration = fDuration; + return bChanged; +} + +double MediaItem::getDuration() const +{ + return m_pImpl->m_fDuration; +} + +bool MediaItem::setTime(double fTime) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::TIME; + bool bChanged = fTime != m_pImpl->m_fTime; + if (bChanged) + m_pImpl->m_fTime = fTime; + return bChanged; +} + +double MediaItem::getTime() const +{ + return m_pImpl->m_fTime; +} + +bool MediaItem::setLoop(bool bLoop) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::LOOP; + bool bChanged = bLoop != m_pImpl->m_bLoop; + if (bChanged) + m_pImpl->m_bLoop = bLoop; + return bChanged; +} + +bool MediaItem::isLoop() const +{ + return m_pImpl->m_bLoop; +} + +bool MediaItem::setMute(bool bMute) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::MUTE; + bool bChanged = bMute != m_pImpl->m_bMute; + if (bChanged) + m_pImpl->m_bMute = bMute; + return bChanged; +} + +bool MediaItem::isMute() const +{ + return m_pImpl->m_bMute; +} + +bool MediaItem::setVolumeDB(sal_Int16 nDB) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::VOLUMEDB; + bool bChanged = nDB != m_pImpl->m_nVolumeDB; + if (bChanged) + m_pImpl->m_nVolumeDB = nDB; + return bChanged; +} + +sal_Int16 MediaItem::getVolumeDB() const +{ + return m_pImpl->m_nVolumeDB; +} + +bool MediaItem::setZoom(css::media::ZoomLevel eZoom) +{ + m_pImpl->m_nMaskSet |= AVMediaSetMask::ZOOM; + bool bChanged = eZoom != m_pImpl->m_eZoom; + if (bChanged) + m_pImpl->m_eZoom = eZoom; + return bChanged; +} + +css::media::ZoomLevel MediaItem::getZoom() const +{ + return m_pImpl->m_eZoom; +} + +OUString GetFilename(OUString const& rSourceURL) +{ + uno::Reference<uri::XUriReferenceFactory> const xUriFactory( + uri::UriReferenceFactory::create( + comphelper::getProcessComponentContext())); + uno::Reference<uri::XUriReference> const xSourceURI( + xUriFactory->parse(rSourceURL), uno::UNO_SET_THROW); + + OUString filename; + { + sal_Int32 const nSegments(xSourceURI->getPathSegmentCount()); + if (0 < nSegments) + { + filename = xSourceURI->getPathSegment(nSegments - 1); + } + } + if (!::comphelper::OStorageHelper::IsValidZipEntryFileName( + filename, false) || !filename.getLength()) + { + filename = "media"; + } + return filename; +} + + +uno::Reference<io::XStream> +CreateStream(uno::Reference<embed::XStorage> const& xStorage, + OUString const& rFilename) +{ + OUString filename(rFilename); + + if (xStorage->hasByName(filename)) + { + OUString basename; + OUString suffix; + sal_Int32 const nIndex(rFilename.lastIndexOf('.')); + if (0 < nIndex) + { + basename = rFilename.copy(0, nIndex); + suffix = rFilename.copy(nIndex); + } + sal_Int32 count(0); // sigh... try to generate non-existent name + do + { + ++count; + filename = basename + OUString::number(count) + suffix; + } + while (xStorage->hasByName(filename)); + } + + uno::Reference<io::XStream> const xStream( + xStorage->openStreamElement(filename, + embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE), + uno::UNO_SET_THROW); + uno::Reference< beans::XPropertySet > const xStreamProps(xStream, + uno::UNO_QUERY); + if (xStreamProps.is()) { // this is NOT supported in FileSystemStorage + xStreamProps->setPropertyValue("MediaType", uno::Any(OUString( + //FIXME how to detect real media type? + //but currently xmloff has this one hardcoded anyway... + "application/vnd.sun.star.media"))); + xStreamProps->setPropertyValue( // turn off compression + "Compressed", uno::Any(false)); + } + return xStream; +} + + +bool EmbedMedia(uno::Reference<frame::XModel> const& xModel, + OUString const& rSourceURL, OUString & o_rEmbeddedURL, uno::Reference<io::XInputStream> const& xInputStream) +{ + try + { + uno::Reference<document::XStorageBasedDocument> const xSBD(xModel, + uno::UNO_QUERY_THROW); + uno::Reference<embed::XStorage> const xStorage( + xSBD->getDocumentStorage(), uno::UNO_SET_THROW); + + OUString const media("Media"); + uno::Reference<embed::XStorage> const xSubStorage( + xStorage->openStorageElement(media, embed::ElementModes::WRITE)); + + OUString filename(GetFilename(rSourceURL)); + + uno::Reference<io::XStream> const xStream( + CreateStream(xSubStorage, filename), uno::UNO_SET_THROW); + uno::Reference<io::XOutputStream> const xOutStream( + xStream->getOutputStream(), uno::UNO_SET_THROW); + + if (xInputStream.is()) + { + // Throw Exception if failed. + ::comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream); + } + else + { + ::ucbhelper::Content sourceContent(rSourceURL, + uno::Reference<ucb::XCommandEnvironment>(), + comphelper::getProcessComponentContext()); + + if (!sourceContent.openStream(xOutStream)) // copy file to storage + { + SAL_INFO("avmedia", "openStream to storage failed"); + return false; + } + } + + uno::Reference<embed::XTransactedObject> const xSubTransaction( + xSubStorage, uno::UNO_QUERY); + if (xSubTransaction.is()) { + xSubTransaction->commit(); + } + uno::Reference<embed::XTransactedObject> const xTransaction( + xStorage, uno::UNO_QUERY); + if (xTransaction.is()) { + xTransaction->commit(); + } + + o_rEmbeddedURL = "vnd.sun.star.Package:" + media + "/" + filename; + return true; + } + catch (uno::Exception const&) + { + SAL_WARN("avmedia", + "Exception while trying to embed media"); + } + return false; +} + +bool CreateMediaTempFile(uno::Reference<io::XInputStream> const& xInStream, + OUString& o_rTempFileURL, std::u16string_view rDesiredExtension) +{ + OUString tempFileURL; + ::osl::FileBase::RC const err = + ::osl::FileBase::createTempFile(nullptr, nullptr, & tempFileURL); + if (::osl::FileBase::E_None != err) + { + SAL_WARN("avmedia", "cannot create temp file"); + return false; + } + + if (!rDesiredExtension.empty()) + { + OUString newTempFileURL = tempFileURL + rDesiredExtension; + if (osl::File::move(tempFileURL, newTempFileURL) != osl::FileBase::E_None) + { + SAL_WARN("avmedia", "Could not rename file '" << tempFileURL << "' to '" << newTempFileURL << "'"); + return false; + } + tempFileURL = newTempFileURL; + } + + try + { + ::ucbhelper::Content tempContent(tempFileURL, + uno::Reference<ucb::XCommandEnvironment>(), + comphelper::getProcessComponentContext()); + tempContent.writeStream(xInStream, true); // copy stream to file + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("avmedia", ""); + return false; + } + o_rTempFileURL = tempFileURL; + return true; +} + +MediaTempFile::~MediaTempFile() +{ + ::osl::File::remove(m_TempFileURL); +} + +} // namespace avmedia + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/framework/mediaplayer.cxx b/avmedia/source/framework/mediaplayer.cxx new file mode 100644 index 000000000..258f23ff4 --- /dev/null +++ b/avmedia/source/framework/mediaplayer.cxx @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <avmedia/mediaplayer.hxx> +#include <avmedia/mediawindow.hxx> +#include <avmedia/mediaitem.hxx> +#include <mediamisc.hxx> +#include <strings.hrc> +#include <helpids.h> + +#include <svl/stritem.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> + +namespace avmedia +{ + +MediaPlayer::MediaPlayer( vcl::Window* _pParent, sal_uInt16 nId, SfxBindings* _pBindings, SfxChildWinInfo* pInfo ) : + SfxChildWindow( _pParent, nId ) +{ + SetWindow( VclPtr<MediaFloater>::Create( _pBindings, this, _pParent ) ); + static_cast< MediaFloater* >( GetWindow() )->Initialize( pInfo ); +}; + + +MediaPlayer::~MediaPlayer() +{ +} + + +SFX_IMPL_DOCKINGWINDOW_WITHID( MediaPlayer, SID_AVMEDIA_PLAYER ) + + +MediaFloater::MediaFloater( SfxBindings* _pBindings, SfxChildWindow* pCW, vcl::Window* pParent ) : + SfxDockingWindow( _pBindings, pCW, pParent, WB_CLOSEABLE | WB_MOVEABLE | WB_SIZEABLE | WB_DOCKABLE ), + mpMediaWindow( new MediaWindow( this, true ) ) +{ + const Size aSize( mpMediaWindow->getPreferredSize() ); + + SetPosSizePixel( Point( 0, 0 ), aSize ); + SetMinOutputSizePixel( aSize ); + SetText( AvmResId( AVMEDIA_STR_MEDIAPLAYER ) ); + mpMediaWindow->show(); +} + + +MediaFloater::~MediaFloater() +{ + disposeOnce(); +} + +void MediaFloater::dispose() +{ + if (IsFloatingMode()) + { + Show(false, ShowFlags::NoFocusChange); + SetFloatingMode(false); + } + mpMediaWindow.reset(); + SfxDockingWindow::dispose(); +} + +void MediaFloater::Resize() +{ + SfxDockingWindow::Resize(); + + if( mpMediaWindow ) + mpMediaWindow->setPosSize( tools::Rectangle( Point(), GetOutputSizePixel() ) ); +} + +void MediaFloater::ToggleFloatingMode() +{ + ::avmedia::MediaItem aRestoreItem; + + if (mpMediaWindow) + mpMediaWindow->updateMediaItem( aRestoreItem ); + mpMediaWindow.reset(); + + SfxDockingWindow::ToggleFloatingMode(); + + if (isDisposed()) + return; + + mpMediaWindow.reset( new MediaWindow( this, true ) ); + + mpMediaWindow->setPosSize( tools::Rectangle( Point(), GetOutputSizePixel() ) ); + mpMediaWindow->executeMediaItem( aRestoreItem ); + + vcl::Window* pWindow = mpMediaWindow->getWindow(); + + if( pWindow ) + pWindow->SetHelpId( HID_AVMEDIA_PLAYERWINDOW ); + + mpMediaWindow->show(); +} + + +void MediaFloater::setURL( const OUString& rURL, const OUString& rReferer, bool bPlayImmediately ) +{ + if( mpMediaWindow ) + { + mpMediaWindow->setURL( rURL, rReferer ); + + if( mpMediaWindow->isValid() && bPlayImmediately ) + mpMediaWindow->start(); + } +} + + +void MediaFloater::dispatchCurrentURL() +{ + SfxDispatcher* pDispatcher = GetBindings().GetDispatcher(); + + if( pDispatcher ) + { + OUString url; + if (mpMediaWindow != nullptr) { + url = mpMediaWindow->getURL(); + } + const SfxStringItem aMediaURLItem( SID_INSERT_AVMEDIA, url ); + pDispatcher->ExecuteList(SID_INSERT_AVMEDIA, SfxCallMode::RECORD, + { &aMediaURLItem }); + } +} + +} // namespace avmedia + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/framework/mediatoolbox.cxx b/avmedia/source/framework/mediatoolbox.cxx new file mode 100644 index 000000000..55a19ac66 --- /dev/null +++ b/avmedia/source/framework/mediatoolbox.cxx @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <avmedia/mediatoolbox.hxx> +#include <avmedia/mediaitem.hxx> +#include <mediacontrol.hxx> + +#include <tools/debug.hxx> +#include <sfx2/sfxsids.hrc> +#include <vcl/toolbox.hxx> + +#include <comphelper/propertysequence.hxx> + +using namespace ::com::sun::star; + +namespace avmedia +{ + +class MediaToolBoxControl_Impl : public MediaControl +{ +public: + + MediaToolBoxControl_Impl( vcl::Window& rParent, MediaToolBoxControl& rControl ); + + void update() override; + void execute( const MediaItem& rItem ) override; + +private: + + MediaToolBoxControl* mpToolBoxControl; +}; + +MediaToolBoxControl_Impl::MediaToolBoxControl_Impl( vcl::Window& rParent, MediaToolBoxControl& rControl ) : + MediaControl( &rParent, MediaControlStyle::SingleLine ), + mpToolBoxControl( &rControl ) +{ + SetSizePixel(m_xContainer->get_preferred_size()); +} + +void MediaToolBoxControl_Impl::update() +{ + mpToolBoxControl->implUpdateMediaControl(); +} + + +void MediaToolBoxControl_Impl::execute( const MediaItem& rItem ) +{ + mpToolBoxControl->implExecuteMediaControl( rItem ); +} + + +SFX_IMPL_TOOLBOX_CONTROL( MediaToolBoxControl, ::avmedia::MediaItem ); + + +MediaToolBoxControl::MediaToolBoxControl( sal_uInt16 nSlotId, ToolBoxItemId nId, ToolBox& rTbx ) : + SfxToolBoxControl( nSlotId, nId, rTbx ) +{ + rTbx.Invalidate(); +} + + +MediaToolBoxControl::~MediaToolBoxControl() +{ +} + + +void MediaToolBoxControl::StateChangedAtToolBoxControl( sal_uInt16, SfxItemState eState, const SfxPoolItem* pState ) +{ + MediaToolBoxControl_Impl* pCtrl = static_cast< MediaToolBoxControl_Impl* >( GetToolBox().GetItemWindow( GetId() ) ); + + DBG_ASSERT( pCtrl, "MediaToolBoxControl::StateChanged: media control not found" ); + + if( eState == SfxItemState::DISABLED ) + { + pCtrl->Enable( false, false ); + pCtrl->SetText( OUString() ); + + const MediaItem aEmptyMediaItem( 0, AVMediaSetMask::ALL ); + pCtrl->setState( aEmptyMediaItem ); + } + else + { + pCtrl->Enable( true, false ); + + const MediaItem* pMediaItem = dynamic_cast<const MediaItem*>( pState ); + + if( pMediaItem && ( eState == SfxItemState::DEFAULT ) ) + pCtrl->setState( *pMediaItem ); + } +} + +VclPtr<InterimItemWindow> MediaToolBoxControl::CreateItemWindow( vcl::Window *pParent ) +{ + return ( pParent ? VclPtr<MediaToolBoxControl_Impl>::Create( *pParent, *this ) : nullptr ); +} + +void MediaToolBoxControl::implUpdateMediaControl() +{ + updateStatus( ".uno:AVMediaToolBox" ); +} + +void MediaToolBoxControl::implExecuteMediaControl( const MediaItem& rItem ) +{ + MediaItem aExecItem( SID_AVMEDIA_TOOLBOX ); + uno::Any aAny; + + aExecItem.merge( rItem ); + aExecItem.QueryValue( aAny ); + auto aArgs(::comphelper::InitPropertySequence({ + { "AVMediaToolBox", aAny } + })); + + Dispatch( ".uno:AVMediaToolBox" , aArgs ); +} + +} // namespace avmedia + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/framework/soundhandler.cxx b/avmedia/source/framework/soundhandler.cxx new file mode 100644 index 000000000..2392c3dd1 --- /dev/null +++ b/avmedia/source/framework/soundhandler.cxx @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "soundhandler.hxx" + +#include <unotools/mediadescriptor.hxx> + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> + +#include <avmedia/mediawindow.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/supportsservice.hxx> + +namespace avmedia{ + + +// XInterface, XTypeProvider, XServiceInfo + + +void SAL_CALL SoundHandler::acquire() noexcept +{ + /* Don't use mutex in methods of XInterface! */ + OWeakObject::acquire(); +} + +void SAL_CALL SoundHandler::release() noexcept +{ + /* Don't use mutex in methods of XInterface! */ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL SoundHandler::queryInterface( const css::uno::Type& aType ) +{ + /* Attention: Don't use mutex or guard in this method!!! Is a method of XInterface. */ + /* Ask for my own supported interfaces ...*/ + css::uno::Any aReturn( ::cppu::queryInterface( aType, + static_cast< css::lang::XTypeProvider* >(this), + static_cast< css::lang::XServiceInfo* >(this), + static_cast< css::frame::XNotifyingDispatch* >(this), + static_cast< css::frame::XDispatch* >(this), + static_cast< css::document::XExtendedFilterDetection* >(this))); + /* If searched interface not supported by this class ... */ + if ( !aReturn.hasValue() ) + { + /* ... ask baseclass for interfaces! */ + aReturn = OWeakObject::queryInterface( aType ); + } + /* Return result of this search. */ + return aReturn; +} + +css::uno::Sequence< sal_Int8 > SAL_CALL SoundHandler::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +css::uno::Sequence< css::uno::Type > SAL_CALL SoundHandler::getTypes() +{ + static ::cppu::OTypeCollection aTypeCollection( + cppu::UnoType<css::lang::XTypeProvider>::get(), + cppu::UnoType<css::lang::XServiceInfo>::get(), + cppu::UnoType<css::frame::XNotifyingDispatch>::get(), + cppu::UnoType<css::frame::XDispatch>::get(), + cppu::UnoType<css::document::XExtendedFilterDetection>::get()); + + return aTypeCollection.getTypes(); +} + +constexpr OUStringLiteral IMPLEMENTATIONNAME_SOUNDHANDLER = u"com.sun.star.comp.framework.SoundHandler"; + +/*===========================================================================================================*/ +/* XServiceInfo */ +/*===========================================================================================================*/ +OUString SAL_CALL SoundHandler::getImplementationName() +{ + return IMPLEMENTATIONNAME_SOUNDHANDLER; +} + +// XServiceInfo +sal_Bool SAL_CALL SoundHandler::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +// XServiceInfo +css::uno::Sequence< OUString > SAL_CALL SoundHandler::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ContentHandler" }; +} + +/*-************************************************************************************************************ + @short standard ctor + @descr These initialize a new instance of this class with needed information for work. + + @seealso using at owner + + @param "xFactory", reference to service manager for creation of new services + @onerror Show an assertion and do nothing else. + @threadsafe yes +*//*-*************************************************************************************************************/ +SoundHandler::SoundHandler() + // Init member + : m_bError ( false ) + , m_aUpdateIdle ( "avmedia SoundHandler Update" ) +{ + m_aUpdateIdle.SetInvokeHandler(LINK(this, SoundHandler, implts_PlayerNotify)); +} + +/*-************************************************************************************************************ + @short standard dtor +*//*-*************************************************************************************************************/ +SoundHandler::~SoundHandler() +{ + if (m_xListener.is()) + { + css::frame::DispatchResultEvent aEvent; + aEvent.State = css::frame::DispatchResultState::FAILURE; + m_xListener->dispatchFinished(aEvent); + m_xListener.clear(); + } +} + +/*-************************************************************************************************************ + @interface css::frame::XDispatch + + @short try to load audio file + @descr This method try to load given audio file by URL and play it. We use vcl/Sound class to do that. + Playing of sound is asynchronous every time. + + @attention We must hold us alive by ourself ... because we use async. vcl sound player ... but playing is started + in async interface call "dispatch()" too. And caller forget us immediately. But then our uno ref count + will decreased to 0 and will die. The only solution is to use own reference to our implementation. + But we do it for really started jobs only and release it during call back of vcl. + + @seealso class vcl/Sound + @seealso method implts_PlayerNotify() + + @param "aURL" , URL to dispatch. + @param "lArguments", list of optional arguments. + @onerror We do nothing. + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL SoundHandler::dispatchWithNotification(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lDescriptor, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // SAFE { + const ::osl::MutexGuard aLock( m_aMutex ); + + utl::MediaDescriptor aDescriptor(lDescriptor); + + { + //close streams otherwise on windows we can't reopen the file in the + //media player when we pass the url to directx as it'll already be open + css::uno::Reference< css::io::XInputStream > xInputStream = + aDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_INPUTSTREAM, + css::uno::Reference< css::io::XInputStream >()); + if (xInputStream.is()) xInputStream->closeInput(); + } + + // If player currently used for other dispatch() requests ... + // cancel it by calling stop()! + m_aUpdateIdle.Stop(); + if (m_xPlayer.is()) + { + if (m_xPlayer->isPlaying()) + m_xPlayer->stop(); + m_xPlayer.clear(); + } + + // Try to initialize player. + m_xListener = xListener; + try + { + m_bError = false; + m_xPlayer.set( avmedia::MediaWindow::createPlayer( aURL.Complete, aDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_REFERRER, OUString()) ), css::uno::UNO_SET_THROW ); + // OK- we can start async playing ... + // Count this request and initialize self-holder against dying by uno ref count ... + m_xSelfHold.set(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY); + m_xPlayer->start(); + m_aUpdateIdle.SetPriority( TaskPriority::HIGH_IDLE ); + m_aUpdateIdle.Start(); + } + catch( css::uno::Exception& ) + { + m_bError = true; + m_xPlayer.clear(); + } + + // } SAFE +} + +void SAL_CALL SoundHandler::dispatch( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + dispatchWithNotification(aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >()); +} + +/*-************************************************************************************************************ + @interface css::document::XExtendedFilterDetection + + @short try to detect file (given as argument included in "lDescriptor") + @descr We try to detect, if given file could be handled by this class and is a well known one. + If it is - we return right internal type name - otherwise we return nothing! + So call can search for another detect service and ask him too. + + @attention a) We don't need any mutex here ... because we don't use any member! + b) Don't use internal player instance "m_pPlayer" to detect given sound file! + It's not necessary to do that ... and we can use temp. variable to do the same. + This way is easy - we don't must synchronize it with currently played sounds! + Another reason to do so ... We are a listener on our internal ma_Player object. + If you would call "IsSoundFile()" on this instance, he would call us back and + we make some unnecessary things ... + @param "lDescriptor", description of file to detect + @return Internal type name which match this file ... or nothing if it is unknown. + + @onerror We return nothing. + @threadsafe yes +*//*-*************************************************************************************************************/ +OUString SAL_CALL SoundHandler::detect( css::uno::Sequence< css::beans::PropertyValue >& lDescriptor ) +{ + // Our default is "nothing". So we can return it, if detection failed or file type is really unknown. + OUString sTypeName; + + // Analyze given descriptor to find filename or input stream or ... + utl::MediaDescriptor aDescriptor(lDescriptor); + OUString sURL = aDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_URL, OUString()); + OUString sReferer = aDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_REFERRER, OUString()); + + if ( + !sURL.isEmpty() && + (avmedia::MediaWindow::isMediaURL(sURL, sReferer)) + ) + { + // If the file type is supported depends on the OS, so... + // I think we can the following ones: + // a) look for given extension of url to map our type decision HARD CODED!!! + // b) return preferred type every time... it's easy :-) + sTypeName = "wav_Wave_Audio_File"; + aDescriptor[utl::MediaDescriptor::PROP_TYPENAME] <<= sTypeName; + aDescriptor >> lDescriptor; + } + + // Return our decision. + return sTypeName; +} + +/*-************************************************************************************************************ + @short call back of sound player + @descr Our player call us back to give us some information. + We use this information to callback our might existing listener. + + @seealso method dispatchWithNotification() + @return 0 every time... it doesn't matter for us. + @threadsafe yes +*//*-*************************************************************************************************************/ +IMPL_LINK_NOARG(SoundHandler, implts_PlayerNotify, Timer *, void) +{ + // SAFE { + ::osl::ClearableMutexGuard aLock( m_aMutex ); + + if (m_xPlayer.is() && m_xPlayer->isPlaying() && m_xPlayer->getMediaTime() < m_xPlayer->getDuration()) + { + m_aUpdateIdle.Start(); + return; + } + m_xPlayer.clear(); + + // We use m_xSelfHold to let us die ... but we must live till real finishing of this method too!!! + // So we SHOULD use another "self-holder" temp. to provide that ... + css::uno::Reference< css::uno::XInterface > xOperationHold = m_xSelfHold; + m_xSelfHold.clear(); + + // notify might existing listener + // And forget this listener! + // Because the corresponding dispatch was finished. + if (m_xListener.is()) + { + css::frame::DispatchResultEvent aEvent; + if (!m_bError) + aEvent.State = css::frame::DispatchResultState::SUCCESS; + else + aEvent.State = css::frame::DispatchResultState::FAILURE; + m_xListener->dispatchFinished(aEvent); + m_xListener.clear(); + } + + // } SAFE + //release aLock before end of method at which point xOperationHold goes out of scope and pThis dies + aLock.clear(); +} + +} // namespace framework + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_framework_SoundHandler_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new avmedia::SoundHandler); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/framework/soundhandler.hxx b/avmedia/source/framework/soundhandler.hxx new file mode 100644 index 000000000..648cbe59d --- /dev/null +++ b/avmedia/source/framework/soundhandler.hxx @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/frame/XNotifyingDispatch.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/document/XExtendedFilterDetection.hpp> +#include <com/sun/star/media/XPlayer.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/util/URL.hpp> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/weak.hxx> + +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include <tools/link.hxx> + +namespace avmedia{ + +/*-************************************************************************************************************ + @short handler to detect and play sounds ("wav" and "au" only!) + @descr Register this implementation as a content handler to detect and/or play wav- and au-sounds. + It doesn't depend from the target platform. But one instance of this class + can play one sound at the same time only. Means every new dispatch request will stop the + might still running one. So we support one operation/one URL/one listener at the same time + only. + + @devstatus ready + @threadsafe yes +*//*-*************************************************************************************************************/ +class SoundHandler : // interfaces + public css::lang::XTypeProvider + , public css::lang::XServiceInfo + , public css::frame::XNotifyingDispatch // => XDispatch + , public css::document::XExtendedFilterDetection + // baseclasses + // Order is necessary for right initialization! + , private cppu::BaseMutex + , public ::cppu::OWeakObject +{ + // public methods + public: + + // constructor / destructor + SoundHandler(); + virtual ~SoundHandler( ) override; + + // XInterface, XTypeProvider, XServiceInfo + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes () override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + + /* interface XServiceInfo */ + virtual OUString SAL_CALL getImplementationName ( ) override; + virtual sal_Bool SAL_CALL supportsService ( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames ( ) override; + + // XNotifyingDispatch + virtual void SAL_CALL dispatchWithNotification(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) override; + + // XDispatch + virtual void SAL_CALL dispatch ( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) override; + // not supported ! + virtual void SAL_CALL addStatusListener ( const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/ , + const css::util::URL& /*aURL*/ ) override {}; + virtual void SAL_CALL removeStatusListener ( const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/ , + const css::util::URL& /*aURL*/ ) override {}; + + // XExtendedFilterDetection + virtual OUString SAL_CALL detect ( css::uno::Sequence< css::beans::PropertyValue >& lDescriptor ) override; + + // protected methods + protected: + + // private methods + private: + DECL_LINK( implts_PlayerNotify, Timer*, void ); + + // variables + // (should be private everyway!) + private: + + bool m_bError; + css::uno::Reference< css::uno::XInterface > m_xSelfHold ; // we must protect us against dying during async(!) dispatch() call! + css::uno::Reference< css::media::XPlayer > m_xPlayer ; // uses avmedia player to play sounds... + + css::uno::Reference< css::frame::XDispatchResultListener > m_xListener ; + Idle m_aUpdateIdle; + +}; // class SoundHandler + +} // namespace avmedia + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/avmediagstreamer.component b/avmedia/source/gstreamer/avmediagstreamer.component new file mode 100644 index 000000000..cdc308d8b --- /dev/null +++ b/avmedia/source/gstreamer/avmediagstreamer.component @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.media.Manager_GStreamer" + constructor="com_sun_star_comp_media_Manager_GStreamer_get_implementation"> + <service name="com.sun.star.comp.avmedia.Manager_GStreamer"/> + </implementation> +</component> diff --git a/avmedia/source/gstreamer/gstcommon.hxx b/avmedia/source/gstreamer/gstcommon.hxx new file mode 100644 index 000000000..0e27907fb --- /dev/null +++ b/avmedia/source/gstreamer/gstcommon.hxx @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <gst/gst.h> + +#include <osl/mutex.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <cppuhelper/weak.hxx> +#include <cppuhelper/factory.hxx> + +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/media/XManager.hpp> + +#define WM_GRAPHNOTIFY (WM_USER + 567) + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstframegrabber.cxx b/avmedia/source/gstreamer/gstframegrabber.cxx new file mode 100644 index 000000000..ece799d87 --- /dev/null +++ b/avmedia/source/gstreamer/gstframegrabber.cxx @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "gstframegrabber.hxx" +#include "gstplayer.hxx" + +#include <cppuhelper/supportsservice.hxx> + +#include <gst/gstbuffer.h> +#include <gst/video/video.h> +#include <gst/video/gstvideosink.h> +#include <o3tl/safeint.hxx> +#include <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> + +#include <string> + +constexpr OUStringLiteral AVMEDIA_GST_FRAMEGRABBER_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.FrameGrabber_GStreamer"; +constexpr OUStringLiteral AVMEDIA_GST_FRAMEGRABBER_SERVICENAME = u"com.sun.star.media.FrameGrabber_GStreamer"; + +using namespace ::com::sun::star; + +namespace avmedia::gstreamer { + +void FrameGrabber::disposePipeline() +{ + if( mpPipeline != nullptr ) + { + gst_element_set_state( mpPipeline, GST_STATE_NULL ); + g_object_unref( G_OBJECT( mpPipeline ) ); + mpPipeline = nullptr; + } +} + +FrameGrabber::FrameGrabber( std::u16string_view rURL ) +{ + gchar *pPipelineStr; + pPipelineStr = g_strdup_printf( + "uridecodebin uri=%s ! videoconvert ! videoscale ! appsink " + "name=sink caps=\"video/x-raw,format=RGB,pixel-aspect-ratio=1/1\"", + OUStringToOString( rURL, RTL_TEXTENCODING_UTF8 ).getStr() ); + + GError *pError = nullptr; + mpPipeline = gst_parse_launch( pPipelineStr, &pError ); + if( pError != nullptr) { + g_warning( "Failed to construct frame-grabber pipeline '%s'\n", pError->message ); + g_error_free( pError ); + disposePipeline(); + } + + if( mpPipeline ) { + // pre-roll + switch( gst_element_set_state( mpPipeline, GST_STATE_PAUSED ) ) { + case GST_STATE_CHANGE_FAILURE: + case GST_STATE_CHANGE_NO_PREROLL: + g_warning( "failure pre-rolling media" ); + disposePipeline(); + break; + default: + break; + } + } + if( mpPipeline && + gst_element_get_state( mpPipeline, nullptr, nullptr, 5 * GST_SECOND ) == GST_STATE_CHANGE_FAILURE ) + disposePipeline(); +} + +FrameGrabber::~FrameGrabber() +{ + disposePipeline(); +} + +rtl::Reference<FrameGrabber> FrameGrabber::create( std::u16string_view rURL ) +{ + return new FrameGrabber( rURL ); +} + +uno::Reference< graphic::XGraphic > SAL_CALL FrameGrabber::grabFrame( double fMediaTime ) +{ + uno::Reference< graphic::XGraphic > xRet; + + if( !mpPipeline ) + return xRet; + + gint64 gst_position = llround( fMediaTime * GST_SECOND ); + gst_element_seek_simple( + mpPipeline, GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_FLUSH), + gst_position ); + + GstElement *pSink = gst_bin_get_by_name( GST_BIN( mpPipeline ), "sink" ); + if( !pSink ) + return xRet; + + GstBuffer *pBuf = nullptr; + GstCaps *pCaps = nullptr; + + // synchronously fetch the frame + GstSample *pSample = nullptr; + g_signal_emit_by_name( pSink, "pull-preroll", &pSample, nullptr ); + + if( pSample ) + { + pBuf = gst_sample_get_buffer( pSample ); + pCaps = gst_sample_get_caps( pSample ); + } + + // get geometry + int nWidth = 0, nHeight = 0; + if( !pCaps ) + g_warning( "could not get snapshot format\n" ); + else + { + GstStructure *pStruct = gst_caps_get_structure( pCaps, 0 ); + + /* we need to get the final caps on the buffer to get the size */ + if( !gst_structure_get_int( pStruct, "width", &nWidth ) || + !gst_structure_get_int( pStruct, "height", &nHeight ) ) + nWidth = nHeight = 0; + } + + if( pBuf && nWidth > 0 && nHeight > 0 && + // sanity check the size + gst_buffer_get_size( pBuf ) >= o3tl::make_unsigned( nWidth * nHeight * 3 ) + ) + { + sal_uInt8 *pData = nullptr; + GstMapInfo aMapInfo; + gst_buffer_map( pBuf, &aMapInfo, GST_MAP_READ ); + pData = aMapInfo.data; + + int nStride = GST_ROUND_UP_4( nWidth * 3 ); + BitmapEx aBmp = vcl::bitmap::CreateFromData(pData, nWidth, nHeight, nStride, vcl::PixelFormat::N24_BPP); + + gst_buffer_unmap( pBuf, &aMapInfo ); + xRet = Graphic( aBmp ).GetXGraphic(); + } + + return xRet; +} + +OUString SAL_CALL FrameGrabber::getImplementationName( ) +{ + return AVMEDIA_GST_FRAMEGRABBER_IMPLEMENTATIONNAME; +} + +sal_Bool SAL_CALL FrameGrabber::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL FrameGrabber::getSupportedServiceNames() +{ + return { AVMEDIA_GST_FRAMEGRABBER_SERVICENAME }; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstframegrabber.hxx b/avmedia/source/gstreamer/gstframegrabber.hxx new file mode 100644 index 000000000..c706192ef --- /dev/null +++ b/avmedia/source/gstreamer/gstframegrabber.hxx @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include "gstplayer.hxx" +#include <com/sun/star/media/XFrameGrabber.hpp> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +namespace avmedia::gstreamer { + +typedef ::cppu::WeakImplHelper< css::media::XFrameGrabber, + css::lang::XServiceInfo > FrameGrabber_BASE; + + +class FrameGrabber : public FrameGrabber_BASE +{ + GstElement *mpPipeline; + void disposePipeline(); +public: + // noncopyable + FrameGrabber(const FrameGrabber&) = delete; + const FrameGrabber& operator=(const FrameGrabber&) =delete; + + // static create method instead of public Ctor + static rtl::Reference<FrameGrabber> create( std::u16string_view rURL ); + + virtual ~FrameGrabber() override; + + // XFrameGrabber + virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL grabFrame( double fMediaTime ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + explicit FrameGrabber( std::u16string_view aURL ); +}; + +} // avmedia::gst + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstmanager.cxx b/avmedia/source/gstreamer/gstmanager.cxx new file mode 100644 index 000000000..1803b5880 --- /dev/null +++ b/avmedia/source/gstreamer/gstmanager.cxx @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cppuhelper/supportsservice.hxx> + +#include "gstmanager.hxx" +#include "gstplayer.hxx" + +#include <tools/urlobj.hxx> +#include <rtl/ref.hxx> + +using namespace ::com::sun::star; + +namespace avmedia::gstreamer { + +Manager::Manager() +{ +} + +Manager::~Manager() +{ +} + +uno::Reference< media::XPlayer > SAL_CALL Manager::createPlayer( const OUString& rURL ) +{ + rtl::Reference<Player> pPlayer( new Player ); + const INetURLObject aURL( rURL ); + + if( !pPlayer->create( aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ) ) ) + pPlayer.clear(); + + return pPlayer; +} + +OUString SAL_CALL Manager::getImplementationName( ) +{ + return "com.sun.star.comp.avmedia.Manager_GStreamer"; +} + +sal_Bool SAL_CALL Manager::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL Manager::getSupportedServiceNames( ) +{ + return { "com.sun.star.media.Manager" }; +} + +} // namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_media_Manager_GStreamer_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new avmedia::gstreamer::Manager()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstmanager.hxx b/avmedia/source/gstreamer/gstmanager.hxx new file mode 100644 index 000000000..21a5245dd --- /dev/null +++ b/avmedia/source/gstreamer/gstmanager.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "gstcommon.hxx" +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/media/XManager.hpp> + +namespace avmedia::gstreamer { + +class Manager : public ::cppu::WeakImplHelper< css::media::XManager, + css::lang::XServiceInfo > +{ +public: + + explicit Manager(); + virtual ~Manager() override; + + // XManager + virtual css::uno::Reference< css::media::XPlayer > SAL_CALL createPlayer( const OUString& aURL ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; + +} // namespace avmedia::gstreamer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstplayer.cxx b/avmedia/source/gstreamer/gstplayer.cxx new file mode 100644 index 000000000..6884654b6 --- /dev/null +++ b/avmedia/source/gstreamer/gstplayer.cxx @@ -0,0 +1,946 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <algorithm> +#include <cassert> +#include <chrono> +#include <cstddef> +#include <cstring> +#include <map> +#include <set> +#include <vector> +#include <math.h> + +#include <com/sun/star/text/GraphicCrop.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> +#include <rtl/string.hxx> +#include <salhelper/thread.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syschild.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/graph.hxx> +#include <avmedia/mediaitem.hxx> + +#include "gstplayer.hxx" +#include "gstframegrabber.hxx" +#include "gstwindow.hxx" + +#include <gst/video/videooverlay.h> +#include <gst/pbutils/missing-plugins.h> +#include <gst/pbutils/pbutils.h> + +constexpr OUStringLiteral AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.Player_GStreamer"; +constexpr OUStringLiteral AVMEDIA_GST_PLAYER_SERVICENAME = u"com.sun.star.media.Player_GStreamer"; +#define AVVERSION "gst 1.0: " + +using namespace ::com::sun::star; + +namespace avmedia::gstreamer { + +namespace { + +class FlagGuard { +public: + explicit FlagGuard(bool & flag): flag_(flag) { flag_ = true; } + + ~FlagGuard() { flag_ = false; } + +private: + bool & flag_; +}; + +class MissingPluginInstallerThread: public salhelper::Thread { +public: + MissingPluginInstallerThread(): Thread("MissingPluginInstaller") {} + +private: + void execute() override; +}; + + +class MissingPluginInstaller { + friend class MissingPluginInstallerThread; + +public: + MissingPluginInstaller(): launchNewThread_(true), inCleanUp_(false) {} + + ~MissingPluginInstaller(); + + void report(rtl::Reference<Player> const & source, GstMessage * message); + + // Player::~Player calls Player::disposing calls + // MissingPluginInstaller::detach, so do not take Player by rtl::Reference + // here (which would bump its refcount back from 0 to 1): + void detach(Player const * source); + +private: + void processQueue(); + + DECL_STATIC_LINK(MissingPluginInstaller, launchUi, void*, void); + + osl::Mutex mutex_; + std::set<OString> reported_; + std::map<OString, std::set<rtl::Reference<Player>>> queued_; + rtl::Reference<MissingPluginInstallerThread> currentThread_; + std::vector<OString> currentDetails_; + std::set<rtl::Reference<Player>> currentSources_; + bool launchNewThread_; + bool inCleanUp_; +}; + + +MissingPluginInstaller::~MissingPluginInstaller() { + osl::MutexGuard g(mutex_); + SAL_WARN_IF(currentThread_.is(), "avmedia.gstreamer", "unjoined thread"); + inCleanUp_ = true; +} + + +void MissingPluginInstaller::report( + rtl::Reference<Player> const & source, GstMessage * message) +{ + // assert(gst_is_missing_plugin_message(message)); + gchar * det = gst_missing_plugin_message_get_installer_detail(message); + if (det == nullptr) { + SAL_WARN( + "avmedia.gstreamer", + "gst_missing_plugin_message_get_installer_detail failed"); + return; + } + std::size_t len = std::strlen(det); + if (len > SAL_MAX_INT32) { + SAL_WARN("avmedia.gstreamer", "detail string too long"); + g_free(det); + return; + } + OString detStr(det, len); + g_free(det); + rtl::Reference<MissingPluginInstallerThread> join; + rtl::Reference<MissingPluginInstallerThread> launch; + { + osl::MutexGuard g(mutex_); + if (reported_.find(detStr) != reported_.end()) { + return; + } + auto & i = queued_[detStr]; + bool fresh = i.empty(); + i.insert(source); + if (!(fresh && launchNewThread_)) { + return; + } + join = currentThread_; + currentThread_ = new MissingPluginInstallerThread; + { + FlagGuard f(inCleanUp_); + currentSources_.clear(); + } + processQueue(); + launchNewThread_ = false; + launch = currentThread_; + } + if (join.is()) { + join->join(); + } + launch->acquire(); + Application::PostUserEvent( + LINK(this, MissingPluginInstaller, launchUi), launch.get()); +} + + +void eraseSource(std::set<rtl::Reference<Player>> & set, Player const * source) +{ + auto i = std::find_if( + set.begin(), set.end(), + [source](rtl::Reference<Player> const & el) { + return el.get() == source; + }); + if (i != set.end()) { + set.erase(i); + } +} + + +void MissingPluginInstaller::detach(Player const * source) { + rtl::Reference<MissingPluginInstallerThread> join; + { + osl::MutexGuard g(mutex_); + if (inCleanUp_) { + // Guard against ~MissingPluginInstaller with erroneously un-joined + // currentThread_ (thus non-empty currentSources_) calling + // destructor of currentSources_, calling ~Player, calling here, + // which would use currentSources_ while it is already being + // destroyed: + return; + } + for (auto i = queued_.begin(); i != queued_.end();) { + eraseSource(i->second, source); + if (i->second.empty()) { + i = queued_.erase(i); + } else { + ++i; + } + } + if (currentThread_.is()) { + assert(!currentSources_.empty()); + eraseSource(currentSources_, source); + if (currentSources_.empty()) { + join = currentThread_; + currentThread_.clear(); + launchNewThread_ = true; + } + } + } + if (join.is()) { + // missing cancellability of gst_install_plugins_sync + join->join(); + } +} + + +void MissingPluginInstaller::processQueue() { + assert(!queued_.empty()); + assert(currentDetails_.empty()); + for (const auto& rEntry : queued_) { + reported_.insert(rEntry.first); + currentDetails_.push_back(rEntry.first); + currentSources_.insert(rEntry.second.begin(), rEntry.second.end()); + } + queued_.clear(); +} + + +IMPL_STATIC_LINK(MissingPluginInstaller, launchUi, void *, p, void) +{ + MissingPluginInstallerThread* thread = static_cast<MissingPluginInstallerThread*>(p); + rtl::Reference<MissingPluginInstallerThread> ref(thread, SAL_NO_ACQUIRE); + gst_pb_utils_init(); + // not thread safe; hopefully fine to consistently call from our event + // loop (which is the only reason to have this + // Application::PostUserEvent diversion, in case + // MissingPluginInstaller::report might be called from outside our event + // loop), and hopefully fine to call gst_is_missing_plugin_message and + // gst_missing_plugin_message_get_installer_detail before calling + // gst_pb_utils_init + ref->launch(); +} + + +struct TheMissingPluginInstaller: + public rtl::Static<MissingPluginInstaller, TheMissingPluginInstaller> +{}; + + +void MissingPluginInstallerThread::execute() { + MissingPluginInstaller & inst = TheMissingPluginInstaller::get(); + for (;;) { + std::vector<OString> details; + { + osl::MutexGuard g(inst.mutex_); + assert(!inst.currentDetails_.empty()); + details.swap(inst.currentDetails_); + } + std::vector<char *> args; + args.reserve(details.size()); + for (auto const& i : details) + { + args.push_back(const_cast<char *>(i.getStr())); + } + args.push_back(nullptr); + gst_install_plugins_sync(args.data(), nullptr); + { + osl::MutexGuard g(inst.mutex_); + if (inst.queued_.empty() || inst.launchNewThread_) { + inst.launchNewThread_ = true; + break; + } + inst.processQueue(); + } + } +} + +} // end anonymous namespace + + +Player::Player() : + GstPlayer_BASE( m_aMutex ), + mpPlaybin( nullptr ), + mpVolumeControl( nullptr ), + mbUseGtkSink( false ), + mbFakeVideo (false ), + mnUnmutedVolume( 0 ), + mbMuted( false ), + mbLooping( false ), + mbInitialized( false ), + mpDisplay( nullptr ), + mnWindowID( 0 ), + mpXOverlay( nullptr ), + mnDuration( 0 ), + mnWidth( 0 ), + mnHeight( 0 ), + mnWatchID( 0 ), + mbWatchID( false ) +{ + // Initialize GStreamer library + int argc = 1; + char name[] = "libreoffice"; + char *arguments[] = { name }; + char** argv = arguments; + GError* pError = nullptr; + + mbInitialized = gst_init_check( &argc, &argv, &pError ); + + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player" ); + + if (pError != nullptr) + { + // TODO: throw an exception? + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player error '" << pError->message << "'" ); + g_error_free (pError); + } +} + + +Player::~Player() +{ + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::~Player" ); + if( mbInitialized ) + disposing(); +} + + +void SAL_CALL Player::disposing() +{ + TheMissingPluginInstaller::get().detach(this); + + ::osl::MutexGuard aGuard(m_aMutex); + + stop(); + + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::disposing" ); + + // Release the elements and pipeline + if( mbInitialized ) + { + if( mpPlaybin ) + { + gst_element_set_state( mpPlaybin, GST_STATE_NULL ); + g_object_unref( G_OBJECT( mpPlaybin ) ); + + mpPlaybin = nullptr; + mpVolumeControl = nullptr; + } + + if( mpXOverlay ) { + g_object_unref( G_OBJECT ( mpXOverlay ) ); + mpXOverlay = nullptr; + } + + } + if (mbWatchID) + { + g_source_remove(mnWatchID); + mbWatchID = false; + } +} + + +static gboolean pipeline_bus_callback( GstBus *, GstMessage *message, gpointer data ) +{ + Player* pPlayer = static_cast<Player*>(data); + + pPlayer->processMessage( message ); + + return true; +} + + +static GstBusSyncReply pipeline_bus_sync_handler( GstBus *, GstMessage * message, gpointer data ) +{ + Player* pPlayer = static_cast<Player*>(data); + + return pPlayer->processSyncMessage( message ); +} + + +void Player::processMessage( GstMessage *message ) +{ + switch( GST_MESSAGE_TYPE( message ) ) { + case GST_MESSAGE_EOS: + gst_element_set_state( mpPlaybin, GST_STATE_READY ); + if (mbLooping) + start(); + break; + case GST_MESSAGE_STATE_CHANGED: + if (message->src == GST_OBJECT(mpPlaybin)) + { + GstState newstate, pendingstate; + + gst_message_parse_state_changed (message, nullptr, &newstate, &pendingstate); + + if (!mbUseGtkSink && newstate == GST_STATE_PAUSED && + pendingstate == GST_STATE_VOID_PENDING && mpXOverlay) + { + gst_video_overlay_expose(mpXOverlay); + } + } + break; + default: + break; + } +} + +#define LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE "GstWaylandDisplayHandleContextType" + +static bool lcl_is_wayland_display_handle_need_context_message(GstMessage* msg) +{ + g_return_val_if_fail(GST_IS_MESSAGE(msg), false); + + if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_NEED_CONTEXT) + return false; + const gchar *type = nullptr; + if (!gst_message_parse_context_type(msg, &type)) + return false; + return !g_strcmp0(type, LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE); +} + +static GstContext* lcl_wayland_display_handle_context_new(void* display) +{ + GstContext *context = gst_context_new(LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE, true); + gst_structure_set (gst_context_writable_structure (context), + "handle", G_TYPE_POINTER, display, nullptr); + return context; +} + +GstBusSyncReply Player::processSyncMessage( GstMessage *message ) +{ +#if OSL_DEBUG_LEVEL > 0 + if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR ) + { + GError* error; + gchar* error_debug; + + gst_message_parse_error( message, &error, &error_debug ); + SAL_WARN( + "avmedia.gstreamer", + "error: '" << error->message << "' debug: '" + << error_debug << "'"); + } +#endif + + if (!mbUseGtkSink) + { + if (gst_is_video_overlay_prepare_window_handle_message (message) ) + { + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " processSyncMessage prepare window id: " << + GST_MESSAGE_TYPE_NAME( message ) << " " << static_cast<int>(mnWindowID) ); + if( mpXOverlay ) + g_object_unref( G_OBJECT ( mpXOverlay ) ); + g_object_set( GST_MESSAGE_SRC( message ), "force-aspect-ratio", FALSE, nullptr ); + mpXOverlay = GST_VIDEO_OVERLAY( GST_MESSAGE_SRC( message ) ); + g_object_ref( G_OBJECT ( mpXOverlay ) ); + if ( mnWindowID != 0 ) + { + gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID ); + gst_video_overlay_handle_events(mpXOverlay, 0); // Let the parent window handle events. + if (maArea.Width > 0 && maArea.Height > 0) + gst_video_overlay_set_render_rectangle(mpXOverlay, maArea.X, maArea.Y, maArea.Width, maArea.Height); + } + + return GST_BUS_DROP; + } + else if (lcl_is_wayland_display_handle_need_context_message(message)) + { + GstContext *context = lcl_wayland_display_handle_context_new(mpDisplay); + gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context); + + return GST_BUS_DROP; + } + } + + if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ASYNC_DONE ) { + if( mnDuration == 0) { + gint64 gst_duration = 0; + if( gst_element_query_duration( mpPlaybin, GST_FORMAT_TIME, &gst_duration) ) + mnDuration = gst_duration; + } + if( mnWidth == 0 ) { + GstPad *pad = nullptr; + + g_signal_emit_by_name( mpPlaybin, "get-video-pad", 0, &pad ); + + if( pad ) { + int w = 0, h = 0; + + GstCaps *caps = gst_pad_get_current_caps( pad ); + + if( gst_structure_get( gst_caps_get_structure( caps, 0 ), + "width", G_TYPE_INT, &w, + "height", G_TYPE_INT, &h, + nullptr ) ) { + mnWidth = w; + mnHeight = h; + + SAL_INFO( "avmedia.gstreamer", AVVERSION "queried size: " << mnWidth << "x" << mnHeight ); + + } + gst_caps_unref( caps ); + g_object_unref( pad ); + } + + maSizeCondition.set(); + } + } else if (gst_is_missing_plugin_message(message)) { + TheMissingPluginInstaller::get().report(this, message); + if( mnWidth == 0 ) { + // an error occurred, set condition so that OOo thread doesn't wait for us + maSizeCondition.set(); + } + } else if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR ) { + if( mnWidth == 0 ) { + // an error occurred, set condition so that OOo thread doesn't wait for us + maSizeCondition.set(); + } + } + + return GST_BUS_PASS; +} + +void Player::preparePlaybin( std::u16string_view rURL, GstElement *pSink ) +{ + if (mpPlaybin != nullptr) + { + gst_element_set_state( mpPlaybin, GST_STATE_NULL ); + g_object_unref( mpPlaybin ); + } + + mpPlaybin = gst_element_factory_make( "playbin", nullptr ); + + //tdf#96989 on systems with flat-volumes setting the volume directly on the + //playbin to 100% results in setting the global volume to 100% of the + //maximum. We expect to set as % of the current volume. + mpVolumeControl = gst_element_factory_make( "volume", nullptr ); + GstElement *pAudioSink = gst_element_factory_make( "autoaudiosink", nullptr ); + GstElement* pAudioOutput = gst_bin_new("audio-output-bin"); + assert(pAudioOutput); + if (pAudioSink) + gst_bin_add(GST_BIN(pAudioOutput), pAudioSink); + if (mpVolumeControl) + { + gst_bin_add(GST_BIN(pAudioOutput), mpVolumeControl); + if (pAudioSink) + gst_element_link(mpVolumeControl, pAudioSink); + GstPad *pPad = gst_element_get_static_pad(mpVolumeControl, "sink"); + gst_element_add_pad(GST_ELEMENT(pAudioOutput), gst_ghost_pad_new("sink", pPad)); + gst_object_unref(GST_OBJECT(pPad)); + } + g_object_set(G_OBJECT(mpPlaybin), "audio-sink", pAudioOutput, nullptr); + + if( pSink != nullptr ) // used for getting preferred size etc. + { + g_object_set( G_OBJECT( mpPlaybin ), "video-sink", pSink, nullptr ); + mbFakeVideo = true; + } + else + mbFakeVideo = false; + + OString ascURL = OUStringToOString( rURL, RTL_TEXTENCODING_UTF8 ); + g_object_set( G_OBJECT( mpPlaybin ), "uri", ascURL.getStr() , nullptr ); + + GstBus *pBus = gst_element_get_bus( mpPlaybin ); + if (mbWatchID) + { + g_source_remove(mnWatchID); + mbWatchID = false; + } + mnWatchID = gst_bus_add_watch( pBus, pipeline_bus_callback, this ); + mbWatchID = true; + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " set sync handler" ); + gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this, nullptr ); + g_object_unref( pBus ); +} + + +bool Player::create( const OUString& rURL ) +{ + bool bRet = false; + + // create all the elements and link them + + SAL_INFO( "avmedia.gstreamer", "create player, URL: '" << rURL << "'" ); + + if( mbInitialized && !rURL.isEmpty() ) + { + // fakesink for pre-roll & sizing ... + preparePlaybin( rURL, gst_element_factory_make( "fakesink", nullptr ) ); + + gst_element_set_state( mpPlaybin, GST_STATE_PAUSED ); + + bRet = true; + } + + if( bRet ) + maURL = rURL; + else + maURL.clear(); + + return bRet; +} + +void SAL_CALL Player::start() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + // set the pipeline state to READY and run the loop + if( mbInitialized && mpPlaybin != nullptr ) + { + gst_element_set_state( mpPlaybin, GST_STATE_PLAYING ); + } +} + +void SAL_CALL Player::stop() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + // set the pipeline in PAUSED STATE + if( mpPlaybin ) + gst_element_set_state( mpPlaybin, GST_STATE_PAUSED ); + + SAL_INFO( "avmedia.gstreamer", AVVERSION "stop " << mpPlaybin ); +} + +sal_Bool SAL_CALL Player::isPlaying() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + bool bRet = false; + + // return whether the pipeline target is PLAYING STATE or not + if (mbInitialized && mpPlaybin) + { + bRet = GST_STATE_TARGET(mpPlaybin) == GST_STATE_PLAYING; + } + + SAL_INFO( "avmedia.gstreamer", AVVERSION "isPlaying " << bRet ); + + return bRet; +} + +double SAL_CALL Player::getDuration() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + // slideshow checks for non-zero duration, so cheat here + double duration = 0.3; + + if( mpPlaybin && mnDuration > 0 ) { + duration = mnDuration / GST_SECOND; + } + + return duration; +} + + +void SAL_CALL Player::setMediaTime( double fTime ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + if( !mpPlaybin ) + return; + + gint64 gst_position = llround (fTime * GST_SECOND); + + gst_element_seek( mpPlaybin, 1.0, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, gst_position, + GST_SEEK_TYPE_NONE, 0 ); + + SAL_INFO( "avmedia.gstreamer", AVVERSION "seek to: " << gst_position << " ns original: " << fTime << " s" ); +} + + +double SAL_CALL Player::getMediaTime() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + double position = 0.0; + + if( mpPlaybin ) { + // get current position in the stream + gint64 gst_position; + if( gst_element_query_position( mpPlaybin, GST_FORMAT_TIME, &gst_position ) ) + position = gst_position / GST_SECOND; + } + + return position; +} + + +void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + // TODO check how to do with GST + mbLooping = bSet; +} + + +sal_Bool SAL_CALL Player::isPlaybackLoop() +{ + ::osl::MutexGuard aGuard(m_aMutex); + // TODO check how to do with GST + return mbLooping; +} + + +void SAL_CALL Player::setMute( sal_Bool bSet ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + SAL_INFO( "avmedia.gstreamer", AVVERSION "set mute: " << bSet << " muted: " << mbMuted << " unmuted volume: " << mnUnmutedVolume ); + + // change the volume to 0 or the unmuted volume + if (mpVolumeControl && mbMuted != bool(bSet)) + { + double nVolume = mnUnmutedVolume; + if( bSet ) + { + nVolume = 0.0; + } + + g_object_set( G_OBJECT( mpVolumeControl ), "volume", nVolume, nullptr ); + + mbMuted = bSet; + } +} + + +sal_Bool SAL_CALL Player::isMute() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + return mbMuted; +} + + +void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + mnUnmutedVolume = pow( 10.0, nVolumeDB / 20.0 ); + + SAL_INFO( "avmedia.gstreamer", AVVERSION "set volume: " << nVolumeDB << " gst volume: " << mnUnmutedVolume ); + + // change volume + if (mpVolumeControl && !mbMuted) + { + g_object_set( G_OBJECT( mpVolumeControl ), "volume", mnUnmutedVolume, nullptr ); + } +} + + +sal_Int16 SAL_CALL Player::getVolumeDB() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + sal_Int16 nVolumeDB(0); + + if (mpVolumeControl) + { + double nGstVolume = 0.0; + + g_object_get( G_OBJECT( mpVolumeControl ), "volume", &nGstVolume, nullptr ); + + nVolumeDB = static_cast<sal_Int16>( 20.0*log10 ( nGstVolume ) ); + } + + return nVolumeDB; +} + + +awt::Size SAL_CALL Player::getPreferredPlayerWindowSize() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + awt::Size aSize( 0, 0 ); + + if( maURL.isEmpty() ) + { + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize - empty URL => 0x0" ); + return aSize; + } + + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " pre-Player::getPreferredPlayerWindowSize, member " << mnWidth << "x" << mnHeight ); + + osl::Condition::Result aResult = maSizeCondition.wait( std::chrono::seconds(10) ); + + SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize after waitCondition " << aResult << ", member " << mnWidth << "x" << mnHeight ); + + if( mnWidth != 0 && mnHeight != 0 ) { + aSize.Width = mnWidth; + aSize.Height = mnHeight; + } + + return aSize; +} + +uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& rArguments ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + uno::Reference< ::media::XPlayerWindow > xRet; + + if (rArguments.getLength() > 1) + rArguments[1] >>= maArea; + + awt::Size aSize = getPreferredPlayerWindowSize(); + + if( mbFakeVideo ) + preparePlaybin( maURL, nullptr ); + + SAL_INFO( "avmedia.gstreamer", AVVERSION "Player::createPlayerWindow " << aSize.Width << "x" << aSize.Height << " length: " << rArguments.getLength() ); + + if( aSize.Width > 0 && aSize.Height > 0 ) + { + if (rArguments.getLength() <= 2) + { + xRet = new ::avmedia::gstreamer::Window; + return xRet; + } + + sal_IntPtr pIntPtr = 0; + rArguments[ 2 ] >>= pIntPtr; + SystemChildWindow *pParentWindow = reinterpret_cast< SystemChildWindow* >( pIntPtr ); + if (!pParentWindow) + return nullptr; + + const SystemEnvData* pEnvData = pParentWindow->GetSystemData(); + if (!pEnvData) + return nullptr; + + // tdf#124027: the position of embedded window is identical w/ the position + // of media object in all other vclplugs (kf5, gen), in gtk3 w/o gtksink it + // needs to be translated + if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk) + { + Point aPoint = pParentWindow->GetPosPixel(); + maArea.X = aPoint.getX(); + maArea.Y = aPoint.getY(); + } + + mbUseGtkSink = false; + + GstElement *pVideosink = static_cast<GstElement*>(pParentWindow->CreateGStreamerSink()); + if (pVideosink) + { + if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk) + mbUseGtkSink = true; + } + else + { + if (pEnvData->platform == SystemEnvData::Platform::Wayland) + pVideosink = gst_element_factory_make("waylandsink", "video-output"); + else + pVideosink = gst_element_factory_make("autovideosink", "video-output"); + if (!pVideosink) + return nullptr; + } + + xRet = new ::avmedia::gstreamer::Window; + + g_object_set(G_OBJECT(mpPlaybin), "video-sink", pVideosink, nullptr); + g_object_set(G_OBJECT(mpPlaybin), "force-aspect-ratio", FALSE, nullptr); + + if ((rArguments.getLength() >= 4) && (rArguments[3] >>= pIntPtr) && pIntPtr) + { + auto pItem = reinterpret_cast<const avmedia::MediaItem*>(pIntPtr); + Graphic aGraphic = pItem->getGraphic(); + const text::GraphicCrop& rCrop = pItem->getCrop(); + if (!aGraphic.IsNone() && (rCrop.Bottom > 0 || rCrop.Left > 0 || rCrop.Right > 0 || rCrop.Top > 0)) + { + // The media item has a non-empty cropping set. Try to crop the video accordingly. + Size aPref = aGraphic.GetPrefSize(); + Size aPixel = aGraphic.GetSizePixel(); + tools::Long nLeft = aPixel.getWidth() * rCrop.Left / aPref.getWidth(); + tools::Long nTop = aPixel.getHeight() * rCrop.Top / aPref.getHeight(); + tools::Long nRight = aPixel.getWidth() * rCrop.Right / aPref.getWidth(); + tools::Long nBottom = aPixel.getHeight() * rCrop.Bottom / aPref.getHeight(); + GstElement* pVideoFilter = gst_element_factory_make("videocrop", nullptr); + if (pVideoFilter) + { + g_object_set(G_OBJECT(pVideoFilter), "left", nLeft, nullptr); + g_object_set(G_OBJECT(pVideoFilter), "top", nTop, nullptr); + g_object_set(G_OBJECT(pVideoFilter), "right", nRight, nullptr); + g_object_set(G_OBJECT(pVideoFilter), "bottom", nBottom, nullptr); + g_object_set(G_OBJECT(mpPlaybin), "video-filter", pVideoFilter, nullptr); + } + } + } + + if (!mbUseGtkSink) + { + mnWindowID = pEnvData->GetWindowHandle(pParentWindow->ImplGetFrame()); + mpDisplay = pEnvData->pDisplay; + SAL_INFO( "avmedia.gstreamer", AVVERSION "set window id to " << static_cast<int>(mnWindowID) << " XOverlay " << mpXOverlay); + } + gst_element_set_state( mpPlaybin, GST_STATE_PAUSED ); + if (!mbUseGtkSink && mpXOverlay) + gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID ); + } + + return xRet; +} + +uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber() +{ + ::osl::MutexGuard aGuard(m_aMutex); + rtl::Reference<FrameGrabber> pFrameGrabber; + const awt::Size aPrefSize( getPreferredPlayerWindowSize() ); + + if( ( aPrefSize.Width > 0 ) && ( aPrefSize.Height > 0 ) ) + pFrameGrabber = FrameGrabber::create( maURL ); + SAL_INFO( "avmedia.gstreamer", AVVERSION "created FrameGrabber " << pFrameGrabber.get() ); + + return pFrameGrabber; +} + + +OUString SAL_CALL Player::getImplementationName() +{ + return AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME; +} + + +sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + + +uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames() +{ + return { AVMEDIA_GST_PLAYER_SERVICENAME }; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstplayer.hxx b/avmedia/source/gstreamer/gstplayer.hxx new file mode 100644 index 000000000..2694ac00c --- /dev/null +++ b/avmedia/source/gstreamer/gstplayer.hxx @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <osl/conditn.hxx> +#include "gstcommon.hxx" + +#include <com/sun/star/media/XPlayer.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +typedef struct _GstVideoOverlay GstVideoOverlay; + +namespace avmedia::gstreamer { + + +typedef ::cppu::WeakComponentImplHelper< css::media::XPlayer, + css::lang::XServiceInfo > GstPlayer_BASE; + +class Player final : public ::cppu::BaseMutex, + public GstPlayer_BASE +{ +public: + + explicit Player(); + virtual ~Player() override; + + void preparePlaybin( std::u16string_view rURL, GstElement *pSink ); + bool create( const OUString& rURL ); + void processMessage( GstMessage *message ); + GstBusSyncReply processSyncMessage( GstMessage *message ); + + // XPlayer + virtual void SAL_CALL start( ) override; + virtual void SAL_CALL stop( ) override; + virtual sal_Bool SAL_CALL isPlaying( ) override; + virtual double SAL_CALL getDuration( ) override; + virtual void SAL_CALL setMediaTime( double fTime ) override; + virtual double SAL_CALL getMediaTime( ) override; + virtual void SAL_CALL setPlaybackLoop( sal_Bool bSet ) override; + virtual sal_Bool SAL_CALL isPlaybackLoop( ) override; + virtual void SAL_CALL setMute( sal_Bool bSet ) override; + virtual sal_Bool SAL_CALL isMute( ) override; + virtual void SAL_CALL setVolumeDB( sal_Int16 nVolumeDB ) override; + virtual sal_Int16 SAL_CALL getVolumeDB( ) override; + virtual css::awt::Size SAL_CALL getPreferredPlayerWindowSize( ) override; + virtual css::uno::Reference< css::media::XPlayerWindow > SAL_CALL createPlayerWindow( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + virtual css::uno::Reference< css::media::XFrameGrabber > SAL_CALL createFrameGrabber( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // ::cppu::OComponentHelper + virtual void SAL_CALL disposing() final override; + +private: + OUString maURL; + + // Add elements and pipeline here + GstElement* mpPlaybin; // the playbin is also a pipeline + GstElement* mpVolumeControl; // the playbin is also a pipeline + bool mbUseGtkSink; + bool mbFakeVideo; + + gdouble mnUnmutedVolume; + bool mbMuted; + bool mbLooping; + bool mbInitialized; + + void* mpDisplay; + tools::Long mnWindowID; + GstVideoOverlay* mpXOverlay; + gint64 mnDuration; + int mnWidth; + int mnHeight; + + css::awt::Rectangle maArea; // Area of the player window. + + guint mnWatchID; + bool mbWatchID; + + osl::Condition maSizeCondition; +}; + +} // namespace avmedia::gstreamer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstwindow.cxx b/avmedia/source/gstreamer/gstwindow.cxx new file mode 100644 index 000000000..5862443da --- /dev/null +++ b/avmedia/source/gstreamer/gstwindow.cxx @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/awt/SystemPointer.hpp> + +#include <cppuhelper/supportsservice.hxx> + +#include "gstwindow.hxx" + +constexpr OUStringLiteral AVMEDIA_GST_WINDOW_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.Window_GStreamer"; +constexpr OUStringLiteral AVMEDIA_GST_WINDOW_SERVICENAME = u"com.sun.star.media.Window_GStreamer"; + +using namespace ::com::sun::star; + +namespace avmedia::gstreamer { + +// Window + + +Window::Window() : + meZoomLevel( media::ZoomLevel_NOT_AVAILABLE ) +{ +} + +Window::~Window() +{ +} + +// XPlayerWindow + + +void SAL_CALL Window::update( ) +{ +} + +sal_Bool SAL_CALL Window::setZoomLevel( media::ZoomLevel eZoomLevel ) +{ + bool bRet = false; + + if( meZoomLevel != media::ZoomLevel_NOT_AVAILABLE && + eZoomLevel != media::ZoomLevel_NOT_AVAILABLE ) + { + if( eZoomLevel != meZoomLevel ) + { + meZoomLevel = eZoomLevel; + } + + bRet = true; + } + + return bRet; +} + +media::ZoomLevel SAL_CALL Window::getZoomLevel( ) +{ + return meZoomLevel; +} + +void SAL_CALL Window::setPointerType( sal_Int32 /*nPointerType*/ ) +{ +} + +// XWindow + + +void SAL_CALL Window::setPosSize( sal_Int32 /*X*/, sal_Int32 /*Y*/, sal_Int32 /*Width*/, sal_Int32 /*Height*/, sal_Int16 /*Flags*/ ) +{ +} + +awt::Rectangle SAL_CALL Window::getPosSize() +{ + awt::Rectangle aRet; + + aRet.X = aRet.Y = 0; + aRet.Width = 320; + aRet.Height = 240; + + return aRet; +} + +void SAL_CALL Window::setVisible( sal_Bool /*bVisible*/ ) +{ +} + +void SAL_CALL Window::setEnable( sal_Bool /*bEnable*/ ) +{ +} + +void SAL_CALL Window::setFocus( ) +{ +} + +void SAL_CALL Window::addWindowListener( const uno::Reference< awt::XWindowListener >& ) +{ +} + +void SAL_CALL Window::removeWindowListener( const uno::Reference< awt::XWindowListener >& ) +{ +} + +void SAL_CALL Window::addFocusListener( const uno::Reference< awt::XFocusListener >& ) +{ +} + +void SAL_CALL Window::removeFocusListener( const uno::Reference< awt::XFocusListener >& ) +{ +} + +void SAL_CALL Window::addKeyListener( const uno::Reference< awt::XKeyListener >& ) +{ +} + +void SAL_CALL Window::removeKeyListener( const uno::Reference< awt::XKeyListener >& ) +{ +} + +void SAL_CALL Window::addMouseListener( const uno::Reference< awt::XMouseListener >& ) +{ +} + +void SAL_CALL Window::removeMouseListener( const uno::Reference< awt::XMouseListener >& ) +{ +} + +void SAL_CALL Window::addMouseMotionListener( const uno::Reference< awt::XMouseMotionListener >& ) +{ +} + +void SAL_CALL Window::removeMouseMotionListener( const uno::Reference< awt::XMouseMotionListener >& ) +{ +} + +void SAL_CALL Window::addPaintListener( const uno::Reference< awt::XPaintListener >& ) +{ +} + +void SAL_CALL Window::removePaintListener( const uno::Reference< awt::XPaintListener >& ) +{ +} + +// XComponent + + +void SAL_CALL Window::dispose( ) +{ +} + +void SAL_CALL Window::addEventListener( const uno::Reference< lang::XEventListener >& ) +{ +} + +void SAL_CALL Window::removeEventListener( const uno::Reference< lang::XEventListener >& ) +{ +} + +// XServiceInfo + + +OUString SAL_CALL Window::getImplementationName( ) +{ + return AVMEDIA_GST_WINDOW_IMPLEMENTATIONNAME; +} + +sal_Bool SAL_CALL Window::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL Window::getSupportedServiceNames( ) +{ + return { AVMEDIA_GST_WINDOW_SERVICENAME }; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gstreamer/gstwindow.hxx b/avmedia/source/gstreamer/gstwindow.hxx new file mode 100644 index 000000000..ff8a7cc91 --- /dev/null +++ b/avmedia/source/gstreamer/gstwindow.hxx @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/interfacecontainer.h> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/media/XPlayerWindow.hpp> + +namespace avmedia::gstreamer { + +class Player; + +class Window : public ::cppu::WeakImplHelper< css::media::XPlayerWindow, + css::lang::XServiceInfo > +{ +public: + + explicit Window(); + virtual ~Window() override; + + // XPlayerWindow + virtual void SAL_CALL update( ) override; + virtual sal_Bool SAL_CALL setZoomLevel( css::media::ZoomLevel ZoomLevel ) override; + virtual css::media::ZoomLevel SAL_CALL getZoomLevel( ) override; + virtual void SAL_CALL setPointerType( sal_Int32 nPointerType ) override; + + // XWindow + virtual void SAL_CALL setPosSize( sal_Int32 X, sal_Int32 Y, sal_Int32 Width, sal_Int32 Height, sal_Int16 Flags ) override; + virtual css::awt::Rectangle SAL_CALL getPosSize( ) override; + virtual void SAL_CALL setVisible( sal_Bool Visible ) override; + virtual void SAL_CALL setEnable( sal_Bool Enable ) override; + virtual void SAL_CALL setFocus( ) override; + virtual void SAL_CALL addWindowListener( const css::uno::Reference< css::awt::XWindowListener >& xListener ) override; + virtual void SAL_CALL removeWindowListener( const css::uno::Reference< css::awt::XWindowListener >& xListener ) override; + virtual void SAL_CALL addFocusListener( const css::uno::Reference< css::awt::XFocusListener >& xListener ) override; + virtual void SAL_CALL removeFocusListener( const css::uno::Reference< css::awt::XFocusListener >& xListener ) override; + virtual void SAL_CALL addKeyListener( const css::uno::Reference< css::awt::XKeyListener >& xListener ) override; + virtual void SAL_CALL removeKeyListener( const css::uno::Reference< css::awt::XKeyListener >& xListener ) override; + virtual void SAL_CALL addMouseListener( const css::uno::Reference< css::awt::XMouseListener >& xListener ) override; + virtual void SAL_CALL removeMouseListener( const css::uno::Reference< css::awt::XMouseListener >& xListener ) override; + virtual void SAL_CALL addMouseMotionListener( const css::uno::Reference< css::awt::XMouseMotionListener >& xListener ) override; + virtual void SAL_CALL removeMouseMotionListener( const css::uno::Reference< css::awt::XMouseMotionListener >& xListener ) override; + virtual void SAL_CALL addPaintListener( const css::uno::Reference< css::awt::XPaintListener >& xListener ) override; + virtual void SAL_CALL removePaintListener( const css::uno::Reference< css::awt::XPaintListener >& xListener ) override; + + // XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + + css::media::ZoomLevel meZoomLevel; +}; + +} // namespace avmedia::gstreamer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gtk/avmediagtk.component b/avmedia/source/gtk/avmediagtk.component new file mode 100644 index 000000000..e3930dc9e --- /dev/null +++ b/avmedia/source/gtk/avmediagtk.component @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.media.Manager_Gtk" + constructor="com_sun_star_comp_media_Manager_Gtk_get_implementation"> + <service name="com.sun.star.comp.avmedia.Manager_Gtk"/> + </implementation> +</component> diff --git a/avmedia/source/gtk/gstwindow.cxx b/avmedia/source/gtk/gstwindow.cxx new file mode 100644 index 000000000..48c70df98 --- /dev/null +++ b/avmedia/source/gtk/gstwindow.cxx @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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 "../gstreamer/gstwindow.cxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/gtk/gtkmanager.cxx b/avmedia/source/gtk/gtkmanager.cxx new file mode 100644 index 000000000..5beff87e1 --- /dev/null +++ b/avmedia/source/gtk/gtkmanager.cxx @@ -0,0 +1,61 @@ +/* -*- 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 <cppuhelper/supportsservice.hxx> + +#include "gtkmanager.hxx" +#include "gtkplayer.hxx" + +#include <tools/urlobj.hxx> +#include <rtl/ref.hxx> + +using namespace ::com::sun::star; + +namespace avmedia::gtk +{ +Manager::Manager() {} + +Manager::~Manager() {} + +uno::Reference<media::XPlayer> SAL_CALL Manager::createPlayer(const OUString& rURL) +{ + const INetURLObject aURL(rURL); + OUString sMainURL = aURL.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous); + + rtl::Reference<GtkPlayer> xPlayer(new GtkPlayer); + if (!xPlayer->create(sMainURL)) + xPlayer.clear(); + return xPlayer; +} + +OUString SAL_CALL Manager::getImplementationName() +{ + return "com.sun.star.comp.avmedia.ManagerGtk"; +} + +sal_Bool SAL_CALL Manager::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence<OUString> SAL_CALL Manager::getSupportedServiceNames() +{ + return { "com.sun.star.media.Manager" }; +} + +} // namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_media_Manager_Gtk_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new avmedia::gtk::Manager()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/avmedia/source/gtk/gtkmanager.hxx b/avmedia/source/gtk/gtkmanager.hxx new file mode 100644 index 000000000..9cf6d93f5 --- /dev/null +++ b/avmedia/source/gtk/gtkmanager.hxx @@ -0,0 +1,34 @@ +/* -*- 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/. + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/media/XManager.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +namespace avmedia::gtk +{ +class Manager : public cppu::WeakImplHelper<css::media::XManager, css::lang::XServiceInfo> +{ +public: + explicit Manager(); + virtual ~Manager() override; + + virtual css::uno::Reference<css::media::XPlayer> + SAL_CALL createPlayer(const OUString& aURL) override; + + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/avmedia/source/gtk/gtkplayer.cxx b/avmedia/source/gtk/gtkplayer.cxx new file mode 100644 index 000000000..b988e7f19 --- /dev/null +++ b/avmedia/source/gtk/gtkplayer.cxx @@ -0,0 +1,461 @@ +/* -*- 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 <sal/config.h> + +#include <mutex> + +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> +#include <rtl/string.hxx> +#include <tools/link.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syschild.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/timer.hxx> + +#include <gstwindow.hxx> +#include "gtkplayer.hxx" + +#include <gtk/gtk.h> + +constexpr OUStringLiteral AVMEDIA_GTK_PLAYER_IMPLEMENTATIONNAME + = u"com.sun.star.comp.avmedia.Player_Gtk"; +constexpr OUStringLiteral AVMEDIA_GTK_PLAYER_SERVICENAME = u"com.sun.star.media.Player_Gtk"; + +using namespace ::com::sun::star; + +namespace avmedia::gtk +{ +GtkPlayer::GtkPlayer() + : GtkPlayer_BASE(m_aMutex) + , m_lListener(m_aMutex) + , m_pStream(nullptr) + , m_pVideo(nullptr) + , m_nNotifySignalId(0) + , m_nInvalidateSizeSignalId(0) + , m_nTimeoutId(0) + , m_nUnmutedVolume(0) +{ +} + +GtkPlayer::~GtkPlayer() { disposing(); } + +static gboolean gtk_media_stream_unref(gpointer user_data) +{ + g_object_unref(user_data); + return FALSE; +} + +void GtkPlayer::cleanup() +{ + if (m_pVideo) + { + gtk_widget_unparent(m_pVideo); + m_pVideo = nullptr; + } + + if (m_pStream) + { + uninstallNotify(); + + // shouldn't have to attempt this unref on idle, but with gtk4-4.4.1 I get + // intermittent "instance of invalid non-instantiatable type '(null)'" + // on some mysterious gst dbus callback + if (g_main_context_default()) + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, gtk_media_stream_unref, m_pStream, nullptr); + else + g_object_unref(m_pStream); + m_pStream = nullptr; + } +} + +void SAL_CALL GtkPlayer::disposing() +{ + osl::MutexGuard aGuard(m_aMutex); + + stop(); + + cleanup(); +} + +static void do_notify(GtkPlayer* pThis) +{ + rtl::Reference<GtkPlayer> xThis(pThis); + xThis->notifyListeners(); + xThis->uninstallNotify(); +} + +static void invalidate_size_cb(GdkPaintable* /*pPaintable*/, GtkPlayer* pThis) { do_notify(pThis); } + +static void notify_cb(GtkMediaStream* /*pStream*/, GParamSpec* pspec, GtkPlayer* pThis) +{ + if (g_str_equal(pspec->name, "prepared") || g_str_equal(pspec->name, "error")) + do_notify(pThis); +} + +static bool timeout_cb(GtkPlayer* pThis) +{ + do_notify(pThis); + return false; +} + +void GtkPlayer::installNotify() +{ + if (m_nNotifySignalId) + return; + m_nNotifySignalId = g_signal_connect(m_pStream, "notify", G_CALLBACK(notify_cb), this); + // notify should be enough, but there is an upstream bug so also try "invalidate-size" and add a timeout for + // audio-only case where that won't happen, see: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4513 + m_nInvalidateSizeSignalId + = g_signal_connect(m_pStream, "invalidate-size", G_CALLBACK(invalidate_size_cb), this); + m_nTimeoutId = g_timeout_add_seconds(10, G_SOURCE_FUNC(timeout_cb), this); +} + +void GtkPlayer::uninstallNotify() +{ + if (!m_nNotifySignalId) + return; + g_signal_handler_disconnect(m_pStream, m_nNotifySignalId); + m_nNotifySignalId = 0; + g_signal_handler_disconnect(m_pStream, m_nInvalidateSizeSignalId); + m_nInvalidateSizeSignalId = 0; + g_source_remove(m_nTimeoutId); + m_nTimeoutId = 0; +} + +bool GtkPlayer::create(const OUString& rURL) +{ + bool bRet = false; + + cleanup(); + + if (!rURL.isEmpty()) + { + GFile* pFile = g_file_new_for_uri(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8).getStr()); + m_pStream = gtk_media_file_new_for_file(pFile); + g_object_unref(pFile); + + bRet = gtk_media_stream_get_error(m_pStream) == nullptr; + } + + if (bRet) + m_aURL = rURL; + else + m_aURL.clear(); + + return bRet; +} + +void GtkPlayer::notifyListeners() +{ + comphelper::OInterfaceContainerHelper2* pContainer + = m_lListener.getContainer(cppu::UnoType<css::media::XPlayerListener>::get()); + if (!pContainer) + return; + + css::lang::EventObject aEvent; + aEvent.Source = static_cast<cppu::OWeakObject*>(this); + + comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + { + css::uno::Reference<css::media::XPlayerListener> xListener( + static_cast<css::media::XPlayerListener*>(pIterator.next())); + xListener->preferredPlayerWindowSizeAvailable(aEvent); + } +} + +void SAL_CALL GtkPlayer::start() +{ + osl::MutexGuard aGuard(m_aMutex); + + if (m_pStream) + gtk_media_stream_play(m_pStream); +} + +void SAL_CALL GtkPlayer::stop() +{ + osl::MutexGuard aGuard(m_aMutex); + + if (m_pStream) + gtk_media_stream_pause(m_pStream); +} + +sal_Bool SAL_CALL GtkPlayer::isPlaying() +{ + osl::MutexGuard aGuard(m_aMutex); + + bool bRet = false; + + if (m_pStream) + bRet = gtk_media_stream_get_playing(m_pStream); + + return bRet; +} + +double SAL_CALL GtkPlayer::getDuration() +{ + osl::MutexGuard aGuard(m_aMutex); + + double duration = 0.0; + + if (m_pStream) + duration = gtk_media_stream_get_duration(m_pStream) / 1000000.0; + + return duration; +} + +void SAL_CALL GtkPlayer::setMediaTime(double fTime) +{ + osl::MutexGuard aGuard(m_aMutex); + + if (!m_pStream) + return; + + gint64 gst_position = llround(fTime * 1000000); + + gtk_media_stream_seek(m_pStream, gst_position); + + // on resetting back to zero the reported timestamp doesn't seem to get + // updated in a reasonable time, so on zero just force an update of + // timestamp to 0 + if (gst_position == 0 && gtk_media_stream_is_prepared(m_pStream)) + gtk_media_stream_update(m_pStream, gst_position); +} + +double SAL_CALL GtkPlayer::getMediaTime() +{ + osl::MutexGuard aGuard(m_aMutex); + + double position = 0.0; + + if (m_pStream) + position = gtk_media_stream_get_timestamp(m_pStream) / 1000000.0; + + return position; +} + +void SAL_CALL GtkPlayer::setPlaybackLoop(sal_Bool bSet) +{ + osl::MutexGuard aGuard(m_aMutex); + gtk_media_stream_set_loop(m_pStream, bSet); +} + +sal_Bool SAL_CALL GtkPlayer::isPlaybackLoop() +{ + osl::MutexGuard aGuard(m_aMutex); + return gtk_media_stream_get_loop(m_pStream); +} + +// gtk4-4.4.1 docs state: "Muting a stream will cause no audio to be played, but +// it does not modify the volume. This means that muting and then unmuting the +// stream will restore the volume settings." but that doesn't seem to be my +// experience at all +void SAL_CALL GtkPlayer::setMute(sal_Bool bSet) +{ + osl::MutexGuard aGuard(m_aMutex); + bool bMuted = gtk_media_stream_get_muted(m_pStream); + if (bMuted == static_cast<bool>(bSet)) + return; + gtk_media_stream_set_muted(m_pStream, bSet); + if (!bSet) + setVolumeDB(m_nUnmutedVolume); +} + +sal_Bool SAL_CALL GtkPlayer::isMute() +{ + osl::MutexGuard aGuard(m_aMutex); + return gtk_media_stream_get_muted(m_pStream); +} + +void SAL_CALL GtkPlayer::setVolumeDB(sal_Int16 nVolumeDB) +{ + osl::MutexGuard aGuard(m_aMutex); + + // range is -40 for silence to 0 for full volume + m_nUnmutedVolume = std::clamp<sal_Int16>(nVolumeDB, -40, 0); + double fValue = (m_nUnmutedVolume + 40) / 40.0; + gtk_media_stream_set_volume(m_pStream, fValue); +} + +sal_Int16 SAL_CALL GtkPlayer::getVolumeDB() +{ + osl::MutexGuard aGuard(m_aMutex); + + if (gtk_media_stream_get_muted(m_pStream)) + return m_nUnmutedVolume; + + double fVolume = gtk_media_stream_get_volume(m_pStream); + + m_nUnmutedVolume = (fVolume * 40) - 40; + + return m_nUnmutedVolume; +} + +awt::Size SAL_CALL GtkPlayer::getPreferredPlayerWindowSize() +{ + osl::MutexGuard aGuard(m_aMutex); + + awt::Size aSize(0, 0); + + if (m_pStream) + { + aSize.Width = gdk_paintable_get_intrinsic_width(GDK_PAINTABLE(m_pStream)); + aSize.Height = gdk_paintable_get_intrinsic_height(GDK_PAINTABLE(m_pStream)); + } + + return aSize; +} + +uno::Reference<::media::XPlayerWindow> + SAL_CALL GtkPlayer::createPlayerWindow(const uno::Sequence<uno::Any>& rArguments) +{ + osl::MutexGuard aGuard(m_aMutex); + + uno::Reference<::media::XPlayerWindow> xRet; + + if (rArguments.getLength() > 1) + rArguments[1] >>= m_aArea; + + if (rArguments.getLength() <= 2) + { + xRet = new ::avmedia::gstreamer::Window; + return xRet; + } + + sal_IntPtr pIntPtr = 0; + rArguments[2] >>= pIntPtr; + SystemChildWindow* pParentWindow = reinterpret_cast<SystemChildWindow*>(pIntPtr); + if (!pParentWindow) + return nullptr; + + const SystemEnvData* pEnvData = pParentWindow->GetSystemData(); + if (!pEnvData) + return nullptr; + + m_pVideo = gtk_picture_new_for_paintable(GDK_PAINTABLE(m_pStream)); + gtk_picture_set_keep_aspect_ratio(GTK_PICTURE(m_pVideo), false); + gtk_widget_set_can_target(m_pVideo, false); + gtk_widget_set_vexpand(m_pVideo, true); + gtk_widget_set_hexpand(m_pVideo, true); + + GtkWidget* pParent = static_cast<GtkWidget*>(pEnvData->pWidget); + gtk_widget_set_can_target(pParent, false); + gtk_grid_attach(GTK_GRID(pParent), m_pVideo, 0, 0, 1, 1); + gtk_widget_show(m_pVideo); + gtk_widget_show(pParent); + + xRet = new ::avmedia::gstreamer::Window; + + return xRet; +} + +void SAL_CALL +GtkPlayer::addPlayerListener(const css::uno::Reference<css::media::XPlayerListener>& rListener) +{ + m_lListener.addInterface(cppu::UnoType<css::media::XPlayerListener>::get(), rListener); + if (gtk_media_stream_is_prepared(m_pStream)) + { + css::lang::EventObject aEvent; + aEvent.Source = static_cast<cppu::OWeakObject*>(this); + rListener->preferredPlayerWindowSizeAvailable(aEvent); + } + else + installNotify(); +} + +void SAL_CALL +GtkPlayer::removePlayerListener(const css::uno::Reference<css::media::XPlayerListener>& rListener) +{ + m_lListener.removeInterface(cppu::UnoType<css::media::XPlayerListener>::get(), rListener); +} + +namespace +{ +class GtkFrameGrabber : public ::cppu::WeakImplHelper<css::media::XFrameGrabber> +{ +private: + awt::Size m_aSize; + GtkMediaStream* m_pStream; + +public: + GtkFrameGrabber(GtkMediaStream* pStream, const awt::Size& rSize) + : m_aSize(rSize) + , m_pStream(pStream) + { + g_object_ref(m_pStream); + } + + virtual ~GtkFrameGrabber() override { g_object_unref(m_pStream); } + + // XFrameGrabber + virtual css::uno::Reference<css::graphic::XGraphic> + SAL_CALL grabFrame(double fMediaTime) override + { + gint64 gst_position = llround(fMediaTime * 1000000); + gtk_media_stream_seek(m_pStream, gst_position); + + cairo_surface_t* surface + = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, m_aSize.Width, m_aSize.Height); + + GtkSnapshot* snapshot = gtk_snapshot_new(); + gdk_paintable_snapshot(GDK_PAINTABLE(m_pStream), snapshot, m_aSize.Width, m_aSize.Height); + GskRenderNode* node = gtk_snapshot_free_to_node(snapshot); + + cairo_t* cr = cairo_create(surface); + gsk_render_node_draw(node, cr); + cairo_destroy(cr); + + gsk_render_node_unref(node); + + std::unique_ptr<BitmapEx> xBitmap( + vcl::bitmap::CreateFromCairoSurface(Size(m_aSize.Width, m_aSize.Height), surface)); + + cairo_surface_destroy(surface); + + return Graphic(*xBitmap).GetXGraphic(); + } +}; +} + +uno::Reference<media::XFrameGrabber> SAL_CALL GtkPlayer::createFrameGrabber() +{ + osl::MutexGuard aGuard(m_aMutex); + + rtl::Reference<GtkFrameGrabber> xFrameGrabber; + + const awt::Size aPrefSize(getPreferredPlayerWindowSize()); + + if (aPrefSize.Width > 0 && aPrefSize.Height > 0) + xFrameGrabber.set(new GtkFrameGrabber(m_pStream, aPrefSize)); + + return xFrameGrabber; +} + +OUString SAL_CALL GtkPlayer::getImplementationName() +{ + return AVMEDIA_GTK_PLAYER_IMPLEMENTATIONNAME; +} + +sal_Bool SAL_CALL GtkPlayer::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence<OUString> SAL_CALL GtkPlayer::getSupportedServiceNames() +{ + return { AVMEDIA_GTK_PLAYER_SERVICENAME }; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/avmedia/source/gtk/gtkplayer.hxx b/avmedia/source/gtk/gtkplayer.hxx new file mode 100644 index 000000000..46e416e79 --- /dev/null +++ b/avmedia/source/gtk/gtkplayer.hxx @@ -0,0 +1,89 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/media/XPlayer.hpp> +#include <com/sun/star/media/XPlayerNotifier.hpp> +#include <comphelper/multicontainer2.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +typedef struct _GtkMediaStream GtkMediaStream; +typedef struct _GtkWidget GtkWidget; + +namespace avmedia::gtk +{ +typedef cppu::WeakComponentImplHelper<css::media::XPlayer, css::media::XPlayerNotifier, + css::lang::XServiceInfo> + GtkPlayer_BASE; + +class GtkPlayer final : public cppu::BaseMutex, public GtkPlayer_BASE +{ +public: + explicit GtkPlayer(); + virtual ~GtkPlayer() override; + + bool create(const OUString& rURL); + + virtual void SAL_CALL start() override; + virtual void SAL_CALL stop() override; + virtual sal_Bool SAL_CALL isPlaying() override; + virtual double SAL_CALL getDuration() override; + virtual void SAL_CALL setMediaTime(double fTime) override; + virtual double SAL_CALL getMediaTime() override; + virtual void SAL_CALL setPlaybackLoop(sal_Bool bSet) override; + virtual sal_Bool SAL_CALL isPlaybackLoop() override; + virtual void SAL_CALL setMute(sal_Bool bSet) override; + virtual sal_Bool SAL_CALL isMute() override; + virtual void SAL_CALL setVolumeDB(sal_Int16 nVolumeDB) override; + virtual sal_Int16 SAL_CALL getVolumeDB() override; + virtual css::awt::Size SAL_CALL getPreferredPlayerWindowSize() override; + virtual css::uno::Reference<css::media::XPlayerWindow> + SAL_CALL createPlayerWindow(const css::uno::Sequence<css::uno::Any>& rArgs) override; + virtual css::uno::Reference<css::media::XFrameGrabber> SAL_CALL createFrameGrabber() override; + + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + virtual void SAL_CALL + addPlayerListener(const css::uno::Reference<css::media::XPlayerListener>& rListener) override; + virtual void SAL_CALL removePlayerListener( + const css::uno::Reference<css::media::XPlayerListener>& rListener) override; + + virtual void SAL_CALL disposing() final override; + + void notifyListeners(); + void installNotify(); + void uninstallNotify(); + +private: + void cleanup(); + + comphelper::OMultiTypeInterfaceContainerHelper2 m_lListener; + + OUString m_aURL; + css::awt::Rectangle m_aArea; // Area of the player window. + GtkMediaStream* m_pStream; + GtkWidget* m_pVideo; + unsigned long m_nNotifySignalId; + unsigned long m_nInvalidateSizeSignalId; + unsigned long m_nTimeoutId; + sal_Int16 m_nUnmutedVolume; +}; + +} // namespace avmedia::gtk + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/avmedia/source/inc/mediamisc.hxx b/avmedia/source/inc/mediamisc.hxx new file mode 100644 index 000000000..061a7177f --- /dev/null +++ b/avmedia/source/inc/mediamisc.hxx @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <unotools/resmgr.hxx> + +#ifdef _WIN32 +#define AVMEDIA_MANAGER_SERVICE_NAME "com.sun.star.comp.avmedia.Manager_DirectX" +#else +#ifdef MACOSX +#define AVMEDIA_MANAGER_SERVICE_NAME "com.sun.star.comp.avmedia.Manager_MacAVF" +#else +#define AVMEDIA_MANAGER_SERVICE_NAME "com.sun.star.comp.avmedia.Manager_GStreamer" +#endif +#endif + +// Mime types +inline constexpr OUStringLiteral AVMEDIA_MIMETYPE_COMMON = u"application/vnd.sun.star.media"; + +inline OUString AvmResId(TranslateId aId) +{ + return Translate::get(aId, Translate::Create("avmedia")); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/avmediaMacAVF.component b/avmedia/source/macavf/avmediaMacAVF.component new file mode 100644 index 000000000..3cb9966c1 --- /dev/null +++ b/avmedia/source/macavf/avmediaMacAVF.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.avmedia.Manager_MacAVF" + constructor="com_sun_star_comp_avmedia_Manager_MacAVF_get_implementation"> + <service name="com.sun.star.media.Manager_MacAVF"/> + </implementation> +</component> diff --git a/avmedia/source/macavf/framegrabber.hxx b/avmedia/source/macavf/framegrabber.hxx new file mode 100644 index 000000000..5e0c749d9 --- /dev/null +++ b/avmedia/source/macavf/framegrabber.hxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "macavfcommon.hxx" +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/media/XFrameGrabber.hpp> + +namespace avmedia::macavf { + +class FrameGrabber : public ::cppu::WeakImplHelper< css::media::XFrameGrabber, + css::lang::XServiceInfo > +{ +public: + + explicit FrameGrabber(); + virtual ~FrameGrabber() override; + + bool create( AVAsset* pMovie ); + + // XFrameGrabber + virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL grabFrame( double fMediaTime ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + + AVAssetImageGenerator* mpImageGen; +}; + +} // namespace avmedia::macavf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/framegrabber.mm b/avmedia/source/macavf/framegrabber.mm new file mode 100644 index 000000000..e0b8bad61 --- /dev/null +++ b/avmedia/source/macavf/framegrabber.mm @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "framegrabber.hxx" +#include "player.hxx" + +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <vcl/graph.hxx> +#include <vcl/cvtgrf.hxx> +#include <unotools/localfilehelper.hxx> + +using namespace ::com::sun::star; + +namespace avmedia::macavf { + +FrameGrabber::FrameGrabber() +: mpImageGen( nullptr ) +{} + + +FrameGrabber::~FrameGrabber() +{ + if( mpImageGen ) + CFRelease( mpImageGen ); +} + + +bool FrameGrabber::create( AVAsset* pMovie ) +{ + if( [[pMovie tracksWithMediaType:AVMediaTypeVideo] count] == 0) + { + SAL_WARN("avmedia", "AVGrabber::create() found no video content!" ); + return false; + } + + mpImageGen = [AVAssetImageGenerator assetImageGeneratorWithAsset:pMovie]; + CFRetain( mpImageGen ); + return true; +} + + +uno::Reference< graphic::XGraphic > SAL_CALL FrameGrabber::grabFrame( double fMediaTime ) +{ + uno::Reference< graphic::XGraphic > xRet; + if( !mpImageGen ) + return xRet; + + // get the requested image from the movie + CGImage* pCGImage = [mpImageGen copyCGImageAtTime:CMTimeMakeWithSeconds(fMediaTime,1000) actualTime:nullptr error:nullptr]; + + // convert the image to a TIFF-formatted byte-array + CFMutableDataRef pCFData = CFDataCreateMutable( kCFAllocatorDefault, 0 ); + SAL_WNODEPRECATED_DECLARATIONS_PUSH // kUTTypeTIFF (12.0) + CGImageDestination* pCGImgDest = CGImageDestinationCreateWithData( pCFData, kUTTypeTIFF, 1, nullptr ); + SAL_WNODEPRECATED_DECLARATIONS_POP + CGImageDestinationAddImage( pCGImgDest, pCGImage, nullptr ); + CGImageDestinationFinalize( pCGImgDest ); + CFRelease( pCGImgDest ); + const CFIndex nBitmapLen = CFDataGetLength( pCFData ); + UInt8 * pBitmapBytes = const_cast<UInt8 *>(CFDataGetBytePtr( pCFData )); + + // convert the image into the return-value type which is a graphic::XGraphic + SvMemoryStream aMemStm( pBitmapBytes, nBitmapLen, StreamMode::READ | StreamMode::WRITE ); + Graphic aGraphic; + if( GraphicConverter::Import( aMemStm, aGraphic, ConvertDataFormat::TIF ) == ERRCODE_NONE ) + xRet = aGraphic.GetXGraphic(); + + // clean up resources + CFRelease( pCFData ); + return xRet; +} + + +OUString SAL_CALL FrameGrabber::getImplementationName( ) +{ + return AVMEDIA_MACAVF_FRAMEGRABBER_IMPLEMENTATIONNAME; +} + +sal_Bool SAL_CALL FrameGrabber::supportsService( const OUString& ServiceName ) +{ + return ServiceName == AVMEDIA_MACAVF_FRAMEGRABBER_SERVICENAME; +} + +uno::Sequence< OUString > SAL_CALL FrameGrabber::getSupportedServiceNames( ) +{ + return { AVMEDIA_MACAVF_FRAMEGRABBER_SERVICENAME }; +} + +} // namespace avmedia::macavf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/macavfcommon.hxx b/avmedia/source/macavf/macavfcommon.hxx new file mode 100644 index 000000000..b031f1ecc --- /dev/null +++ b/avmedia/source/macavf/macavfcommon.hxx @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <premac.h> +#import <Cocoa/Cocoa.h> +#import <AVFoundation/AVFoundation.h> +#include <postmac.h> + +#include <unordered_map> + +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <cppuhelper/weak.hxx> +#include <cppuhelper/factory.hxx> + +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/media/XManager.hpp> + + +#define AVMEDIA_MACAVF_PLAYER_IMPLEMENTATIONNAME "com.sun.star.comp.avmedia.Player_MacAVF" +#define AVMEDIA_MACAVF_PLAYER_SERVICENAME "com.sun.star.media.Player_MacAVF" + +#define AVMEDIA_MACAVF_WINDOW_IMPLEMENTATIONNAME "com.sun.star.comp.avmedia.Window_MacAVF" +#define AVMEDIA_MACAVF_WINDOW_SERVICENAME "com.sun.star.media.Window_MacAVF" + +#define AVMEDIA_MACAVF_FRAMEGRABBER_IMPLEMENTATIONNAME "com.sun.star.comp.avmedia.FrameGrabber_MacAVF" +#define AVMEDIA_MACAVF_FRAMEGRABBER_SERVICENAME "com.sun.star.media.FrameGrabber_MacAVF" + + +// MacAVObserver handles the notifications used in the AVFoundation framework + +namespace avmedia::macavf { class MacAVObserverHandler; } + +typedef std::unordered_map<NSObject*,avmedia::macavf::MacAVObserverHandler*> HandlersForObject; + +@interface MacAVObserverObject : NSObject +{ + HandlersForObject maHandlersForObject; +} +- (void)observeValueForKeyPath:(NSString*)pKeyPath ofObject:(id)pObject change:(NSDictionary*)pChangeDict context:(void*)pContext; +- (void)onNotification:(NSNotification*)pNotification; +@end + +namespace avmedia::macavf { + +class MacAVObserverHandler +{ +private: + static MacAVObserverObject* mpMacAVObserverObject; +public: + virtual ~MacAVObserverHandler() {} + static MacAVObserverObject* getObserver(); + virtual bool handleObservation( NSString* pKeyPath ) = 0; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/manager.hxx b/avmedia/source/macavf/manager.hxx new file mode 100644 index 000000000..9072b3800 --- /dev/null +++ b/avmedia/source/macavf/manager.hxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "macavfcommon.hxx" +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/media/XManager.hpp> + + +namespace avmedia::macavf { + +class Manager : public ::cppu::WeakImplHelper< css::media::XManager, + css::lang::XServiceInfo > +{ +public: + + Manager(); + virtual ~Manager() override; + + // XManager + virtual css::uno::Reference< css::media::XPlayer > SAL_CALL createPlayer( const OUString& aURL ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; + +} // namespace avmedia::macavf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/manager.mm b/avmedia/source/macavf/manager.mm new file mode 100644 index 000000000..d240bb27a --- /dev/null +++ b/avmedia/source/macavf/manager.mm @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "manager.hxx" +#include "player.hxx" +#include <tools/urlobj.hxx> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> + +using namespace ::com::sun::star; + +namespace avmedia::macavf { + +Manager::Manager() +{ +} + + +Manager::~Manager() +{} + + +uno::Reference< media::XPlayer > SAL_CALL Manager::createPlayer( const OUString& rURL ) +{ + rtl::Reference<Player> xPlayer( new Player() ); + INetURLObject aURL( rURL ); + + if( !xPlayer->create( aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ) ) ) + return {}; + + return uno::Reference<media::XPlayer>(xPlayer); +} + + +OUString SAL_CALL Manager::getImplementationName( ) +{ + return "com.sun.star.comp.avmedia.Manager_MacAVF"; +} + + +sal_Bool SAL_CALL Manager::supportsService( const OUString& ServiceName ) +{ + return ServiceName == "com.sun.star.media.Manager_MacAVF"; +} + + +uno::Sequence< OUString > SAL_CALL Manager::getSupportedServiceNames( ) +{ + return { "com.sun.star.media.Manager_MacAVF" }; +} + +} // namespace avmedia::macavf + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_avmedia_Manager_MacAVF_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ::avmedia::macavf::Manager()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/player.hxx b/avmedia/source/macavf/player.hxx new file mode 100644 index 000000000..3dc41b347 --- /dev/null +++ b/avmedia/source/macavf/player.hxx @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <osl/conditn.h> +#include "macavfcommon.hxx" +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/media/XPlayer.hpp> + +namespace avmedia::macavf { + +class Player +: public MacAVObserverHandler +, public ::cppu::WeakImplHelper< css::media::XPlayer, + css::lang::XServiceInfo > +{ +public: + explicit Player(); + virtual ~Player() override; + + bool create( const OUString& rURL ); + bool create( AVAsset* ); + + // XPlayer + virtual void SAL_CALL start() override; + virtual void SAL_CALL stop() override; + virtual sal_Bool SAL_CALL isPlaying() override; + virtual double SAL_CALL getDuration() override; + virtual void SAL_CALL setMediaTime( double fTime ) override; + virtual double SAL_CALL getMediaTime() override; + /// @throws css::uno::RuntimeException + virtual void setStopTime( double fTime ); + /// @throws css::uno::RuntimeException + virtual double getStopTime(); + virtual void SAL_CALL setPlaybackLoop( sal_Bool bSet ) override; + virtual sal_Bool SAL_CALL isPlaybackLoop() override; + virtual void SAL_CALL setMute( sal_Bool bSet ) override; + virtual sal_Bool SAL_CALL isMute() override; + virtual void SAL_CALL setVolumeDB( sal_Int16 nVolumeDB ) override; + virtual sal_Int16 SAL_CALL getVolumeDB() override; + virtual css::awt::Size SAL_CALL getPreferredPlayerWindowSize( ) override; + virtual css::uno::Reference< css::media::XPlayerWindow > SAL_CALL createPlayerWindow( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + virtual css::uno::Reference< css::media::XFrameGrabber > SAL_CALL createFrameGrabber( ) override; + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + AVPlayer* getAVPlayer() const { return mpPlayer; } + virtual bool handleObservation( NSString* pKeyPath ) override; + +private: + + AVPlayer* mpPlayer; + + float mfUnmutedVolume; + double mfStopTime; + + bool mbMuted; + bool mbLooping; +}; + +} // namespace avmedia::macavf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/player.mm b/avmedia/source/macavf/player.mm new file mode 100644 index 000000000..401cba74c --- /dev/null +++ b/avmedia/source/macavf/player.mm @@ -0,0 +1,359 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "player.hxx" +#include "framegrabber.hxx" +#include "window.hxx" +#include <rtl/ref.hxx> + +#include <cmath> // for log10() + +using namespace ::com::sun::star; + +@implementation MacAVObserverObject + +- (void)observeValueForKeyPath:(NSString*)pKeyPath ofObject:(id)pObject change:(NSDictionary*)pChangeDict context:(void*)pContext +{ + (void) pObject; + (void) pChangeDict; + avmedia::macavf::MacAVObserverHandler* pHandler = static_cast<avmedia::macavf::MacAVObserverHandler*>(pContext); + pHandler->handleObservation( pKeyPath ); +} + +- (void)onNotification:(NSNotification*)pNotification +{ + NSString* pNoteName = [pNotification name]; + HandlersForObject::iterator it = maHandlersForObject.find( [pNotification object]); + if( it != maHandlersForObject.end() ) + (*it).second->handleObservation( pNoteName ); +} + +- (void)setHandlerForObject:(NSObject*)pObject handler:(avmedia::macavf::MacAVObserverHandler*)pHandler +{ + maHandlersForObject[ pObject] = pHandler; +} + +- (void)removeHandlerForObject:(NSObject*)pObject +{ + maHandlersForObject.erase( pObject); +} + +@end + + +namespace avmedia::macavf { + +MacAVObserverObject* MacAVObserverHandler::mpMacAVObserverObject = nullptr; + +MacAVObserverObject* MacAVObserverHandler::getObserver() +{ + if( !mpMacAVObserverObject) + { + mpMacAVObserverObject = [MacAVObserverObject alloc]; + [mpMacAVObserverObject retain]; + } + return mpMacAVObserverObject; +} + + +Player::Player() +: mpPlayer( nullptr ) +, mfUnmutedVolume( 0 ) +, mfStopTime( DBL_MAX ) +, mbMuted( false ) +, mbLooping( false ) +{} + + +Player::~Player() +{ + if( !mpPlayer ) + return; + // remove the observers + [mpPlayer removeObserver:getObserver() forKeyPath:@"currentItem.status"]; + AVPlayerItem* pOldPlayerItem = [mpPlayer currentItem]; + [[NSNotificationCenter defaultCenter] removeObserver:getObserver() + name:AVPlayerItemDidPlayToEndTimeNotification + object:pOldPlayerItem]; + [getObserver() removeHandlerForObject:pOldPlayerItem]; + // release the AVPlayer + CFRelease( mpPlayer ); +} + + +bool Player::handleObservation( NSString* pKeyPath ) +{ + if( [pKeyPath isEqualToString:AVPlayerItemDidPlayToEndTimeNotification]) + { + if( mbLooping ) + setMediaTime( 0.0); + } + return true; +} + + +bool Player::create( const OUString& rURL ) +{ + // get the media asset + NSString* aNSStr = [NSString stringWithCharacters:reinterpret_cast<unichar const *>(rURL.getStr()) length:rURL.getLength()]; + SAL_WNODEPRECATED_DECLARATIONS_PUSH + //TODO: 10.11 stringByAddingPercentEscapesUsingEncoding + NSURL* aNSURL = [NSURL URLWithString: [aNSStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + SAL_WNODEPRECATED_DECLARATIONS_POP + // get the matching AVPlayerItem + AVPlayerItem* pPlayerItem = [AVPlayerItem playerItemWithURL:aNSURL]; + + // create or update the AVPlayer with the new AVPlayerItem + if( !mpPlayer ) + { + mpPlayer = [AVPlayer playerWithPlayerItem:pPlayerItem]; + CFRetain( mpPlayer ); + [mpPlayer setActionAtItemEnd:AVPlayerActionAtItemEndNone]; + } + else + { + // remove the obsoleted observers + AVPlayerItem* pOldPlayerItem = [mpPlayer currentItem]; + [mpPlayer removeObserver:getObserver() forKeyPath:@"currentItem.status"]; + [getObserver() removeHandlerForObject:pOldPlayerItem]; + [[NSNotificationCenter defaultCenter] removeObserver:getObserver() + name:AVPlayerItemDidPlayToEndTimeNotification + object:pOldPlayerItem]; + // replace the playeritem + [mpPlayer replaceCurrentItemWithPlayerItem:pPlayerItem]; + } + + // observe the status of the current player item + [mpPlayer addObserver:getObserver() forKeyPath:@"currentItem.status" options:0 context:this]; + + // observe playback-end needed for playback looping + [[NSNotificationCenter defaultCenter] addObserver:getObserver() + selector:@selector(onNotification:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:pPlayerItem]; + [getObserver() setHandlerForObject:pPlayerItem handler:this]; + + return true; +} + + +void SAL_CALL Player::start() +{ + if( !mpPlayer ) + return; + + [mpPlayer play]; + // else // TODO: delay until it becomes ready +} + + +void SAL_CALL Player::stop() +{ + if( !mpPlayer ) + return; + const bool bPlaying = isPlaying(); + if( bPlaying ) + [mpPlayer pause]; +} + + +sal_Bool SAL_CALL Player::isPlaying() +{ + if( !mpPlayer ) + return false; + const float fRate = [mpPlayer rate]; + return (fRate != 0.0); +} + + +double SAL_CALL Player::getDuration() +{ + // slideshow checks for non-zero duration, so cheat here + double duration = 0.01; + + if( mpPlayer ) + { + AVPlayerItem* pItem = [mpPlayer currentItem]; + if( [pItem status] == AVPlayerItemStatusReadyToPlay ) + duration = CMTimeGetSeconds( [pItem duration] ); + else // fall back to AVAsset's best guess + duration = CMTimeGetSeconds( [[pItem asset] duration] ); + } + + return duration; +} + + +void SAL_CALL Player::setMediaTime( double fTime ) +{ + if( mpPlayer ) + [mpPlayer seekToTime: CMTimeMakeWithSeconds(fTime,1000) ]; +} + + +double SAL_CALL Player::getMediaTime() +{ + if( !mpPlayer ) + return 0.0; + + const double position = CMTimeGetSeconds( [mpPlayer currentTime] ); + if( position >= mfStopTime ) + if( isPlaying() ) + stop(); + + return position; +} + + +void Player::setStopTime( double fTime ) +{ + mfStopTime = fTime; +} + + +double Player::getStopTime() +{ + return mfStopTime; +} + + +void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet ) +{ + mbLooping = bSet; +} + + +sal_Bool SAL_CALL Player::isPlaybackLoop() +{ + return mbLooping; +} + + +void SAL_CALL Player::setMute( sal_Bool bSet ) +{ + if( !mpPlayer ) + return; + + mbMuted = bSet; + [mpPlayer setMuted:mbMuted]; +} + + +sal_Bool SAL_CALL Player::isMute() +{ + return mbMuted; +} + + +void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB ) +{ + // -40dB <-> AVPlayer volume 0.0 + // 0dB <-> AVPlayer volume 1.0 + mfUnmutedVolume = (nVolumeDB <= -40) ? 0.0 : pow( 10.0, nVolumeDB / 20.0 ); + + // change volume + if( !mbMuted && mpPlayer ) + [mpPlayer setVolume:mfUnmutedVolume]; +} + + +sal_Int16 SAL_CALL Player::getVolumeDB() +{ + if( !mpPlayer ) + return 0; + + // get the actual volume + const float fVolume = [mpPlayer volume]; + + // convert into Decibel value + // -40dB <-> AVPlayer volume 0.0 + // 0dB <-> AVPlayer volume 1.0 + const int nVolumeDB = (fVolume <= 0) ? -40 : lrint( 20.0*log10(fVolume)); + + return static_cast<sal_Int16>(nVolumeDB); +} + + +awt::Size SAL_CALL Player::getPreferredPlayerWindowSize() +{ + awt::Size aSize( 0, 0 ); // default size + + AVAsset* pMovie = [[mpPlayer currentItem] asset]; + NSArray* pVideoTracks = [pMovie tracksWithMediaType:AVMediaTypeVideo]; + if ([pVideoTracks count] > 0) + { + AVAssetTrack* pFirstVideoTrack = static_cast<AVAssetTrack*>([pVideoTracks objectAtIndex:0]); + const CGSize aPrefSize = [pFirstVideoTrack naturalSize]; + aSize = awt::Size( aPrefSize.width, aPrefSize.height ); + } + + return aSize; +} + + +uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& aArguments ) +{ + // get the preferred window size + const awt::Size aSize( getPreferredPlayerWindowSize() ); + + // get the parent view + sal_IntPtr nNSViewPtr = 0; + aArguments[0] >>= nNSViewPtr; + NSView* pParentView = reinterpret_cast<NSView*>(nNSViewPtr); + + // check the window parameters + if( (aSize.Width <= 0) || (aSize.Height <= 0) || (pParentView == nullptr) ) + return {}; + + // create the window + return new ::avmedia::macavf::Window( *this, pParentView ); +} + + +uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber() +{ + rtl::Reference<FrameGrabber> pGrabber = new FrameGrabber(); + AVAsset* pMovie = [[mpPlayer currentItem] asset]; + if( !pGrabber->create( pMovie ) ) + return {}; + + return pGrabber; +} + + +OUString SAL_CALL Player::getImplementationName( ) +{ + return AVMEDIA_MACAVF_PLAYER_IMPLEMENTATIONNAME; +} + + +sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName ) +{ + return ServiceName == AVMEDIA_MACAVF_PLAYER_SERVICENAME; +} + + +uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames( ) +{ + return { AVMEDIA_MACAVF_PLAYER_SERVICENAME }; +} + +} // namespace avmedia::macavf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/window.hxx b/avmedia/source/macavf/window.hxx new file mode 100644 index 000000000..14fdaf389 --- /dev/null +++ b/avmedia/source/macavf/window.hxx @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "macavfcommon.hxx" +#include <cppuhelper/implbase.hxx> +#include <comphelper/multicontainer2.hxx> + +#include <com/sun/star/media/XPlayerWindow.hpp> + + +@interface MyMediaView : NSView +@property (nonatomic, readonly, strong) AVPlayer* player; +@property (nonatomic, readonly, strong) AVPlayerLayer* playerLayer; +@property (nonatomic, retain) NSURL* videoURL; +- (void) play; +@end + +namespace avmedia::macavf { + +class Player; + +class Window +: public MacAVObserverHandler +, public ::cppu::WeakImplHelper< css::media::XPlayerWindow, + css::lang::XServiceInfo > +{ +public: + + Window( Player& i_rPlayer, + NSView* i_pParentView + ); + virtual ~Window() override; + + void processGraphEvent(); + void updatePointer(); + + // XPlayerWindow + virtual void SAL_CALL update( ) override; + virtual sal_Bool SAL_CALL setZoomLevel( css::media::ZoomLevel ZoomLevel ) override; + virtual css::media::ZoomLevel SAL_CALL getZoomLevel( ) override; + virtual void SAL_CALL setPointerType( sal_Int32 nPointerType ) override; + + // XWindow + virtual void SAL_CALL setPosSize( sal_Int32 X, sal_Int32 Y, sal_Int32 Width, sal_Int32 Height, sal_Int16 Flags ) override; + virtual css::awt::Rectangle SAL_CALL getPosSize( ) override; + virtual void SAL_CALL setVisible( sal_Bool Visible ) override; + virtual void SAL_CALL setEnable( sal_Bool Enable ) override; + virtual void SAL_CALL setFocus( ) override; + virtual void SAL_CALL addWindowListener( const css::uno::Reference< css::awt::XWindowListener >& xListener ) override; + virtual void SAL_CALL removeWindowListener( const css::uno::Reference< css::awt::XWindowListener >& xListener ) override; + virtual void SAL_CALL addFocusListener( const css::uno::Reference< css::awt::XFocusListener >& xListener ) override; + virtual void SAL_CALL removeFocusListener( const css::uno::Reference< css::awt::XFocusListener >& xListener ) override; + virtual void SAL_CALL addKeyListener( const css::uno::Reference< css::awt::XKeyListener >& xListener ) override; + virtual void SAL_CALL removeKeyListener( const css::uno::Reference< css::awt::XKeyListener >& xListener ) override; + virtual void SAL_CALL addMouseListener( const css::uno::Reference< css::awt::XMouseListener >& xListener ) override; + virtual void SAL_CALL removeMouseListener( const css::uno::Reference< css::awt::XMouseListener >& xListener ) override; + virtual void SAL_CALL addMouseMotionListener( const css::uno::Reference< css::awt::XMouseMotionListener >& xListener ) override; + virtual void SAL_CALL removeMouseMotionListener( const css::uno::Reference< css::awt::XMouseMotionListener >& xListener ) override; + virtual void SAL_CALL addPaintListener( const css::uno::Reference< css::awt::XPaintListener >& xListener ) override; + virtual void SAL_CALL removePaintListener( const css::uno::Reference< css::awt::XPaintListener >& xListener ) override; + + // XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + virtual bool handleObservation( NSString* pKeyPath ) override; + +private: + + ::osl::Mutex maMutex; + comphelper::OMultiTypeInterfaceContainerHelper2 maListeners; + css::media::ZoomLevel meZoomLevel; + Player& mrPlayer; + int mnPointerType; + + NSView* mpView; // parent-view == movie-view + AVPlayerLayer* mpPlayerLayer; + + void ImplLayoutVideoWindow(); +}; + +} // namespace avmedia::macavf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/macavf/window.mm b/avmedia/source/macavf/window.mm new file mode 100644 index 000000000..fdb2e5065 --- /dev/null +++ b/avmedia/source/macavf/window.mm @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/awt/SystemPointer.hpp> +#include <com/sun/star/awt/PosSize.hpp> + +#include "window.hxx" +#include "player.hxx" + +using namespace ::com::sun::star; + + +namespace avmedia::macavf { + +Window::Window( Player& i_rPlayer, NSView* i_pParentView ) +: maListeners( maMutex ) +, meZoomLevel( media::ZoomLevel_NOT_AVAILABLE ) +, mrPlayer( i_rPlayer ) +, mnPointerType( awt::SystemPointer::ARROW ) +, mpView( i_pParentView ) +, mpPlayerLayer( nullptr ) +{ + if( !mpView ) // sanity check + return; + + // check the media asset for video content + AVPlayer* pAVPlayer = mrPlayer.getAVPlayer(); + AVAsset* pMovie = [[pAVPlayer currentItem] asset]; + const int nVideoCount = [pMovie tracksWithMediaType:AVMediaTypeVideo].count; + if( nVideoCount <= 0 ) + return; + + // setup the AVPlayerLayer + [pAVPlayer retain]; + [pAVPlayer pause]; + mpPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:pAVPlayer]; + [mpPlayerLayer retain]; + NSRect viewFrame = [mpView frame]; + [mpPlayerLayer setFrame:CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.width, viewFrame.size.height)]; + [mpPlayerLayer setHidden:YES]; + [mpPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; + [mpPlayerLayer addObserver:getObserver() forKeyPath:@"readyForDisplay" options:0 context:this]; + + // setup the target view + [mpView setWantsLayer:YES]; + [mpView.layer addSublayer:mpPlayerLayer]; +} + + +Window::~Window() +{ + [mpPlayerLayer removeObserver:getObserver() forKeyPath:@"readyForDisplay"]; + [mpPlayerLayer release]; +} + + +bool Window::handleObservation( NSString* /*pKeyPath*/ ) +{ + const bool bReadyForDisplay = [mpPlayerLayer isReadyForDisplay]; + [mpPlayerLayer setHidden:!bReadyForDisplay]; + return true; +} + +// XPlayerWindow + +void SAL_CALL Window::update() +{} + + +sal_Bool SAL_CALL Window::setZoomLevel( media::ZoomLevel /* eZoomLevel */ ) +{ + return false; +} + + +media::ZoomLevel SAL_CALL Window::getZoomLevel( ) +{ + return meZoomLevel; +} + + +void SAL_CALL Window::setPointerType( sal_Int32 nPointerType ) +{ + mnPointerType = nPointerType; +} + +// XWindow + +void SAL_CALL Window::setPosSize( sal_Int32 /*X*/, sal_Int32 /*Y*/, sal_Int32 Width, sal_Int32 Height, sal_Int16 /* Flags */ ) +{ + if( !mpView ) + return; + NSRect aRect = [mpView frame]; + // NOTE: if( (Flags & awt::PosSize::WIDTH) ) + aRect.size.width = Width; + // NOTE: if( (Flags & awt::PosSize::HEIGHT) ) + aRect.size.height = Height; + + [mpView setFrameSize: aRect.size]; + NSRect viewFrame = [mpView frame]; + [mpPlayerLayer setFrame:CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.width, viewFrame.size.height)]; +} + + +awt::Rectangle SAL_CALL Window::getPosSize() +{ + awt::Rectangle aRet; + + NSRect aRect = [mpView frame]; + aRet.X = aRet.Y = 0; + aRet.Width = aRect.size.width; + aRet.Height = aRect.size.height; + + return aRet; +} + + +void SAL_CALL Window::setVisible( sal_Bool /*bVisible*/ ) +{ +} + + +void SAL_CALL Window::setEnable( sal_Bool /*bEnable*/ ) +{ +} + + +void SAL_CALL Window::setFocus() +{ +} + + +void SAL_CALL Window::addWindowListener( const uno::Reference< awt::XWindowListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::removeWindowListener( const uno::Reference< awt::XWindowListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::addFocusListener( const uno::Reference< awt::XFocusListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::removeFocusListener( const uno::Reference< awt::XFocusListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::addKeyListener( const uno::Reference< awt::XKeyListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::removeKeyListener( const uno::Reference< awt::XKeyListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::addMouseListener( const uno::Reference< awt::XMouseListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::removeMouseListener( const uno::Reference< awt::XMouseListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::addMouseMotionListener( const uno::Reference< awt::XMouseMotionListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::removeMouseMotionListener( const uno::Reference< awt::XMouseMotionListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::addPaintListener( const uno::Reference< awt::XPaintListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::removePaintListener( const uno::Reference< awt::XPaintListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +// XComponent + +void SAL_CALL Window::dispose( ) +{ +} + + +void SAL_CALL Window::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void SAL_CALL Window::removeEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +// XServiceInfo + +OUString SAL_CALL Window::getImplementationName( ) +{ + return AVMEDIA_MACAVF_WINDOW_IMPLEMENTATIONNAME; +} + + +sal_Bool SAL_CALL Window::supportsService( const OUString& ServiceName ) +{ + return ServiceName == AVMEDIA_MACAVF_WINDOW_SERVICENAME; +} + + +uno::Sequence< OUString > SAL_CALL Window::getSupportedServiceNames( ) +{ + return { AVMEDIA_MACAVF_WINDOW_SERVICENAME }; +} + +} // namespace avmedia::macavf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/viewer/mediaevent_impl.cxx b/avmedia/source/viewer/mediaevent_impl.cxx new file mode 100644 index 000000000..d6a2c082c --- /dev/null +++ b/avmedia/source/viewer/mediaevent_impl.cxx @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "mediaevent_impl.hxx" +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include <vcl/event.hxx> +#include <vcl/window.hxx> + +using namespace ::com::sun::star; + +namespace avmedia::priv { + +MediaEventListenersImpl::MediaEventListenersImpl( vcl::Window& rEventWindow ) : + mpNotifyWindow( &rEventWindow ) +{ +} + + +MediaEventListenersImpl::~MediaEventListenersImpl() +{ +} + + +void MediaEventListenersImpl::cleanUp() +{ + Application::RemoveMouseAndKeyEvents( mpNotifyWindow.get() ); + mpNotifyWindow = nullptr; +} + + +void SAL_CALL MediaEventListenersImpl::disposing( const css::lang::EventObject& ) +{ +} + + +void SAL_CALL MediaEventListenersImpl::keyPressed( const css::awt::KeyEvent& e ) +{ + const std::unique_lock aGuard( maMutex ); + + if( mpNotifyWindow ) + { + vcl::KeyCode aVCLKeyCode( e.KeyCode, + ( ( e.Modifiers & 1 ) ? KEY_SHIFT : 0 ) | + ( ( e.Modifiers & 2 ) ? KEY_MOD1 : 0 ) | + ( ( e.Modifiers & 4 ) ? KEY_MOD2 : 0 ) ); + KeyEvent aVCLKeyEvt( e.KeyChar, aVCLKeyCode ); + + Application::PostKeyEvent( VclEventId::WindowKeyInput, mpNotifyWindow.get(), &aVCLKeyEvt ); + } +} + + +void SAL_CALL MediaEventListenersImpl::keyReleased( const css::awt::KeyEvent& e ) +{ + const std::unique_lock aGuard( maMutex ); + + if( mpNotifyWindow ) + { + vcl::KeyCode aVCLKeyCode( e.KeyCode, + ( ( e.Modifiers & 1 ) ? KEY_SHIFT : 0 ) | + ( ( e.Modifiers & 2 ) ? KEY_MOD1 : 0 ) | + ( ( e.Modifiers & 4 ) ? KEY_MOD2 : 0 ) ); + KeyEvent aVCLKeyEvt( e.KeyChar, aVCLKeyCode ); + Application::PostKeyEvent( VclEventId::WindowKeyUp, mpNotifyWindow.get(), &aVCLKeyEvt ); + } +} + + +void SAL_CALL MediaEventListenersImpl::mousePressed( const css::awt::MouseEvent& e ) +{ + const std::unique_lock aGuard( maMutex ); + + if( mpNotifyWindow ) + { + MouseEvent aVCLMouseEvt( Point( e.X, e.Y ), + sal::static_int_cast< sal_uInt16 >(e.ClickCount), + MouseEventModifiers::NONE, + ( ( e.Buttons & 1 ) ? MOUSE_LEFT : 0 ) | + ( ( e.Buttons & 2 ) ? MOUSE_RIGHT : 0 ) | + ( ( e.Buttons & 4 ) ? MOUSE_MIDDLE : 0 ), + e.Modifiers ); + Application::PostMouseEvent( VclEventId::WindowMouseButtonDown, mpNotifyWindow.get(), &aVCLMouseEvt ); + } +} + + +void SAL_CALL MediaEventListenersImpl::mouseReleased( const css::awt::MouseEvent& e ) +{ + const std::unique_lock aGuard( maMutex ); + const SolarMutexGuard aAppGuard; + + if( mpNotifyWindow ) + { + MouseEvent aVCLMouseEvt( Point( e.X, e.Y ), + sal::static_int_cast< sal_uInt16 >(e.ClickCount), + MouseEventModifiers::NONE, + ( ( e.Buttons & 1 ) ? MOUSE_LEFT : 0 ) | + ( ( e.Buttons & 2 ) ? MOUSE_RIGHT : 0 ) | + ( ( e.Buttons & 4 ) ? MOUSE_MIDDLE : 0 ), + e.Modifiers ); + Application::PostMouseEvent( VclEventId::WindowMouseButtonUp, mpNotifyWindow.get(), &aVCLMouseEvt ); + } +} + + +void SAL_CALL MediaEventListenersImpl::mouseEntered( const css::awt::MouseEvent& ) +{ +} + + +void SAL_CALL MediaEventListenersImpl::mouseExited( const css::awt::MouseEvent& ) +{ +} + + +void SAL_CALL MediaEventListenersImpl::mouseDragged( const css::awt::MouseEvent& e ) +{ + const std::unique_lock aGuard( maMutex ); + + if( mpNotifyWindow ) + { + MouseEvent aVCLMouseEvt( Point( e.X, e.Y ), 0, MouseEventModifiers::NONE, e.Buttons, e.Modifiers ); + Application::PostMouseEvent( VclEventId::WindowMouseMove, mpNotifyWindow.get(), &aVCLMouseEvt ); + } +} + + +void SAL_CALL MediaEventListenersImpl::mouseMoved( const css::awt::MouseEvent& e ) +{ + const std::unique_lock aGuard( maMutex ); + + if( mpNotifyWindow ) + { + MouseEvent aVCLMouseEvt( Point( e.X, e.Y ), 0, MouseEventModifiers::NONE, e.Buttons, e.Modifiers ); + Application::PostMouseEvent( VclEventId::WindowMouseMove, mpNotifyWindow.get(), &aVCLMouseEvt ); + } +} + + +void SAL_CALL MediaEventListenersImpl::focusGained( const css::awt::FocusEvent& ) +{ +} + + +void SAL_CALL MediaEventListenersImpl::focusLost( const css::awt::FocusEvent& ) +{ +} + + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/viewer/mediaevent_impl.hxx b/avmedia/source/viewer/mediaevent_impl.hxx new file mode 100644 index 000000000..682eff1d7 --- /dev/null +++ b/avmedia/source/viewer/mediaevent_impl.hxx @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <avmedia/mediawindow.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/awt/XKeyListener.hpp> +#include <com/sun/star/awt/XMouseListener.hpp> +#include <com/sun/star/awt/XMouseMotionListener.hpp> +#include <com/sun/star/awt/XFocusListener.hpp> +#include <vcl/vclptr.hxx> + +#include <mutex> + +namespace avmedia::priv + { + + // - MediaEventListenersImpl - + + class MediaEventListenersImpl : public ::cppu::WeakImplHelper< css::awt::XKeyListener, + css::awt::XMouseListener, + css::awt::XMouseMotionListener, + css::awt::XFocusListener > + { + public: + + explicit MediaEventListenersImpl( vcl::Window& rNotifyWindow ); + virtual ~MediaEventListenersImpl() override; + + void cleanUp(); + + protected: + + // XKeyListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + virtual void SAL_CALL keyPressed( const css::awt::KeyEvent& e ) override; + virtual void SAL_CALL keyReleased( const css::awt::KeyEvent& e ) override; + + // XMouseListener + virtual void SAL_CALL mousePressed( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseReleased( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseEntered( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseExited( const css::awt::MouseEvent& e ) override; + + // XMouseMotionListener + virtual void SAL_CALL mouseDragged( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseMoved( const css::awt::MouseEvent& e ) override; + + // XFocusListener + virtual void SAL_CALL focusGained( const css::awt::FocusEvent& e ) override; + virtual void SAL_CALL focusLost( const css::awt::FocusEvent& e ) override; + + private: + + VclPtr<vcl::Window> mpNotifyWindow; + mutable std::mutex maMutex; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/viewer/mediawindow.cxx b/avmedia/source/viewer/mediawindow.cxx new file mode 100644 index 000000000..df8110f54 --- /dev/null +++ b/avmedia/source/viewer/mediawindow.cxx @@ -0,0 +1,512 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <avmedia/mediawindow.hxx> +#include "mediawindow_impl.hxx" +#include <mediamisc.hxx> +#include <bitmaps.hlst> +#include <strings.hrc> +#include <tools/urlobj.hxx> +#include <utility> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <sfx2/filedlghelper.hxx> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/frame/XDispatchHelper.hpp> +#include <com/sun/star/media/XPlayer.hpp> +#include <com/sun/star/media/XPlayerNotifier.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <memory> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#define AVMEDIA_FRAMEGRABBER_DEFAULTFRAME_MEDIATIME 3.0 + +using namespace ::com::sun::star; + +namespace avmedia { + +MediaWindow::MediaWindow( vcl::Window* parent, bool bInternalMediaControl ) : + mpImpl( VclPtr<priv::MediaWindowImpl>::Create( parent, this, bInternalMediaControl ) ) +{ + mpImpl->Show(); +} + + +MediaWindow::~MediaWindow() +{ + mpImpl.disposeAndClear(); +} + + +void MediaWindow::setURL( const OUString& rURL, const OUString& rReferer ) +{ + mpImpl->setURL( rURL, OUString(), rReferer ); +} + + +const OUString& MediaWindow::getURL() const +{ + return mpImpl->getURL(); +} + + +bool MediaWindow::isValid() const +{ + return mpImpl->isValid(); +} + + +void MediaWindow::MouseMove( const MouseEvent& ) +{ +} + + +void MediaWindow::MouseButtonDown( const MouseEvent& ) +{ +} + + +void MediaWindow::MouseButtonUp( const MouseEvent& ) +{ +} + + +void MediaWindow::KeyInput( const KeyEvent& ) +{ +} + + +void MediaWindow::KeyUp( const KeyEvent& ) +{ +} + +void MediaWindow::Command( const CommandEvent& ) +{ +} + + +sal_Int8 MediaWindow::AcceptDrop( const AcceptDropEvent& ) +{ + return 0; +} + + +sal_Int8 MediaWindow::ExecuteDrop( const ExecuteDropEvent& ) +{ + return 0; +} + + +void MediaWindow::StartDrag( sal_Int8, const Point& ) +{ +} + + +Size MediaWindow::getPreferredSize() const +{ + return mpImpl->getPreferredSize(); +} + + +void MediaWindow::setPosSize( const tools::Rectangle& rNewRect ) +{ + mpImpl->setPosSize( rNewRect ); +} + + +void MediaWindow::setPointer( PointerStyle nPointer ) +{ + mpImpl->setPointer( nPointer ); +} + + +bool MediaWindow::start() +{ + return mpImpl->start(); +} + +void MediaWindow::updateMediaItem( MediaItem& rItem ) const +{ + mpImpl->updateMediaItem( rItem ); +} + +void MediaWindow::executeMediaItem( const MediaItem& rItem ) +{ + mpImpl->executeMediaItem( rItem ); +} + +void MediaWindow::show() +{ + mpImpl->Show(); +} + +void MediaWindow::hide() +{ + mpImpl->Hide(); +} + +bool MediaWindow::isVisible() const +{ + return mpImpl->IsVisible(); +} + +vcl::Window* MediaWindow::getWindow() const +{ + return mpImpl.get(); +} + + +FilterNameVector MediaWindow::getMediaFilters() +{ + return {{"Advanced Audio Coding", "aac"}, + {"AIF Audio", "aif;aiff"}, + {"Advanced Systems Format", "asf;wma;wmv"}, + {"AU Audio", "au"}, + {"AC3 Audio", "ac3"}, + {"AVI", "avi"}, + {"CD Audio", "cda"}, + {"Digital Video", "dv"}, + {"FLAC Audio", "flac"}, + {"Flash Video", "flv"}, + {"Matroska Media", "mkv"}, + {"MIDI Audio", "mid;midi"}, + {"MPEG Audio", "mp2;mp3;mpa;m4a"}, + {"MPEG Video", "mpg;mpeg;mpv;mp4;m4v"}, + {"Ogg Audio", "ogg;oga;opus"}, + {"Ogg Video", "ogv;ogx"}, + {"Real Audio", "ra"}, + {"Real Media", "rm"}, + {"RMI MIDI Audio", "rmi"}, + {"SND (SouND) Audio", "snd"}, + {"Quicktime Video", "mov"}, + {"Vivo Video", "viv"}, + {"WAVE Audio", "wav"}, + {"WebM Video", "webm"}, + {"Windows Media Audio", "wma"}, + {"Windows Media Video", "wmv"}}; +} + + +bool MediaWindow::executeMediaURLDialog(weld::Window* pParent, OUString& rURL, bool *const o_pbLink) +{ + ::sfx2::FileDialogHelper aDlg(o_pbLink != nullptr + ? ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW + : ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, + FileDialogFlags::NONE, pParent); + static const char aWildcard[] = "*."; + FilterNameVector aFilters = getMediaFilters(); + static const char aSeparator[] = ";"; + OUStringBuffer aAllTypes; + + aDlg.SetContext(sfx2::FileDialogHelper::InsertMedia); + aDlg.SetTitle( AvmResId( o_pbLink != nullptr + ? AVMEDIA_STR_INSERTMEDIA_DLG : AVMEDIA_STR_OPENMEDIA_DLG ) ); + + for( const auto &filter : aFilters ) + { + for( sal_Int32 nIndex = 0; nIndex >= 0; ) + { + if( !aAllTypes.isEmpty() ) + aAllTypes.append(aSeparator); + + aAllTypes.append(OUString::Concat(aWildcard) + o3tl::getToken(filter.second, 0, ';', nIndex )); + } + } + + // add filter for all media types + aDlg.AddFilter( AvmResId( AVMEDIA_STR_ALL_MEDIAFILES ), aAllTypes.makeStringAndClear() ); + + for( const auto &filter : aFilters ) + { + OUStringBuffer aTypes; + + for( sal_Int32 nIndex = 0; nIndex >= 0; ) + { + if( !aTypes.isEmpty() ) + aTypes.append(aSeparator); + + aTypes.append(OUString::Concat(aWildcard) + o3tl::getToken(filter.second, 0, ';', nIndex )); + } + + // add single filters + aDlg.AddFilter( filter.first, aTypes.makeStringAndClear() ); + } + + // add filter for all types + aDlg.AddFilter( AvmResId( AVMEDIA_STR_ALL_FILES ), "*.*" ); + + uno::Reference<ui::dialogs::XFilePicker3> const xFP(aDlg.GetFilePicker()); + uno::Reference<ui::dialogs::XFilePickerControlAccess> const xCtrlAcc(xFP, + uno::UNO_QUERY_THROW); + if (o_pbLink != nullptr) + { + // for video link should be the default + xCtrlAcc->setValue( + ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK, 0, + uno::Any(true) ); + // disabled for now: TODO: preview? + xCtrlAcc->enableControl( + ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_PREVIEW, + false); + } + + if( aDlg.Execute() == ERRCODE_NONE ) + { + const INetURLObject aURL( aDlg.GetPath() ); + rURL = aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ); + + if (o_pbLink != nullptr) + { + uno::Any const any = xCtrlAcc->getValue( + ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_LINK, 0); + if (!(any >>= *o_pbLink)) + { + SAL_WARN("avmedia", "invalid link property"); + *o_pbLink = true; + } + } + } + else if( !rURL.isEmpty() ) + rURL.clear(); + + return !rURL.isEmpty(); +} + +void MediaWindow::executeFormatErrorBox(weld::Window* pParent) +{ + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, + VclMessageType::Warning, VclButtonsType::Ok, AvmResId(AVMEDIA_STR_ERR_URL))); + xBox->run(); +} + +bool MediaWindow::isMediaURL(std::u16string_view rURL, const OUString& rReferer, bool bDeep, rtl::Reference<PlayerListener> xPreferredPixelSizeListener) +{ + const INetURLObject aURL( rURL ); + + if( aURL.GetProtocol() == INetProtocol::NotValid ) + return false; + + if (bDeep || xPreferredPixelSizeListener) + { + try + { + uno::Reference< media::XPlayer > xPlayer( priv::MediaWindowImpl::createPlayer( + aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ), + rReferer, nullptr ) ); + + if( xPlayer.is() ) + { + if (xPreferredPixelSizeListener) + { + uno::Reference<media::XPlayerNotifier> xPlayerNotifier(xPlayer, css::uno::UNO_QUERY); + if (xPlayerNotifier) + { + // wait until it's possible to query this to get a sensible answer + xPreferredPixelSizeListener->startListening(xPlayerNotifier); + } + else + { + // assume the size is possible to query immediately + xPreferredPixelSizeListener->callPlayerWindowSizeAvailable(xPlayer); + } + } + return true; + } + } + catch( ... ) + { + } + } + else + { + FilterNameVector aFilters = getMediaFilters(); + const OUString aExt( aURL.getExtension() ); + + for( const auto &filter : aFilters ) + { + for( sal_Int32 nIndex = 0; nIndex >= 0; ) + { + if( aExt.equalsIgnoreAsciiCase( o3tl::getToken(filter.second, 0, ';', nIndex ) ) ) + return true; + } + } + } + + return false; +} + +uno::Reference< media::XPlayer > MediaWindow::createPlayer( const OUString& rURL, const OUString& rReferer, const OUString* pMimeType ) +{ + return priv::MediaWindowImpl::createPlayer( rURL, rReferer, pMimeType ); +} + +uno::Reference<graphic::XGraphic> +MediaWindow::grabFrame(const uno::Reference<media::XPlayer>& xPlayer, + const uno::Reference<graphic::XGraphic>& rGraphic) +{ + uno::Reference< graphic::XGraphic > xRet; + std::unique_ptr< Graphic > xGraphic; + + if( xPlayer.is() ) + { + uno::Reference< media::XFrameGrabber > xGrabber( xPlayer->createFrameGrabber() ); + + if( xGrabber.is() ) + { + double fMediaTime = AVMEDIA_FRAMEGRABBER_DEFAULTFRAME_MEDIATIME; + + if( fMediaTime >= xPlayer->getDuration() ) + fMediaTime = ( xPlayer->getDuration() * 0.5 ); + + xRet = xGrabber->grabFrame( fMediaTime ); + } + + if( !xRet.is() ) + { + awt::Size aPrefSize( xPlayer->getPreferredPlayerWindowSize() ); + + if( !aPrefSize.Width && !aPrefSize.Height ) + { + const BitmapEx aBmpEx(AVMEDIA_BMP_AUDIOLOGO); + xGraphic.reset( new Graphic( aBmpEx ) ); + } + } + } + + if (!xRet.is() && !xGraphic) + { + const BitmapEx aBmpEx(AVMEDIA_BMP_EMPTYLOGO); + xGraphic.reset( new Graphic( aBmpEx ) ); + } + + if (xGraphic) + { + if (rGraphic) + xGraphic.reset(new Graphic(rGraphic)); + xRet = xGraphic->GetXGraphic(); + } + + return xRet; +} + +uno::Reference< graphic::XGraphic > MediaWindow::grabFrame(const OUString& rURL, + const OUString& rReferer, + const OUString& sMimeType, + rtl::Reference<PlayerListener> xPreferredPixelSizeListener) +{ + uno::Reference<media::XPlayer> xPlayer(createPlayer(rURL, rReferer, &sMimeType)); + + if (xPreferredPixelSizeListener) + { + uno::Reference<media::XPlayerNotifier> xPlayerNotifier(xPlayer, css::uno::UNO_QUERY); + if (xPlayerNotifier) + { + // set a callback to call when a more sensible result is available, which + // might be called immediately if already available + xPreferredPixelSizeListener->startListening(xPlayerNotifier); + } + else + { + // assume the size is possible to query immediately + xPreferredPixelSizeListener->callPlayerWindowSizeAvailable(xPlayer); + } + + return nullptr; + } + + return grabFrame(xPlayer); +} + +void MediaWindow::dispatchInsertAVMedia(const css::uno::Reference<css::frame::XDispatchProvider>& rDispatchProvider, + const css::awt::Size& rSize, const OUString& rURL, bool bLink) +{ + util::URL aDispatchURL; + aDispatchURL.Complete = ".uno:InsertAVMedia"; + + css::uno::Reference<css::util::XURLTransformer> xTrans(css::util::URLTransformer::create(::comphelper::getProcessComponentContext())); + xTrans->parseStrict(aDispatchURL); + + css::uno::Reference<css::frame::XDispatch> xDispatch = rDispatchProvider->queryDispatch(aDispatchURL, "", 0); + css::uno::Sequence<css::beans::PropertyValue> aArgs(comphelper::InitPropertySequence({ + { "URL", css::uno::Any(rURL) }, + { "Size.Width", uno::Any(rSize.Width)}, + { "Size.Height", uno::Any(rSize.Height)}, + { "IsLink", css::uno::Any(bLink) }, + })); + xDispatch->dispatch(aDispatchURL, aArgs); +} + +PlayerListener::PlayerListener(std::function<void(const css::uno::Reference<css::media::XPlayer>&)> fn) + : PlayerListener_BASE(m_aMutex) + , m_aFn(std::move(fn)) +{ +} + +void PlayerListener::dispose() +{ + stopListening(); + PlayerListener_BASE::dispose(); +} + +void PlayerListener::startListening(const css::uno::Reference<media::XPlayerNotifier>& rNotifier) +{ + osl::MutexGuard aGuard(m_aMutex); + + m_xNotifier = rNotifier; + m_xNotifier->addPlayerListener(this); +} + +void PlayerListener::stopListening() +{ + osl::MutexGuard aGuard(m_aMutex); + if (!m_xNotifier) + return; + m_xNotifier->removePlayerListener(this); + m_xNotifier.clear(); +} + +void SAL_CALL PlayerListener::preferredPlayerWindowSizeAvailable(const css::lang::EventObject&) +{ + osl::MutexGuard aGuard(m_aMutex); + + css::uno::Reference<media::XPlayer> xPlayer(m_xNotifier, css::uno::UNO_QUERY_THROW); + callPlayerWindowSizeAvailable(xPlayer); + + stopListening(); +} + +void SAL_CALL PlayerListener::disposing(const css::lang::EventObject&) +{ +} + +PlayerListener::~PlayerListener() +{ +} + +} // namespace avmedia + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/viewer/mediawindow_impl.cxx b/avmedia/source/viewer/mediawindow_impl.cxx new file mode 100644 index 000000000..a12ed42d4 --- /dev/null +++ b/avmedia/source/viewer/mediawindow_impl.cxx @@ -0,0 +1,674 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <iostream> +#include "mediawindow_impl.hxx" +#include "mediaevent_impl.hxx" +#include <mediamisc.hxx> +#include <bitmaps.hlst> +#include <helpids.h> + +#include <algorithm> +#include <string_view> + +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/scopeguard.hxx> +#include <tools/urlobj.hxx> +#include <unotools/securityoptions.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/svapp.hxx> + +#include <com/sun/star/awt/SystemPointer.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/media/XManager.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +using namespace ::com::sun::star; + +namespace avmedia::priv { + +MediaWindowControl::MediaWindowControl(vcl::Window* pParent) + : MediaControl(pParent, MediaControlStyle::MultiLine) +{ +} + +void MediaWindowControl::update() +{ + MediaItem aItem; + + static_cast< MediaWindowImpl* >( GetParent() )->updateMediaItem( aItem ); + setState(aItem); +} + +void MediaWindowControl::execute(const MediaItem& rItem) +{ + static_cast<MediaWindowImpl*>(GetParent())->executeMediaItem(rItem); +} + +MediaChildWindow::MediaChildWindow(vcl::Window* pParent) + : SystemChildWindow(pParent, WB_CLIPCHILDREN) +{ +} + +void MediaChildWindow::MouseMove( const MouseEvent& rMEvt ) +{ + const MouseEvent aTransformedEvent( GetParent()->ScreenToOutputPixel( OutputToScreenPixel( rMEvt.GetPosPixel() ) ), + rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), rMEvt.GetModifier() ); + + SystemChildWindow::MouseMove( rMEvt ); + GetParent()->MouseMove( aTransformedEvent ); +} + +void MediaChildWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + const MouseEvent aTransformedEvent( GetParent()->ScreenToOutputPixel( OutputToScreenPixel( rMEvt.GetPosPixel() ) ), + rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), rMEvt.GetModifier() ); + + SystemChildWindow::MouseButtonDown( rMEvt ); + GetParent()->MouseButtonDown( aTransformedEvent ); +} + +void MediaChildWindow::MouseButtonUp( const MouseEvent& rMEvt ) +{ + const MouseEvent aTransformedEvent( GetParent()->ScreenToOutputPixel( OutputToScreenPixel( rMEvt.GetPosPixel() ) ), + rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), rMEvt.GetModifier() ); + + SystemChildWindow::MouseButtonUp( rMEvt ); + GetParent()->MouseButtonUp( aTransformedEvent ); +} + +void MediaChildWindow::KeyInput( const KeyEvent& rKEvt ) +{ + SystemChildWindow::KeyInput( rKEvt ); + GetParent()->KeyInput( rKEvt ); +} + +void MediaChildWindow::KeyUp( const KeyEvent& rKEvt ) +{ + SystemChildWindow::KeyUp( rKEvt ); + GetParent()->KeyUp( rKEvt ); +} + +void MediaChildWindow::Command( const CommandEvent& rCEvt ) +{ + const CommandEvent aTransformedEvent( GetParent()->ScreenToOutputPixel( OutputToScreenPixel( rCEvt.GetMousePosPixel() ) ), + rCEvt.GetCommand(), rCEvt.IsMouseEvent(), rCEvt.GetEventData() ); + + SystemChildWindow::Command( rCEvt ); + GetParent()->Command( aTransformedEvent ); +} + +MediaWindowImpl::MediaWindowImpl(vcl::Window* pParent, MediaWindow* pMediaWindow, bool bInternalMediaControl) + : Control(pParent) + , DropTargetHelper(this) + , DragSourceHelper(this) + , mpMediaWindow(pMediaWindow) + , mpMediaWindowControl(bInternalMediaControl ? VclPtr<MediaWindowControl>::Create(this) : nullptr) +{ + if (mpMediaWindowControl) + { + mpMediaWindowControl->SetSizePixel(mpMediaWindowControl->GetOptimalSize()); + mpMediaWindowControl->Show(); + } +} + +MediaWindowImpl::~MediaWindowImpl() +{ + disposeOnce(); +} + +void MediaWindowImpl::dispose() +{ + if (mxEvents.is()) + mxEvents->cleanUp(); + + if (mxPlayerWindow.is()) + { + mxPlayerWindow->removeKeyListener( uno::Reference< awt::XKeyListener >( mxEvents ) ); + mxPlayerWindow->removeMouseListener( uno::Reference< awt::XMouseListener >( mxEvents ) ); + mxPlayerWindow->removeMouseMotionListener( uno::Reference< awt::XMouseMotionListener >( mxEvents ) ); + mxPlayerWindow->dispose(); + mxPlayerWindow.clear(); + } + + uno::Reference< lang::XComponent > xComponent( mxPlayer, uno::UNO_QUERY ); + if (xComponent.is()) // this stops the player + xComponent->dispose(); + + mxPlayer.clear(); + + mpMediaWindow = nullptr; + + mpEmptyBmpEx.reset(); + mpAudioBmpEx.reset(); + mpMediaWindowControl.disposeAndClear(); + mpChildWindow.disposeAndClear(); + + Control::dispose(); +} + +uno::Reference<media::XPlayer> MediaWindowImpl::createPlayer(const OUString& rURL, const OUString& rReferer, const OUString* pMimeType) +{ + uno::Reference<media::XPlayer> xPlayer; + + if( rURL.isEmpty() ) + return xPlayer; + + if (SvtSecurityOptions::isUntrustedReferer(rReferer)) + { + return xPlayer; + } + + if (!pMimeType || *pMimeType == AVMEDIA_MIMETYPE_COMMON) + { + uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext()); + if (Application::GetToolkitName() == "gtk4") + xPlayer = createPlayer(rURL, "com.sun.star.comp.avmedia.Manager_Gtk", xContext); + else + xPlayer = createPlayer(rURL, AVMEDIA_MANAGER_SERVICE_NAME, xContext); + } + + return xPlayer; +} + +uno::Reference< media::XPlayer > MediaWindowImpl::createPlayer( + const OUString& rURL, const OUString& rManagerServName, + const uno::Reference< uno::XComponentContext >& xContext) +{ + uno::Reference< media::XPlayer > xPlayer; + try + { + uno::Reference< media::XManager > xManager ( + xContext->getServiceManager()->createInstanceWithContext(rManagerServName, xContext), + uno::UNO_QUERY ); + if( xManager.is() ) + xPlayer = xManager->createPlayer( rURL ); + else + SAL_INFO( "avmedia", "failed to create media player service " << rManagerServName ); + } catch ( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION( "avmedia", "couldn't create media player " << rManagerServName); + } + return xPlayer; +} + +void MediaWindowImpl::setURL( const OUString& rURL, + OUString const& rTempURL, OUString const& rReferer) +{ + maReferer = rReferer; + if( rURL == getURL() ) + return; + + if( mxPlayer.is() ) + mxPlayer->stop(); + + if( mxPlayerWindow.is() ) + { + mxPlayerWindow->setVisible( false ); + mxPlayerWindow.clear(); + } + + mxPlayer.clear(); + mTempFileURL.clear(); + + if (!rTempURL.isEmpty()) + { + maFileURL = rURL; + mTempFileURL = rTempURL; + } + else + { + INetURLObject aURL( rURL ); + + if (aURL.GetProtocol() != INetProtocol::NotValid) + maFileURL = aURL.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous); + else + maFileURL = rURL; + } + + mxPlayer = createPlayer((!mTempFileURL.isEmpty()) ? mTempFileURL : maFileURL, rReferer, &m_sMimeType ); + onURLChanged(); +} + +const OUString& MediaWindowImpl::getURL() const +{ + return maFileURL; +} + +bool MediaWindowImpl::isValid() const +{ + return mxPlayer.is(); +} + +Size MediaWindowImpl::getPreferredSize() const +{ + Size aRet(480, 360); + + if( mxPlayer.is() ) + { + awt::Size aPrefSize( mxPlayer->getPreferredPlayerWindowSize() ); + + aRet.setWidth( aPrefSize.Width ); + aRet.setHeight( aPrefSize.Height ); + } + + return aRet; +} + +bool MediaWindowImpl::start() +{ + return mxPlayer.is() && ( mxPlayer->start(), true ); +} + +void MediaWindowImpl::updateMediaItem( MediaItem& rItem ) const +{ + if( isPlaying() ) + rItem.setState( MediaState::Play ); + else + rItem.setState( ( getMediaTime() == 0.0 ) ? MediaState::Stop : MediaState::Pause ); + + rItem.setDuration( getDuration() ); + rItem.setTime( getMediaTime() ); + rItem.setLoop( mxPlayer.is() && mxPlayer->isPlaybackLoop() ); + rItem.setMute( mxPlayer.is() && mxPlayer->isMute() ); + rItem.setVolumeDB( mxPlayer.is() ? mxPlayer->getVolumeDB() : 0 ); + rItem.setZoom( mxPlayerWindow.is() ? mxPlayerWindow->getZoomLevel() : media::ZoomLevel_NOT_AVAILABLE ); + rItem.setURL( getURL(), mTempFileURL, maReferer ); +} + +void MediaWindowImpl::executeMediaItem( const MediaItem& rItem ) +{ + mpItem = &rItem; + comphelper::ScopeGuard g([this] { this->mpItem = nullptr; }); + + const AVMediaSetMask nMaskSet = rItem.getMaskSet(); + + // set URL first + if (nMaskSet & AVMediaSetMask::URL) + { + m_sMimeType = rItem.getMimeType(); + setURL(rItem.getURL(), rItem.getTempURL(), rItem.getReferer()); + } + + // set different states next + if (nMaskSet & AVMediaSetMask::TIME) + setMediaTime(std::min(rItem.getTime(), getDuration())); + + if (nMaskSet & AVMediaSetMask::LOOP && mxPlayer.is() ) + mxPlayer->setPlaybackLoop( rItem.isLoop() ); + + if (nMaskSet & AVMediaSetMask::MUTE && mxPlayer.is() ) + mxPlayer->setMute( rItem.isMute() ); + + if (nMaskSet & AVMediaSetMask::VOLUMEDB && mxPlayer.is() ) + mxPlayer->setVolumeDB( rItem.getVolumeDB() ); + + if (nMaskSet & AVMediaSetMask::ZOOM && mxPlayerWindow.is() ) + mxPlayerWindow->setZoomLevel( rItem.getZoom() ); + + // set play state at last + if (!(nMaskSet & AVMediaSetMask::STATE)) + return; + + switch (rItem.getState()) + { + case MediaState::Play: + { + if (!isPlaying()) + start(); + } + break; + + case MediaState::Pause: + { + if (isPlaying()) + stop(); + } + break; + + case MediaState::Stop: + { + if (isPlaying()) + { + setMediaTime( 0.0 ); + stop(); + setMediaTime( 0.0 ); + } + } + break; + } +} + +void MediaWindowImpl::stop() +{ + if( mxPlayer.is() ) + mxPlayer->stop(); +} + +bool MediaWindowImpl::isPlaying() const +{ + return( mxPlayer.is() && mxPlayer->isPlaying() ); +} + +double MediaWindowImpl::getDuration() const +{ + return( mxPlayer.is() ? mxPlayer->getDuration() : 0.0 ); +} + +void MediaWindowImpl::setMediaTime( double fTime ) +{ + if( mxPlayer.is() ) + mxPlayer->setMediaTime( fTime ); +} + +double MediaWindowImpl::getMediaTime() const +{ + return( mxPlayer.is() ? mxPlayer->getMediaTime() : 0.0 ); +} + +void MediaWindowImpl::stopPlayingInternal(bool bStop) +{ + if (isPlaying()) + { + bStop ? mxPlayer->stop() : mxPlayer->start(); + } +} + +void MediaWindowImpl::onURLChanged() +{ + if (m_sMimeType == AVMEDIA_MIMETYPE_COMMON) + { + mpChildWindow.disposeAndClear(); + mpChildWindow.reset(VclPtr<MediaChildWindow>::Create(this)); + } + if (!mpChildWindow) + return; + mpChildWindow->SetHelpId(HID_AVMEDIA_PLAYERWINDOW); + mxEvents = new MediaEventListenersImpl(*mpChildWindow); + + if (mxPlayer.is()) + { + Resize(); + uno::Reference<media::XPlayerWindow> xPlayerWindow; + const Point aPoint; + const Size aSize(mpChildWindow->GetSizePixel()); + + sal_IntPtr nParentWindowHandle(0); + const SystemEnvData* pEnvData = mpChildWindow->GetSystemData(); + // tdf#139609 gtk doesn't need the handle, and fetching it is undesirable + if (!pEnvData || pEnvData->toolkit != SystemEnvData::Toolkit::Gtk) + nParentWindowHandle = mpChildWindow->GetParentWindowHandle(); + uno::Sequence<uno::Any> aArgs{ + uno::Any(nParentWindowHandle), + uno::Any(awt::Rectangle(aPoint.X(), aPoint.Y(), aSize.Width(), aSize.Height())), + uno::Any(reinterpret_cast<sal_IntPtr>(mpChildWindow.get())), + // Media item contains media properties, e.g. cropping. + uno::Any(reinterpret_cast<sal_IntPtr>(mpItem)) + }; + + try + { + xPlayerWindow = mxPlayer->createPlayerWindow( aArgs ); + } + catch( const uno::RuntimeException& ) + { + // happens eg, on MacOSX where Java frames cannot be created from X11 window handles + } + + mxPlayerWindow = xPlayerWindow; + + if( xPlayerWindow.is() ) + { + xPlayerWindow->addKeyListener( uno::Reference< awt::XKeyListener >( mxEvents ) ); + xPlayerWindow->addMouseListener( uno::Reference< awt::XMouseListener >( mxEvents ) ); + xPlayerWindow->addMouseMotionListener( uno::Reference< awt::XMouseMotionListener >( mxEvents ) ); + xPlayerWindow->addFocusListener( uno::Reference< awt::XFocusListener >( mxEvents ) ); + } + } + else + mxPlayerWindow.clear(); + + if( mxPlayerWindow.is() ) + mpChildWindow->Show(); + else + mpChildWindow->Hide(); + + if( mpMediaWindowControl ) + { + MediaItem aItem; + + updateMediaItem( aItem ); + mpMediaWindowControl->setState( aItem ); + } +} + +void MediaWindowImpl::setPosSize(const tools::Rectangle& rRect) +{ + SetPosSizePixel(rRect.TopLeft(), rRect.GetSize()); +} + +void MediaWindowImpl::setPointer(PointerStyle aPointer) +{ + SetPointer(aPointer); + + if (mpChildWindow) + mpChildWindow->SetPointer(aPointer); + + if (!mxPlayerWindow.is()) + return; + + sal_Int32 nPointer; + + switch (aPointer) + { + case PointerStyle::Cross: + nPointer = awt::SystemPointer::CROSS; + break; + case PointerStyle::Hand: + nPointer = awt::SystemPointer::HAND; + break; + case PointerStyle::Move: + nPointer = awt::SystemPointer::MOVE; + break; + case PointerStyle::Wait: + nPointer = awt::SystemPointer::WAIT; + break; + default: + nPointer = awt::SystemPointer::ARROW; + break; + } + + mxPlayerWindow->setPointerType(nPointer); +} + +void MediaWindowImpl::Resize() +{ + const Size aCurSize(GetOutputSizePixel()); + const sal_Int32 nOffset(mpMediaWindowControl ? AVMEDIA_CONTROLOFFSET : 0); + + Size aPlayerWindowSize(aCurSize.Width() - (nOffset << 1), + aCurSize.Height() - (nOffset << 1)); + + if (mpMediaWindowControl) + { + const sal_Int32 nControlHeight = mpMediaWindowControl->GetSizePixel().Height(); + const sal_Int32 nControlY = std::max(aCurSize.Height() - nControlHeight - nOffset, tools::Long(0)); + + aPlayerWindowSize.setHeight( nControlY - (nOffset << 1) ); + mpMediaWindowControl->SetPosSizePixel(Point(nOffset, nControlY ), Size(aCurSize.Width() - (nOffset << 1), nControlHeight)); + } + if (mpChildWindow) + mpChildWindow->SetPosSizePixel(Point(0, 0), aPlayerWindowSize); + + if (mxPlayerWindow.is()) + mxPlayerWindow->setPosSize(0, 0, aPlayerWindowSize.Width(), aPlayerWindowSize.Height(), 0); +} + +void MediaWindowImpl::StateChanged(StateChangedType eType) +{ + if (!mxPlayerWindow.is()) + return; + + // stop playing when going disabled or hidden + switch (eType) + { + case StateChangedType::Visible: + { + stopPlayingInternal(!IsVisible()); + mxPlayerWindow->setVisible(IsVisible()); + } + break; + + case StateChangedType::Enable: + { + stopPlayingInternal(!IsEnabled()); + mxPlayerWindow->setEnable(IsEnabled()); + } + break; + + default: + break; + } +} + +void MediaWindowImpl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (mxPlayerWindow.is()) + mxPlayerWindow->update(); + + BitmapEx* pLogo = nullptr; + + if (!mxPlayer.is()) + { + if (!mpEmptyBmpEx) + mpEmptyBmpEx.reset(new BitmapEx(AVMEDIA_BMP_EMPTYLOGO)); + + pLogo = mpEmptyBmpEx.get(); + } + else if (!mxPlayerWindow.is()) + { + if (!mpAudioBmpEx) + mpAudioBmpEx.reset(new BitmapEx(AVMEDIA_BMP_AUDIOLOGO)); + + pLogo = mpAudioBmpEx.get(); + } + + if (!mpChildWindow) + return; + + const Point aBasePos(mpChildWindow->GetPosPixel()); + const tools::Rectangle aVideoRect(aBasePos, mpChildWindow->GetSizePixel()); + + if (!pLogo || pLogo->IsEmpty() || aVideoRect.IsEmpty()) + return; + + Size aLogoSize(pLogo->GetSizePixel()); + const Color aBackgroundColor(67, 67, 67); + + rRenderContext.SetLineColor(aBackgroundColor); + rRenderContext.SetFillColor(aBackgroundColor); + rRenderContext.DrawRect(aVideoRect); + + if ((aLogoSize.Width() > aVideoRect.GetWidth() || aLogoSize.Height() > aVideoRect.GetHeight() ) && + (aLogoSize.Height() > 0)) + { + const double fLogoWH = double(aLogoSize.Width()) / aLogoSize.Height(); + + if (fLogoWH < (double(aVideoRect.GetWidth()) / aVideoRect.GetHeight())) + { + aLogoSize.setWidth( tools::Long(aVideoRect.GetHeight() * fLogoWH) ); + aLogoSize.setHeight( aVideoRect.GetHeight() ); + } + else + { + aLogoSize.setWidth( aVideoRect.GetWidth() ); + aLogoSize.setHeight( tools::Long(aVideoRect.GetWidth() / fLogoWH) ); + } + } + + Point aPoint(aBasePos.X() + ((aVideoRect.GetWidth() - aLogoSize.Width()) >> 1), + aBasePos.Y() + ((aVideoRect.GetHeight() - aLogoSize.Height()) >> 1)); + + rRenderContext.DrawBitmapEx(aPoint, aLogoSize, *pLogo); +} + +void MediaWindowImpl::GetFocus() +{ +} + +void MediaWindowImpl::MouseMove(const MouseEvent& rMEvt) +{ + if (mpMediaWindow) + mpMediaWindow->MouseMove(rMEvt); +} + +void MediaWindowImpl::MouseButtonDown(const MouseEvent& rMEvt) +{ + if (mpMediaWindow) + mpMediaWindow->MouseButtonDown(rMEvt); +} + +void MediaWindowImpl::MouseButtonUp(const MouseEvent& rMEvt) +{ + if (mpMediaWindow) + mpMediaWindow->MouseButtonUp(rMEvt); +} + +void MediaWindowImpl::KeyInput(const KeyEvent& rKEvt) +{ + if (mpMediaWindow) + mpMediaWindow->KeyInput(rKEvt); +} + +void MediaWindowImpl::KeyUp(const KeyEvent& rKEvt) +{ + if (mpMediaWindow) + mpMediaWindow->KeyUp(rKEvt); +} + +void MediaWindowImpl::Command(const CommandEvent& rCEvt) +{ + if (mpMediaWindow) + mpMediaWindow->Command(rCEvt); +} + +sal_Int8 MediaWindowImpl::AcceptDrop(const AcceptDropEvent& rEvt) +{ + return (mpMediaWindow ? mpMediaWindow->AcceptDrop(rEvt) : 0); +} + +sal_Int8 MediaWindowImpl::ExecuteDrop(const ExecuteDropEvent& rEvt) +{ + return (mpMediaWindow ? mpMediaWindow->ExecuteDrop(rEvt) : 0); +} + +void MediaWindowImpl::StartDrag(sal_Int8 nAction, const Point& rPosPixel) +{ + if (mpMediaWindow) + mpMediaWindow->StartDrag(nAction, rPosPixel); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/viewer/mediawindow_impl.hxx b/avmedia/source/viewer/mediawindow_impl.hxx new file mode 100644 index 000000000..aa95fde22 --- /dev/null +++ b/avmedia/source/viewer/mediawindow_impl.hxx @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <vcl/transfer.hxx> +#include <vcl/syschild.hxx> + +#include <mediacontrol.hxx> + +namespace com::sun::star::media { + class XPlayer; + class XPlayerWindow; +} + +namespace com::sun::star::uno { + class XComponentContext; +} + +class BitmapEx; + +namespace avmedia +{ + +class MediaWindow; + +namespace priv +{ + +class MediaWindowControl : public MediaControl +{ +public: + + explicit MediaWindowControl( vcl::Window* pParent ); + +protected: + + void update() override; + void execute( const MediaItem& rItem ) override; +}; + +class MediaChildWindow : public SystemChildWindow +{ +public: + + explicit MediaChildWindow( vcl::Window* pParent ); + +protected: + + virtual void MouseMove( const MouseEvent& rMEvt ) override; + virtual void MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual void MouseButtonUp( const MouseEvent& rMEvt ) override; + virtual void KeyInput( const KeyEvent& rKEvt ) override; + virtual void KeyUp( const KeyEvent& rKEvt ) override; + virtual void Command( const CommandEvent& rCEvt ) override; +}; + +class MediaEventListenersImpl; + +class MediaWindowImpl : public Control, public DropTargetHelper, public DragSourceHelper +{ +public: + MediaWindowImpl(vcl::Window* parent, MediaWindow* pMediaWindow, bool bInternalMediaControl); + virtual ~MediaWindowImpl() override; + + virtual void dispose() override; + + static css::uno::Reference<css::media::XPlayer> createPlayer(const OUString& rURL, const OUString& rReferer, const OUString* pMimeType); + + void setURL(const OUString& rURL, OUString const& rTempURL, OUString const& rReferer); + + const OUString& getURL() const; + + bool isValid() const; + + Size getPreferredSize() const; + + bool start(); + + void updateMediaItem( MediaItem& rItem ) const; + void executeMediaItem( const MediaItem& rItem ); + + void setPosSize( const tools::Rectangle& rRect ); + + void setPointer( PointerStyle nPointer ); + +private: + + // Window + virtual void MouseMove( const MouseEvent& rMEvt ) override; + virtual void MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual void MouseButtonUp( const MouseEvent& rMEvt ) override; + virtual void KeyInput( const KeyEvent& rKEvt ) override; + virtual void KeyUp( const KeyEvent& rKEvt ) override; + virtual void Command( const CommandEvent& rCEvt ) override; + virtual void Resize() override; + virtual void StateChanged( StateChangedType ) override; + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override; // const + virtual void GetFocus() override; + + // DropTargetHelper + virtual sal_Int8 AcceptDrop( const AcceptDropEvent& rEvt ) override; + virtual sal_Int8 ExecuteDrop( const ExecuteDropEvent& rEvt ) override; + + // DragSourceHelper + virtual void StartDrag( sal_Int8 nAction, const Point& rPosPixel ) override; + + void stop(); + + bool isPlaying() const; + + double getDuration() const; + + void setMediaTime( double fTime ); + double getMediaTime() const; + + void stopPlayingInternal( bool ); + + void onURLChanged(); + + static css::uno::Reference<css::media::XPlayer> createPlayer(const OUString& rURL, const OUString& rManagerServName, + const css::uno::Reference<css::uno::XComponentContext>& xContext); + + OUString maFileURL; + OUString mTempFileURL; + OUString maReferer; + OUString m_sMimeType; + css::uno::Reference<css::media::XPlayer> mxPlayer; + css::uno::Reference<css::media::XPlayerWindow> mxPlayerWindow; + MediaWindow* mpMediaWindow; + + rtl::Reference<MediaEventListenersImpl> mxEvents; + VclPtr<MediaChildWindow> mpChildWindow; + VclPtr<MediaWindowControl> mpMediaWindowControl; + std::unique_ptr<BitmapEx> mpEmptyBmpEx; + std::unique_ptr<BitmapEx> mpAudioBmpEx; + const MediaItem* mpItem = nullptr; +}; + +}} // end namespace avmedia::priv + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/avmediawin.component b/avmedia/source/win/avmediawin.component new file mode 100644 index 000000000..4b68b1cfc --- /dev/null +++ b/avmedia/source/win/avmediawin.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.avmedia.Manager_DirectX" + constructor="avmedia_Manager_DirectX_get_implementation"> + <service name="com.sun.star.media.Manager_DirectX"/> + </implementation> +</component> diff --git a/avmedia/source/win/framegrabber.cxx b/avmedia/source/win/framegrabber.cxx new file mode 100644 index 000000000..547b3ffa6 --- /dev/null +++ b/avmedia/source/win/framegrabber.cxx @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <prewin.h> +#include <postwin.h> +#include <objbase.h> +#include <strmif.h> +#include <Amvideo.h> +#include "interface.hxx" +#include <uuids.h> + +#include "framegrabber.hxx" +#include "player.hxx" + +#include <cppuhelper/supportsservice.hxx> +#include <osl/file.hxx> +#include <tools/stream.hxx> +#include <vcl/graph.hxx> +#include <vcl/dibtools.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <systools/win32/oleauto.hxx> + +constexpr OUStringLiteral AVMEDIA_WIN_FRAMEGRABBER_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.FrameGrabber_DirectX"; +constexpr OUStringLiteral AVMEDIA_WIN_FRAMEGRABBER_SERVICENAME = u"com.sun.star.media.FrameGrabber_DirectX"; + +using namespace ::com::sun::star; + +namespace avmedia::win { + + +FrameGrabber::FrameGrabber() + : sal::systools::CoInitializeGuard(COINIT_APARTMENTTHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow) +{ +} + + +FrameGrabber::~FrameGrabber() = default; + +namespace { + +sal::systools::COMReference<IMediaDet> implCreateMediaDet( const OUString& rURL ) +{ + sal::systools::COMReference<IMediaDet> pDet; + + if( SUCCEEDED(pDet.CoCreateInstance(CLSID_MediaDet, nullptr, CLSCTX_INPROC_SERVER)) ) + { + OUString aLocalStr; + + if( osl::FileBase::getSystemPathFromFileURL( rURL, aLocalStr ) + == osl::FileBase::E_None ) + { + if( !SUCCEEDED( pDet->put_Filename(sal::systools::BStr(aLocalStr)) ) ) + pDet.clear(); + } + } + + return pDet; +} + +} + +bool FrameGrabber::create( const OUString& rURL ) +{ + // just check if a MediaDet interface can be created with the given URL + if (implCreateMediaDet(rURL)) + maURL = rURL; + else + maURL.clear(); + + return !maURL.isEmpty(); +} + + +uno::Reference< graphic::XGraphic > SAL_CALL FrameGrabber::grabFrame( double fMediaTime ) +{ + uno::Reference< graphic::XGraphic > xRet; + if (sal::systools::COMReference<IMediaDet> pDet = implCreateMediaDet(maURL)) + { + double fLength; + long nStreamCount; + bool bFound = false; + + if( SUCCEEDED( pDet->get_OutputStreams( &nStreamCount ) ) ) + { + for( long n = 0; ( n < nStreamCount ) && !bFound; ++n ) + { + GUID aMajorType; + + if( SUCCEEDED( pDet->put_CurrentStream( n ) ) && + SUCCEEDED( pDet->get_StreamType( &aMajorType ) ) && + ( aMajorType == MEDIATYPE_Video ) ) + { + bFound = true; + } + } + } + + if( bFound && + ( S_OK == pDet->get_StreamLength( &fLength ) ) && + ( fLength > 0.0 ) && ( fMediaTime >= 0.0 ) && ( fMediaTime <= fLength ) ) + { + AM_MEDIA_TYPE aMediaType; + LONG nWidth = 0, nHeight = 0; + long nSize = 0; + + if( SUCCEEDED( pDet->get_StreamMediaType( &aMediaType ) ) ) + { + if( ( aMediaType.formattype == FORMAT_VideoInfo ) && + ( aMediaType.cbFormat >= sizeof( VIDEOINFOHEADER ) ) ) + { + VIDEOINFOHEADER* pVih = reinterpret_cast< VIDEOINFOHEADER* >( aMediaType.pbFormat ); + + nWidth = pVih->bmiHeader.biWidth; + nHeight = pVih->bmiHeader.biHeight; + + if( nHeight < 0 ) + nHeight *= -1; + } + + if( aMediaType.cbFormat != 0 ) + { + ::CoTaskMemFree( aMediaType.pbFormat ); + aMediaType.cbFormat = 0; + aMediaType.pbFormat = nullptr; + } + + if( aMediaType.pUnk != nullptr ) + { + aMediaType.pUnk->Release(); + aMediaType.pUnk = nullptr; + } + } + + if( ( nWidth > 0 ) && ( nHeight > 0 ) && + SUCCEEDED( pDet->GetBitmapBits( 0, &nSize, nullptr, nWidth, nHeight ) ) && + ( nSize > 0 ) ) + { + auto pBuffer = std::make_unique<char[]>(nSize); + + try + { + if( SUCCEEDED( pDet->GetBitmapBits( fMediaTime, nullptr, pBuffer.get(), nWidth, nHeight ) ) ) + { + SvMemoryStream aMemStm( pBuffer.get(), nSize, StreamMode::READ | StreamMode::WRITE ); + Bitmap aBmp; + + if( ReadDIB(aBmp, aMemStm, false ) && !aBmp.IsEmpty() ) + { + BitmapEx aBitmapEx(aBmp); + Graphic aGraphic(aBitmapEx); + xRet = aGraphic.GetXGraphic(); + } + } + } + catch( ... ) + { + } + } + } + } + + return xRet; +} + + +OUString SAL_CALL FrameGrabber::getImplementationName( ) +{ + return AVMEDIA_WIN_FRAMEGRABBER_IMPLEMENTATIONNAME; +} + + +sal_Bool SAL_CALL FrameGrabber::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + + +uno::Sequence< OUString > SAL_CALL FrameGrabber::getSupportedServiceNames( ) +{ + return { AVMEDIA_WIN_FRAMEGRABBER_SERVICENAME }; +} + +} // namespace avmedia::win + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/framegrabber.hxx b/avmedia/source/win/framegrabber.hxx new file mode 100644 index 000000000..d1ca48e84 --- /dev/null +++ b/avmedia/source/win/framegrabber.hxx @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "wincommon.hxx" +#include <cppuhelper/implbase.hxx> +#include <systools/win32/comtools.hxx> + +#include <com/sun/star/media/XFrameGrabber.hpp> + +struct IMediaDet; + +namespace avmedia::win { + +class FrameGrabber : public ::cppu::WeakImplHelper< css::media::XFrameGrabber, + css::lang::XServiceInfo >, + public sal::systools::CoInitializeGuard +{ +public: + explicit FrameGrabber(); + ~FrameGrabber() override; + + bool create( const OUString& rURL ); + + // XFrameGrabber + virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL grabFrame( double fMediaTime ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + OUString maURL; +}; + +} // namespace avmedia::win + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/interface.hxx b/avmedia/source/win/interface.hxx new file mode 100644 index 000000000..a52c6ab1f --- /dev/null +++ b/avmedia/source/win/interface.hxx @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +extern "C" const CLSID CLSID_MediaDet; +extern "C" const IID IID_IMediaDet; +struct ISampleGrabber; + +struct +__declspec(uuid("65BD0710-24D2-4ff7-9324-ED2E5D3ABAFA")) __declspec(novtable) +IMediaDet : public IUnknown +{ +public: + virtual HRESULT __stdcall get_Filter( + IUnknown **pVal) = 0; + virtual HRESULT __stdcall put_Filter( + IUnknown *newVal) = 0; + virtual HRESULT __stdcall get_OutputStreams( + long *pVal) = 0; + virtual HRESULT __stdcall get_CurrentStream( + long *pVal) = 0; + virtual HRESULT __stdcall put_CurrentStream( + long newVal) = 0; + virtual HRESULT __stdcall get_StreamType( + GUID *pVal) = 0; + virtual HRESULT __stdcall get_StreamTypeB( + BSTR *pVal) = 0; + virtual HRESULT __stdcall get_StreamLength( + double *pVal) = 0; + virtual HRESULT __stdcall get_Filename( + BSTR *pVal) = 0; + virtual HRESULT __stdcall put_Filename( + BSTR newVal) = 0; + virtual HRESULT __stdcall GetBitmapBits( + double StreamTime, + long *pBufferSize, + char *pBuffer, + long Width, + long Height) = 0; + virtual HRESULT __stdcall WriteBitmapBits( + double StreamTime, + long Width, + long Height, + BSTR Filename) = 0; + virtual HRESULT __stdcall get_StreamMediaType( + AM_MEDIA_TYPE *pVal) = 0; + virtual HRESULT __stdcall GetSampleGrabber( + ISampleGrabber **ppVal) = 0; + virtual HRESULT __stdcall get_FrameRate( + double *pVal) = 0; + virtual HRESULT __stdcall EnterBitmapGrabMode( + double SeekTime) = 0; + +protected: + ~IMediaDet() {} +}; + +extern "C" const IID IID_ISampleGrabberCB; +struct +__declspec(uuid("0579154A-2B53-4994-B0D0-E773148EFF85")) __declspec(novtable) +ISampleGrabberCB : public IUnknown +{ +public: + virtual HRESULT __stdcall SampleCB( + double SampleTime, + IMediaSample *pSample) = 0; + virtual HRESULT __stdcall BufferCB( + double SampleTime, + BYTE *pBuffer, + long BufferLen) = 0; + +protected: + ~ISampleGrabberCB() {} +}; + +extern "C" const IID IID_ISampleGrabber; +struct +__declspec(uuid("6B652FFF-11FE-4fce-92AD-0266B5D7C78F")) __declspec(novtable) +ISampleGrabber : public IUnknown +{ +public: + virtual HRESULT __stdcall SetOneShot( + BOOL OneShot) = 0; + virtual HRESULT __stdcall SetMediaType( + const AM_MEDIA_TYPE *pType) = 0; + virtual HRESULT __stdcall GetConnectedMediaType( + AM_MEDIA_TYPE *pType) = 0; + virtual HRESULT __stdcall SetBufferSamples( + BOOL BufferThem) = 0; + virtual HRESULT __stdcall GetCurrentBuffer( + long *pBufferSize, + long *pBuffer) = 0; + virtual HRESULT __stdcall GetCurrentSample( + IMediaSample **ppSample) = 0; + virtual HRESULT __stdcall SetCallback( + ISampleGrabberCB *pCallback, + long WhichMethodToCallback) = 0; + +protected: + ~ISampleGrabber() {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/manager.cxx b/avmedia/source/win/manager.cxx new file mode 100644 index 000000000..578ec9d2a --- /dev/null +++ b/avmedia/source/win/manager.cxx @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "manager.hxx" +#include "player.hxx" + +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> +#include <tools/urlobj.hxx> + +using namespace ::com::sun::star; + +namespace avmedia::win { + +Manager::Manager() +{ +} + + +Manager::~Manager() +{ +} + + +uno::Reference< media::XPlayer > SAL_CALL Manager::createPlayer( const OUString& rURL ) +{ + rtl::Reference<Player> pPlayer( new Player() ); + const INetURLObject aURL( rURL ); + + if( !pPlayer->create( aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ) ) ) + pPlayer.clear(); + + return pPlayer; +} + + +OUString SAL_CALL Manager::getImplementationName( ) +{ + return "com.sun.star.comp.avmedia.Manager_DirectX"; +} + + +sal_Bool SAL_CALL Manager::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + + +uno::Sequence< OUString > SAL_CALL Manager::getSupportedServiceNames( ) +{ + return { "com.sun.star.media.Manager" }; +} + +} // namespace avmedia::win + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +avmedia_Manager_DirectX_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new avmedia::win::Manager()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/manager.hxx b/avmedia/source/win/manager.hxx new file mode 100644 index 000000000..8742103c4 --- /dev/null +++ b/avmedia/source/win/manager.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "wincommon.hxx" +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/media/XManager.hpp> + + +namespace avmedia::win { + +class Manager : public ::cppu::WeakImplHelper< css::media::XManager, + css::lang::XServiceInfo > +{ +public: + + explicit Manager(); + ~Manager() override; + + // XManager + virtual css::uno::Reference< css::media::XPlayer > SAL_CALL createPlayer( const OUString& aURL ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; + +} // namespace avmedia::win + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/player.cxx b/avmedia/source/win/player.cxx new file mode 100644 index 000000000..5c05dcbc3 --- /dev/null +++ b/avmedia/source/win/player.cxx @@ -0,0 +1,418 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <objbase.h> +#include <strmif.h> +#include <control.h> +#include <uuids.h> +#include <evcode.h> + +#include "player.hxx" +#include "framegrabber.hxx" +#include "window.hxx" +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <osl/file.hxx> +#include <rtl/ref.hxx> + +constexpr OUStringLiteral AVMEDIA_WIN_PLAYER_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.Player_DirectX"; +constexpr OUStringLiteral AVMEDIA_WIN_PLAYER_SERVICENAME = u"com.sun.star.media.Player_DirectX"; + +using namespace ::com::sun::star; + +namespace avmedia::win { + +static LRESULT CALLBACK MediaPlayerWndProc_2( HWND hWnd,UINT nMsg, WPARAM nPar1, LPARAM nPar2 ) +{ + Player* pPlayer = reinterpret_cast<Player*>(::GetWindowLongPtrW( hWnd, 0 )); + bool bProcessed = true; + + if( pPlayer ) + { + switch( nMsg ) + { + case WM_GRAPHNOTIFY: + pPlayer->processEvent(); + break; + default: + bProcessed = false; + break; + } + } + else + bProcessed = false; + + return( bProcessed ? 0 : DefWindowProcW( hWnd, nMsg, nPar1, nPar2 ) ); +} + + +Player::Player() : + Player_BASE(m_aMutex), + sal::systools::CoInitializeGuard(COINIT_APARTMENTTHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow), + mnUnmutedVolume( 0 ), + mnFrameWnd( nullptr ), + mbMuted( false ), + mbLooping( false ), + mbAddWindow( true ) +{ +} + + +Player::~Player() +{ + if( mnFrameWnd ) + ::DestroyWindow( mnFrameWnd ); +} + + +void SAL_CALL Player::disposing() +{ + ::osl::MutexGuard aGuard(m_aMutex); + stop(); + if( mpME ) + mpME->SetNotifyWindow( 0, WM_GRAPHNOTIFY, 0); +} + + +bool Player::create( const OUString& rURL ) +{ + bool bRet = false; + + if( SUCCEEDED(mpGB.CoCreateInstance(CLSID_FilterGraph, nullptr, CLSCTX_INPROC_SERVER)) ) + { + // Don't use the overlay mixer on Windows Vista + // It disables the desktop composition as soon as RenderFile is called + // also causes some other problems: video rendering is not reliable + + // tdf#128057: IGraphBuilder::RenderFile seems to fail to handle file URIs properly when + // they contain encoded characters like "%23"; so pass system path in that case instead. + OUString aFile(rURL); + if (aFile.startsWithIgnoreAsciiCase("file:")) + osl::FileBase::getSystemPathFromFileURL(rURL, aFile); + + if( SUCCEEDED( mpGB->RenderFile( o3tl::toW(aFile.getStr()), nullptr ) ) && + mpMC.set(mpGB, sal::systools::COM_QUERY) && + mpME.set(mpGB, sal::systools::COM_QUERY) && + mpMP.set(mpGB, sal::systools::COM_QUERY) ) + { + // Video interfaces + mpVW.set(mpGB, sal::systools::COM_QUERY); + mpBV.set(mpGB, sal::systools::COM_QUERY); + + // Audio interface + mpBA.set(mpGB, sal::systools::COM_QUERY); + + if( mpBA ) + mpBA->put_Volume( mnUnmutedVolume ); + + bRet = true; + } + } + + if( bRet ) + maURL = rURL; + else + maURL.clear(); + + return bRet; +} + + +const IVideoWindow* Player::getVideoWindow() const +{ + return mpVW; +} + + +void Player::setNotifyWnd( HWND nNotifyWnd ) +{ + mbAddWindow = false; + if( mpME ) + mpME->SetNotifyWindow( reinterpret_cast<OAHWND>(nNotifyWnd), WM_GRAPHNOTIFY, reinterpret_cast< LONG_PTR>( this ) ); +} + + +void Player::processEvent() +{ + long nCode; + LONG_PTR nParam1, nParam2; + + while( mpME && SUCCEEDED( mpME->GetEvent( &nCode, &nParam1, &nParam2, 0 ) ) ) + { + if( EC_COMPLETE == nCode ) + { + if( mbLooping ) + { + setMediaTime( 0.0 ); + start(); + } + else + { + setMediaTime( getDuration() ); + stop(); + } + } + + mpME->FreeEventParams( nCode, nParam1, nParam2 ); + } +} + + +void SAL_CALL Player::start( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + if( mpMC ) + { + if ( mbAddWindow ) + { + static WNDCLASSW* mpWndClass = nullptr; + if ( !mpWndClass ) + { + mpWndClass = new WNDCLASSW; + + memset( mpWndClass, 0, sizeof( *mpWndClass ) ); + mpWndClass->hInstance = GetModuleHandleW( nullptr ); + mpWndClass->cbWndExtra = sizeof( DWORD ); + mpWndClass->lpfnWndProc = MediaPlayerWndProc_2; + mpWndClass->lpszClassName = L"com_sun_star_media_Sound_Player"; + mpWndClass->hbrBackground = static_cast<HBRUSH>(::GetStockObject( BLACK_BRUSH )); + mpWndClass->hCursor = ::LoadCursor( nullptr, IDC_ARROW ); + + RegisterClassW( mpWndClass ); + } + if ( !mnFrameWnd ) + { + mnFrameWnd = CreateWindowW( mpWndClass->lpszClassName, nullptr, + 0, + 0, 0, 0, 0, + nullptr, nullptr, mpWndClass->hInstance, nullptr ); + if ( mnFrameWnd ) + { + ::ShowWindow(mnFrameWnd, SW_HIDE); + SetWindowLongPtrW( mnFrameWnd, 0, reinterpret_cast<LONG_PTR>(this) ); + // mpVW->put_Owner( (OAHWND) mnFrameWnd ); + setNotifyWnd( mnFrameWnd ); + } + } + } + + mpMC->Run(); + } +} + + +void SAL_CALL Player::stop( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + if( mpMC ) + mpMC->Stop(); +} + + +sal_Bool SAL_CALL Player::isPlaying() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + OAFilterState eFilterState; + bool bRet = false; + + if( mpMC && SUCCEEDED( mpMC->GetState( 10, &eFilterState ) ) ) + bRet = ( State_Running == eFilterState ); + + return bRet; +} + + +double SAL_CALL Player::getDuration( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + REFTIME aRefTime( 0.0 ); + + if( mpMP ) + mpMP->get_Duration( &aRefTime ); + + return aRefTime; +} + + +void SAL_CALL Player::setMediaTime( double fTime ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + if( mpMP ) + { + const bool bPlaying = isPlaying(); + + mpMP->put_CurrentPosition( fTime ); + + if( !bPlaying && mpMC ) + mpMC->StopWhenReady(); + } +} + + +double SAL_CALL Player::getMediaTime( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + REFTIME aRefTime( 0.0 ); + + if( mpMP ) + mpMP->get_CurrentPosition( &aRefTime ); + + return aRefTime; +} + + +void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + mbLooping = bSet; +} + + +sal_Bool SAL_CALL Player::isPlaybackLoop( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + return mbLooping; +} + + +void SAL_CALL Player::setMute( sal_Bool bSet ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + if (mpBA && (mbMuted != static_cast<bool>(bSet))) + { + mbMuted = bSet; + mpBA->put_Volume( mbMuted ? -10000 : mnUnmutedVolume ); + } +} + + +sal_Bool SAL_CALL Player::isMute( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + return mbMuted; +} + + +void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + mnUnmutedVolume = static_cast< long >( nVolumeDB ) * 100; + + if( !mbMuted && mpBA ) + mpBA->put_Volume( mnUnmutedVolume ); +} + + +sal_Int16 SAL_CALL Player::getVolumeDB( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + return static_cast< sal_Int16 >( mnUnmutedVolume / 100 ); +} + + +awt::Size SAL_CALL Player::getPreferredPlayerWindowSize( ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + awt::Size aSize( 0, 0 ); + + if( mpBV ) + { + long nWidth = 0, nHeight = 0; + + mpBV->GetVideoSize( &nWidth, &nHeight ); + aSize.Width = nWidth; + aSize.Height = nHeight; + } + + return aSize; +} + + +uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& aArguments ) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + uno::Reference< ::media::XPlayerWindow > xRet; + awt::Size aSize( getPreferredPlayerWindowSize() ); + + if( mpVW && aSize.Width > 0 && aSize.Height > 0 ) + { + rtl::Reference<::avmedia::win::Window> pWindow = new ::avmedia::win::Window( *this ); + + xRet = pWindow; + + if( !pWindow->create( aArguments ) ) + xRet.clear(); + } + + return xRet; +} + + +uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber( ) +{ + uno::Reference< media::XFrameGrabber > xRet; + + if( !maURL.isEmpty() ) + { + rtl::Reference<FrameGrabber> pGrabber = new FrameGrabber(); + + xRet = pGrabber; + + if( !pGrabber->create( maURL ) ) + xRet.clear(); + } + + return xRet; +} + + +OUString SAL_CALL Player::getImplementationName( ) +{ + return AVMEDIA_WIN_PLAYER_IMPLEMENTATIONNAME; +} + + +sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + + +uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames( ) +{ + return { AVMEDIA_WIN_PLAYER_SERVICENAME }; +} + +} // namespace avmedia::win + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/player.hxx b/avmedia/source/win/player.hxx new file mode 100644 index 000000000..1563d549a --- /dev/null +++ b/avmedia/source/win/player.hxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <WinDef.h> + +#include "wincommon.hxx" + +#include <com/sun/star/media/XPlayer.hpp> + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <systools/win32/comtools.hxx> + +struct IGraphBuilder; +struct IBaseFilter; +struct IMediaControl; +struct IMediaEventEx; +struct IMediaSeeking; +struct IMediaPosition; +struct IBasicAudio; +struct IBasicVideo; +struct IVideoWindow; +struct IDDrawExclModeVideo; +struct IDirectDraw; +struct IDirectDrawSurface; + +namespace avmedia::win { + +typedef ::cppu::WeakComponentImplHelper< css::media::XPlayer, + css::lang::XServiceInfo > Player_BASE; + + +class Player : public cppu::BaseMutex, + public Player_BASE, + public sal::systools::CoInitializeGuard +{ +public: + + explicit Player(); + ~Player() override; + + bool create( const OUString& rURL ); + + void setNotifyWnd( HWND nNotifyWnd ); + void processEvent(); + + const IVideoWindow* getVideoWindow() const; + + // XPlayer + virtual void SAL_CALL start( ) override; + virtual void SAL_CALL stop( ) override; + virtual sal_Bool SAL_CALL isPlaying( ) override; + virtual double SAL_CALL getDuration( ) override; + virtual void SAL_CALL setMediaTime( double fTime ) override; + virtual double SAL_CALL getMediaTime( ) override; + virtual void SAL_CALL setPlaybackLoop( sal_Bool bSet ) override; + virtual sal_Bool SAL_CALL isPlaybackLoop( ) override; + virtual void SAL_CALL setMute( sal_Bool bSet ) override; + virtual sal_Bool SAL_CALL isMute( ) override; + virtual void SAL_CALL setVolumeDB( sal_Int16 nVolumeDB ) override; + virtual sal_Int16 SAL_CALL getVolumeDB( ) override; + virtual css::awt::Size SAL_CALL getPreferredPlayerWindowSize( ) override; + virtual css::uno::Reference< css::media::XPlayerWindow > SAL_CALL createPlayerWindow( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + virtual css::uno::Reference< css::media::XFrameGrabber > SAL_CALL createFrameGrabber( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // ::cppu::OComponentHelper + virtual void SAL_CALL disposing() override; + +private: + + OUString maURL; + sal::systools::COMReference<IGraphBuilder> mpGB; + sal::systools::COMReference<IMediaControl> mpMC; + sal::systools::COMReference<IMediaEventEx> mpME; + sal::systools::COMReference<IMediaPosition> mpMP; + sal::systools::COMReference<IBasicAudio> mpBA; + sal::systools::COMReference<IBasicVideo> mpBV; + sal::systools::COMReference<IVideoWindow> mpVW; + long mnUnmutedVolume; + HWND mnFrameWnd; + bool mbMuted; + bool mbLooping; + bool mbAddWindow; +}; + +} // namespace avmedia::win + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/wincommon.hxx b/avmedia/source/win/wincommon.hxx new file mode 100644 index 000000000..5572f6c3e --- /dev/null +++ b/avmedia/source/win/wincommon.hxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <tools/stream.hxx> +#include <cppuhelper/weak.hxx> +#include <cppuhelper/factory.hxx> + +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/media/XManager.hpp> + +#define WM_GRAPHNOTIFY (WM_USER + 567) + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/window.cxx b/avmedia/source/win/window.cxx new file mode 100644 index 000000000..4f4ef4745 --- /dev/null +++ b/avmedia/source/win/window.cxx @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <objbase.h> +#include <strmif.h> +#include <control.h> +#include <dshow.h> + +#include <com/sun/star/awt/SystemPointer.hpp> +#include <cppuhelper/supportsservice.hxx> + +#include "window.hxx" +#include "player.hxx" + +constexpr OUStringLiteral AVMEDIA_WIN_WINDOW_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.Window_DirectX"; +constexpr OUStringLiteral AVMEDIA_WIN_WINDOW_SERVICENAME = u"com.sun.star.media.Window_DirectX"; + +using namespace ::com::sun::star; + +namespace avmedia::win { + +static LRESULT CALLBACK MediaPlayerWndProc( HWND hWnd,UINT nMsg, WPARAM nPar1, LPARAM nPar2 ) +{ + Window* pWindow = reinterpret_cast<Window*>(GetWindowLongPtrW( hWnd, 0 )); + bool bProcessed = true; + + if( pWindow ) + { + switch( nMsg ) + { + case WM_SETCURSOR: + pWindow->updatePointer(); + break; + + case WM_GRAPHNOTIFY: + pWindow->processGraphEvent(); + break; + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + PostMessage(pWindow->getParentWnd(), nMsg, nPar1, nPar2); + break; + + case WM_SETFOCUS: + { + const awt::FocusEvent aUNOEvt; + pWindow->fireSetFocusEvent( aUNOEvt ); + } + break; + + default: + bProcessed = false; + break; + } + } + else + bProcessed = false; + + return( bProcessed ? 0 : DefWindowProcW( hWnd, nMsg, nPar1, nPar2 ) ); +} + +static WNDCLASSW* lcl_getWndClass() +{ + WNDCLASSW* s_pWndClass = new WNDCLASSW; + + memset( s_pWndClass, 0, sizeof( *s_pWndClass ) ); + s_pWndClass->hInstance = GetModuleHandleW( nullptr ); + s_pWndClass->cbWndExtra = sizeof( DWORD_PTR ); + s_pWndClass->lpfnWndProc = MediaPlayerWndProc; + s_pWndClass->lpszClassName = L"com_sun_star_media_PlayerWnd"; + s_pWndClass->hbrBackground = static_cast<HBRUSH>(::GetStockObject( BLACK_BRUSH )); + s_pWndClass->hCursor = ::LoadCursor( nullptr, IDC_ARROW ); + + RegisterClassW( s_pWndClass ); + + return s_pWndClass; +} + +Window::Window( Player& rPlayer ) : + maListeners( maMutex ), + meZoomLevel( media::ZoomLevel_NOT_AVAILABLE ), + mrPlayer( rPlayer ), + mnFrameWnd( nullptr ), + mnParentWnd( nullptr ), + mnPointerType( awt::SystemPointer::ARROW ) +{ +} + +Window::~Window() +{ + if( mnFrameWnd ) + ::DestroyWindow( mnFrameWnd ); +} + +void Window::ImplLayoutVideoWindow() +{ + if( media::ZoomLevel_NOT_AVAILABLE != meZoomLevel ) + { + awt::Size aPrefSize( mrPlayer.getPreferredPlayerWindowSize() ); + awt::Rectangle aRect = getPosSize(); + int nW = aRect.Width, nH = aRect.Height; + int nVideoW = nW, nVideoH = nH; + int nX = 0, nY = 0, nWidth = 0, nHeight = 0; + bool bDone = false, bZoom = false; + + if( media::ZoomLevel_ORIGINAL == meZoomLevel ) + { + bZoom = true; + } + else if( media::ZoomLevel_ZOOM_1_TO_4 == meZoomLevel ) + { + aPrefSize.Width >>= 2; + aPrefSize.Height >>= 2; + bZoom = true; + } + else if( media::ZoomLevel_ZOOM_1_TO_2 == meZoomLevel ) + { + aPrefSize.Width >>= 1; + aPrefSize.Height >>= 1; + bZoom = true; + } + else if( media::ZoomLevel_ZOOM_2_TO_1 == meZoomLevel ) + { + aPrefSize.Width <<= 1; + aPrefSize.Height <<= 1; + bZoom = true; + } + else if( media::ZoomLevel_ZOOM_4_TO_1 == meZoomLevel ) + { + aPrefSize.Width <<= 2; + aPrefSize.Height <<= 2; + bZoom = true; + } + else if( media::ZoomLevel_FIT_TO_WINDOW == meZoomLevel ) + { + nWidth = nVideoW; + nHeight = nVideoH; + bDone = true; + } + + if( bZoom ) + { + if( ( aPrefSize.Width <= nVideoW ) && ( aPrefSize.Height <= nVideoH ) ) + { + nX = ( nVideoW - aPrefSize.Width ) >> 1; + nY = ( nVideoH - aPrefSize.Height ) >> 1; + nWidth = aPrefSize.Width; + nHeight = aPrefSize.Height; + bDone = true; + } + } + + if( !bDone ) + { + if( aPrefSize.Width > 0 && aPrefSize.Height > 0 && nVideoW > 0 && nVideoH > 0 ) + { + double fPrefWH = static_cast<double>(aPrefSize.Width) / aPrefSize.Height; + + if( fPrefWH < ( static_cast<double>(nVideoW) / nVideoH ) ) + nVideoW = static_cast<int>( nVideoH * fPrefWH ); + else + nVideoH = static_cast<int>( nVideoW / fPrefWH ); + + nX = ( nW - nVideoW ) >> 1; + nY = ( nH - nVideoH ) >> 1; + nWidth = nVideoW; + nHeight = nVideoH; + } + else + nX = nY = nWidth = nHeight = 0; + } + + IVideoWindow* pVideoWindow = const_cast< IVideoWindow* >( mrPlayer.getVideoWindow() ); + + if( pVideoWindow ) + pVideoWindow->SetWindowPosition( nX, nY, nWidth, nHeight ); + } +} + +bool Window::create( const uno::Sequence< uno::Any >& rArguments ) +{ + IVideoWindow* pVideoWindow = const_cast< IVideoWindow* >( mrPlayer.getVideoWindow() ); + static WNDCLASSW* mpWndClass = lcl_getWndClass(); + + if( !mnFrameWnd && pVideoWindow && mpWndClass ) + { + awt::Rectangle aRect; + sal_IntPtr nWnd; + + rArguments[ 0 ] >>= nWnd; + rArguments[ 1 ] >>= aRect; + + mnParentWnd = reinterpret_cast<HWND>(nWnd); + + mnFrameWnd = CreateWindowW( mpWndClass->lpszClassName, nullptr, + WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, + aRect.X, aRect.Y, aRect.Width, aRect.Height, + mnParentWnd, nullptr, mpWndClass->hInstance, nullptr ); + + if( mnFrameWnd ) + { + SetWindowLongPtrW( mnFrameWnd, 0, reinterpret_cast<LONG_PTR>(this) ); + + pVideoWindow->put_Owner( reinterpret_cast<OAHWND>(mnFrameWnd) ); + pVideoWindow->put_MessageDrain( reinterpret_cast<OAHWND>(mnFrameWnd) ); + pVideoWindow->put_WindowStyle( WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN ); + + mrPlayer.setNotifyWnd( mnFrameWnd ); + + meZoomLevel = media::ZoomLevel_FIT_TO_WINDOW; + ImplLayoutVideoWindow(); + } + } + + return( mnFrameWnd != nullptr ); +} + +void Window::processGraphEvent() +{ + mrPlayer.processEvent(); +} + +void Window::updatePointer() +{ + LPCTSTR pCursorName; + + switch( mnPointerType ) + { + case awt::SystemPointer::CROSS: pCursorName = IDC_CROSS; break; + case awt::SystemPointer::MOVE: pCursorName = IDC_SIZEALL; break; + case awt::SystemPointer::WAIT: pCursorName = IDC_WAIT; break; + + default: + pCursorName = IDC_ARROW; + break; + } + + SetCursor( LoadCursor( nullptr, pCursorName ) ); +} + +void SAL_CALL Window::update( ) +{ + ::RedrawWindow( mnFrameWnd, nullptr, nullptr, RDW_ALLCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE ); +} + +sal_Bool SAL_CALL Window::setZoomLevel( media::ZoomLevel eZoomLevel ) +{ + bool bRet = false; + + if( media::ZoomLevel_NOT_AVAILABLE != meZoomLevel && + media::ZoomLevel_NOT_AVAILABLE != eZoomLevel ) + { + if( eZoomLevel != meZoomLevel ) + { + meZoomLevel = eZoomLevel; + ImplLayoutVideoWindow(); + } + + bRet = true; + } + + return bRet; +} + +media::ZoomLevel SAL_CALL Window::getZoomLevel( ) +{ + return meZoomLevel; +} + +void SAL_CALL Window::setPointerType( sal_Int32 nPointerType ) +{ + mnPointerType = nPointerType; +} + +void SAL_CALL Window::setPosSize( sal_Int32 X, sal_Int32 Y, sal_Int32 Width, sal_Int32 Height, sal_Int16 ) +{ + if( mnFrameWnd ) + { + ::SetWindowPos( mnFrameWnd, HWND_TOP, X, Y, Width, Height, 0 ); + ImplLayoutVideoWindow(); + } +} + +awt::Rectangle SAL_CALL Window::getPosSize() +{ + awt::Rectangle aRet; + + if( mnFrameWnd ) + { + ::RECT aWndRect; + + if( ::GetClientRect( mnFrameWnd, &aWndRect ) ) + { + aRet.X = aWndRect.left; + aRet.Y = aWndRect.top; + aRet.Width = aWndRect.right - aWndRect.left + 1; + aRet.Height = aWndRect.bottom - aWndRect.top + 1; + } + } + + return aRet; +} + +void SAL_CALL Window::setVisible( sal_Bool bVisible ) +{ + if( mnFrameWnd ) + { + IVideoWindow* pVideoWindow = const_cast< IVideoWindow* >( mrPlayer.getVideoWindow() ); + + if( pVideoWindow ) + pVideoWindow->put_Visible( bVisible ? OATRUE : OAFALSE ); + + ::ShowWindow( mnFrameWnd, bVisible ? SW_SHOW : SW_HIDE ); + } +} + +void SAL_CALL Window::setEnable( sal_Bool bEnable ) +{ + if( mnFrameWnd ) + ::EnableWindow( mnFrameWnd, bEnable ); +} + +void SAL_CALL Window::setFocus( ) +{ + if( mnFrameWnd ) + ::SetFocus( mnFrameWnd ); +} + +void SAL_CALL Window::addWindowListener( const uno::Reference< awt::XWindowListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::removeWindowListener( const uno::Reference< awt::XWindowListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::addFocusListener( const uno::Reference< awt::XFocusListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::removeFocusListener( const uno::Reference< awt::XFocusListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::addKeyListener( const uno::Reference< awt::XKeyListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::removeKeyListener( const uno::Reference< awt::XKeyListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::addMouseListener( const uno::Reference< awt::XMouseListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::removeMouseListener( const uno::Reference< awt::XMouseListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::addMouseMotionListener( const uno::Reference< awt::XMouseMotionListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::removeMouseMotionListener( const uno::Reference< awt::XMouseMotionListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::addPaintListener( const uno::Reference< awt::XPaintListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::removePaintListener( const uno::Reference< awt::XPaintListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::dispose( ) +{ +} + +void SAL_CALL Window::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + maListeners.addInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void SAL_CALL Window::removeEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + maListeners.removeInterface( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void Window::fireMousePressedEvent( const css::awt::MouseEvent& rEvt ) +{ + comphelper::OInterfaceContainerHelper2* pContainer = maListeners.getContainer( cppu::UnoType<awt::XMouseListener>::get()); + + if( pContainer ) + { + comphelper::OInterfaceIteratorHelper2 aIter( *pContainer ); + + while( aIter.hasMoreElements() ) + static_cast< awt::XMouseListener* >( aIter.next() )->mousePressed( rEvt ); + } +} + +void Window::fireMouseReleasedEvent( const css::awt::MouseEvent& rEvt ) +{ + comphelper::OInterfaceContainerHelper2* pContainer = maListeners.getContainer( cppu::UnoType<awt::XMouseListener>::get()); + + if( pContainer ) + { + comphelper::OInterfaceIteratorHelper2 aIter( *pContainer ); + + while( aIter.hasMoreElements() ) + static_cast< awt::XMouseListener* >( aIter.next() )->mouseReleased( rEvt ); + } +} + +void Window::fireMouseMovedEvent( const css::awt::MouseEvent& rEvt ) +{ + comphelper::OInterfaceContainerHelper2* pContainer = maListeners.getContainer( cppu::UnoType<awt::XMouseMotionListener>::get()); + + if( pContainer ) + { + comphelper::OInterfaceIteratorHelper2 aIter( *pContainer ); + + while( aIter.hasMoreElements() ) + static_cast< awt::XMouseMotionListener* >( aIter.next() )->mouseMoved( rEvt ); + } +} + +void Window::fireSetFocusEvent( const css::awt::FocusEvent& rEvt ) +{ + comphelper::OInterfaceContainerHelper2* pContainer = maListeners.getContainer( cppu::UnoType<awt::XFocusListener>::get()); + + if( pContainer ) + { + comphelper::OInterfaceIteratorHelper2 aIter( *pContainer ); + + while( aIter.hasMoreElements() ) + static_cast< awt::XFocusListener* >( aIter.next() )->focusGained( rEvt ); + } +} + +OUString SAL_CALL Window::getImplementationName( ) +{ + return AVMEDIA_WIN_WINDOW_IMPLEMENTATIONNAME; +} + +sal_Bool SAL_CALL Window::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL Window::getSupportedServiceNames( ) +{ + return { AVMEDIA_WIN_WINDOW_SERVICENAME }; +} + +} // namespace avmedia::win + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/avmedia/source/win/window.hxx b/avmedia/source/win/window.hxx new file mode 100644 index 000000000..39d5aec62 --- /dev/null +++ b/avmedia/source/win/window.hxx @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <WinDef.h> + +#include "wincommon.hxx" +#include <cppuhelper/implbase.hxx> +#include <comphelper/multicontainer2.hxx> + +#include <com/sun/star/media/XPlayerWindow.hpp> + +struct IVideoWindow; + +namespace avmedia::win { + +class Player; + + +class Window : public ::cppu::WeakImplHelper< css::media::XPlayerWindow, + css::lang::XServiceInfo > +{ +public: + + Window( Player& rPlayer ); + ~Window() override; + + bool create( const css::uno::Sequence< css::uno::Any >& aArguments ); + void processGraphEvent(); + void updatePointer(); + + // XPlayerWindow + virtual void SAL_CALL update( ) override; + virtual sal_Bool SAL_CALL setZoomLevel( css::media::ZoomLevel ZoomLevel ) override; + virtual css::media::ZoomLevel SAL_CALL getZoomLevel( ) override; + virtual void SAL_CALL setPointerType( sal_Int32 nPointerType ) override; + + // XWindow + virtual void SAL_CALL setPosSize( sal_Int32 X, sal_Int32 Y, sal_Int32 Width, sal_Int32 Height, sal_Int16 Flags ) override; + virtual css::awt::Rectangle SAL_CALL getPosSize( ) override; + virtual void SAL_CALL setVisible( sal_Bool Visible ) override; + virtual void SAL_CALL setEnable( sal_Bool Enable ) override; + virtual void SAL_CALL setFocus( ) override; + virtual void SAL_CALL addWindowListener( const css::uno::Reference< css::awt::XWindowListener >& xListener ) override; + virtual void SAL_CALL removeWindowListener( const css::uno::Reference< css::awt::XWindowListener >& xListener ) override; + virtual void SAL_CALL addFocusListener( const css::uno::Reference< css::awt::XFocusListener >& xListener ) override; + virtual void SAL_CALL removeFocusListener( const css::uno::Reference< css::awt::XFocusListener >& xListener ) override; + virtual void SAL_CALL addKeyListener( const css::uno::Reference< css::awt::XKeyListener >& xListener ) override; + virtual void SAL_CALL removeKeyListener( const css::uno::Reference< css::awt::XKeyListener >& xListener ) override; + virtual void SAL_CALL addMouseListener( const css::uno::Reference< css::awt::XMouseListener >& xListener ) override; + virtual void SAL_CALL removeMouseListener( const css::uno::Reference< css::awt::XMouseListener >& xListener ) override; + virtual void SAL_CALL addMouseMotionListener( const css::uno::Reference< css::awt::XMouseMotionListener >& xListener ) override; + virtual void SAL_CALL removeMouseMotionListener( const css::uno::Reference< css::awt::XMouseMotionListener >& xListener ) override; + virtual void SAL_CALL addPaintListener( const css::uno::Reference< css::awt::XPaintListener >& xListener ) override; + virtual void SAL_CALL removePaintListener( const css::uno::Reference< css::awt::XPaintListener >& xListener ) override; + + // XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +public: + + void fireMousePressedEvent( const css::awt::MouseEvent& rEvt ); + void fireMouseReleasedEvent( const css::awt::MouseEvent& rEvt ); + void fireMouseMovedEvent( const css::awt::MouseEvent& rEvt ); + void fireKeyPressedEvent( const css::awt::KeyEvent& rEvt ); + void fireKeyReleasedEvent( const css::awt::KeyEvent& rEvt ); + void fireSetFocusEvent( const css::awt::FocusEvent& rEvt ); + HWND getParentWnd() const { return mnParentWnd; } + +private: + + ::osl::Mutex maMutex; + comphelper::OMultiTypeInterfaceContainerHelper2 maListeners; + css::media::ZoomLevel meZoomLevel; + Player& mrPlayer; + HWND mnFrameWnd; + HWND mnParentWnd; + int mnPointerType; + + void ImplLayoutVideoWindow(); +}; + +} // namespace avmedia::win + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |