slideshow/CppunitTest_slideshow_engine.mk                       |   50 ++++
 slideshow/Library_slideshow.mk                                  |    4 
 slideshow/Module_slideshow.mk                                   |    1 
 slideshow/qa/engine/data/video-loop.pptx                        |binary
 slideshow/qa/engine/engine.cxx                                  |  102 
++++++++++
 slideshow/source/engine/animationnodes/animationcommandnode.cxx |   97 
+++++++++
 slideshow/source/engine/animationnodes/animationcommandnode.hxx |   10 
 slideshow/source/engine/shapes/externalshapebase.cxx            |    1 
 slideshow/source/engine/shapes/externalshapebase.hxx            |    2 
 slideshow/source/engine/shapes/mediashape.cxx                   |    8 
 slideshow/source/engine/shapes/viewmediashape.cxx               |    8 
 slideshow/source/engine/shapes/viewmediashape.hxx               |    2 
 slideshow/source/inc/iexternalmediashapebase.hxx                |    2 
 slideshow/source/inc/slideshowdllapi.h                          |   20 +
 14 files changed, 306 insertions(+), 1 deletion(-)

New commits:
commit a49d5891464662385108ee3a9813525e237ed714
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Fri Sep 2 10:27:05 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Tue Sep 6 09:29:51 2022 +0200

    tdf#149969 slideshow: handle loop from the animation of a media shape
    
    Impress supported video looping on media shapes, while PowerPoint
    supports this as part of the animation tree for the slide. To be more
    specific, it seems in case there is play command, then it looks up if
    there is a media node of the timing tree that has the current shape as
    its target, and checks if the looping is enabled for this shape in this
    media node.
    
    The PPTX import already creates an (audio) node for the media shape and
    we have UNO API to set/get a RepatCount attribute on this.
    
    Rather than tweaking the PPTX import to create a different doc model,
    just extend the rendering so in case the shape doesn't enable looping we
    can also notice that an animation requested looping.
    
    The PPTX export doesn't write a media node for videos at the moment, so
    that still needs work.
    
    (cherry picked from commit d0176d65de40b1ea53b4fcbc0f607911fa64d8e5)
    
    Change-Id: Ifc6600be760a954230243fd8151736656958a429
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139398
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/slideshow/CppunitTest_slideshow_engine.mk 
b/slideshow/CppunitTest_slideshow_engine.mk
new file mode 100644
index 000000000000..a88531df49f7
--- /dev/null
+++ b/slideshow/CppunitTest_slideshow_engine.mk
@@ -0,0 +1,50 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# 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/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,slideshow_engine))
+
+$(eval $(call gb_CppunitTest_use_externals,slideshow_engine,\
+       boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,slideshow_engine, \
+    slideshow/qa/engine/engine \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,slideshow_engine, \
+    comphelper \
+    cppu \
+    slideshow \
+    sal \
+    test \
+    unotest \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,slideshow_engine))
+
+$(eval $(call gb_CppunitTest_set_include,slideshow_engine,\
+    -I$(SRCDIR)/slideshow/source/inc \
+    -I$(SRCDIR)/slideshow/source/engine/animationnodes \
+    $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,slideshow_engine))
+$(eval $(call gb_CppunitTest_use_vcl,slideshow_engine))
+
+$(eval $(call gb_CppunitTest_use_rdb,slideshow_engine,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,slideshow_engine,\
+       officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,slideshow_engine))
+
+# vim: set noet sw=4 ts=4:
diff --git a/slideshow/Library_slideshow.mk b/slideshow/Library_slideshow.mk
index de589f210602..0c2db10ee5d3 100644
--- a/slideshow/Library_slideshow.mk
+++ b/slideshow/Library_slideshow.mk
@@ -32,6 +32,10 @@ $(eval $(call gb_Library_use_externals,slideshow,\
  ))
 endif
 
+$(eval $(call gb_Library_add_defs,slideshow,\
+    -DSLIDESHOW_DLLIMPLEMENTATION \
+))
+
 $(eval $(call gb_Library_use_sdk_api,slideshow))
 
 $(eval $(call gb_Library_use_libraries,slideshow,\
diff --git a/slideshow/Module_slideshow.mk b/slideshow/Module_slideshow.mk
index 51b832cfa4f1..9cbaff1eb7e7 100644
--- a/slideshow/Module_slideshow.mk
+++ b/slideshow/Module_slideshow.mk
@@ -21,6 +21,7 @@ endif
 
 $(eval $(call gb_Module_add_check_targets,slideshow,\
        CppunitTest_slideshow \
+       CppunitTest_slideshow_engine \
 ))
 
 # vim: set noet sw=4 ts=4:
diff --git a/slideshow/qa/engine/data/video-loop.pptx 
b/slideshow/qa/engine/data/video-loop.pptx
new file mode 100644
index 000000000000..4cb7e20b7428
Binary files /dev/null and b/slideshow/qa/engine/data/video-loop.pptx differ
diff --git a/slideshow/qa/engine/engine.cxx b/slideshow/qa/engine/engine.cxx
new file mode 100644
index 000000000000..09e56f73d3f8
--- /dev/null
+++ b/slideshow/qa/engine/engine.cxx
@@ -0,0 +1,102 @@
+/* -*- 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 <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/animations/XAnimationNodeSupplier.hpp>
+#include <com/sun/star/animations/AnimationNodeType.hpp>
+
+#include <comphelper/embeddedobjectcontainer.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <animationcommandnode.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Covers slideshow/source/engine/ fixes.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest
+{
+private:
+    uno::Reference<lang::XComponent> mxComponent;
+
+public:
+    void setUp() override;
+    void tearDown() override;
+    uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+void Test::setUp()
+{
+    test::BootstrapFixture::setUp();
+
+    mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void Test::tearDown()
+{
+    if (mxComponent.is())
+        mxComponent->dispose();
+
+    test::BootstrapFixture::tearDown();
+}
+
+/// Get the first command node in the animation tree of the page, assuming 
that it's the first child
+/// (recursively).
+uno::Reference<animations::XCommand>
+GetFirstCommandNodeOfPage(const uno::Reference<drawing::XDrawPage>& xPage)
+{
+    uno::Reference<animations::XAnimationNodeSupplier> 
xAnimationNodeSupplier(xPage,
+                                                                              
uno::UNO_QUERY);
+    uno::Reference<animations::XAnimationNode> xNode = 
xAnimationNodeSupplier->getAnimationNode();
+    while (true)
+    {
+        if (xNode->getType() == animations::AnimationNodeType::COMMAND)
+        {
+            break;
+        }
+        uno::Reference<container::XEnumerationAccess> xEnumAccess(xNode, 
uno::UNO_QUERY);
+        uno::Reference<container::XEnumeration> xNodes = 
xEnumAccess->createEnumeration();
+        xNode.set(xNodes->nextElement(), uno::UNO_QUERY);
+    }
+    uno::Reference<animations::XCommand> xRet(xNode, uno::UNO_QUERY);
+    return xRet;
+}
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testLoopingFromAnimation)
+{
+    // Given a document with a looping video, the looping is defined as part 
of its auto-play
+    // animation (and not on the media shape):
+    OUString aURL = 
m_directories.getURLFromSrc(u"slideshow/qa/engine/data/video-loop.pptx");
+    getComponent().set(loadFromDesktop(aURL));
+    uno::Reference<drawing::XDrawPagesSupplier> xDoc(getComponent(), 
uno::UNO_QUERY);
+    uno::Reference<drawing::XDrawPage> 
xPage(xDoc->getDrawPages()->getByIndex(0), uno::UNO_QUERY);
+    uno::Reference<animations::XCommand> xCommandNode = 
GetFirstCommandNodeOfPage(xPage);
+    uno::Reference<drawing::XShape> xShape(xPage->getByIndex(0), 
uno::UNO_QUERY);
+
+    // When determining if the video should be looping or not:
+    bool bLooping
+        = 
slideshow::internal::AnimationCommandNode::GetLoopingFromAnimation(xCommandNode,
 xShape);
+
+    // Then make sure that we detect the looping is wanted:
+    CPPUNIT_ASSERT(bLooping);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationcommandnode.cxx 
b/slideshow/source/engine/animationnodes/animationcommandnode.cxx
index 2e8507e3a5f5..df70cb1ab37c 100644
--- a/slideshow/source/engine/animationnodes/animationcommandnode.cxx
+++ b/slideshow/source/engine/animationnodes/animationcommandnode.cxx
@@ -19,8 +19,14 @@
 
 
 #include <com/sun/star/presentation/EffectCommands.hpp>
+#include <com/sun/star/presentation/EffectNodeType.hpp>
+#include <com/sun/star/animations/AnimationNodeType.hpp>
+#include <com/sun/star/animations/XAudio.hpp>
+#include <com/sun/star/animations/Timing.hpp>
 #include <com/sun/star/beans/PropertyValue.hpp>
 
+#include <comphelper/sequenceashashmap.hxx>
+
 #include "animationcommandnode.hxx"
 #include <eventmultiplexer.hxx>
 #include <delayevent.hxx>
@@ -28,6 +34,52 @@
 
 using namespace com::sun::star;
 
+namespace
+{
+/// Determines if this is the root of the timing node tree.
+bool IsTimingRootNode(const uno::Reference<animations::XAnimationNode>& xNode)
+{
+    uno::Sequence<beans::NamedValue> aUserData = xNode->getUserData();
+    comphelper::SequenceAsHashMap aMap(aUserData);
+    auto it = aMap.find("node-type");
+    if (it == aMap.end())
+    {
+        return false;
+    }
+
+    sal_Int16 nNodeType{};
+    if (!(it->second >>= nNodeType))
+    {
+        return false;
+    }
+
+    return nNodeType == css::presentation::EffectNodeType::TIMING_ROOT;
+}
+
+/// Walks the parent chain of xNode and stops at the timing root.
+uno::Reference<animations::XAnimationNode>
+GetTimingRoot(const uno::Reference<animations::XAnimationNode>& xNode)
+{
+    uno::Reference<animations::XAnimationNode> xParent(xNode->getParent(), 
uno::UNO_QUERY);
+    while (true)
+    {
+        if (!xParent.is())
+        {
+            break;
+        }
+
+        if (IsTimingRootNode(xParent))
+        {
+            return xParent;
+        }
+
+        xParent.set(xParent->getParent(), uno::UNO_QUERY);
+    }
+
+    return {};
+}
+}
+
 namespace slideshow::internal {
 
 namespace EffectCommands = css::presentation::EffectCommands;
@@ -43,6 +95,7 @@ AnimationCommandNode::AnimationCommandNode( 
uno::Reference<animations::XAnimatio
                                               uno::UNO_QUERY );
     ShapeSharedPtr pShape( 
getContext().mpSubsettableShapeManager->lookupShape( xShape ) );
     mpShape = ::std::dynamic_pointer_cast< IExternalMediaShapeBase >( pShape );
+    mxShape = xShape;
 }
 
 void AnimationCommandNode::dispose()
@@ -52,6 +105,42 @@ void AnimationCommandNode::dispose()
     BaseNode::dispose();
 }
 
+bool AnimationCommandNode::GetLoopingFromAnimation(
+    const uno::Reference<animations::XCommand>& xCommandNode,
+    const uno::Reference<drawing::XShape>& xShape)
+{
+    uno::Reference<animations::XAnimationNode> xTimingRoot = 
GetTimingRoot(xCommandNode);
+    uno::Reference<container::XEnumerationAccess> xEnumAccess(xTimingRoot, 
uno::UNO_QUERY);
+    if (!xEnumAccess.is())
+    {
+        return false;
+    }
+
+    uno::Reference<container::XEnumeration> xNodes = 
xEnumAccess->createEnumeration();
+    while (xNodes->hasMoreElements())
+    {
+        uno::Reference<animations::XAnimationNode> 
xNode(xNodes->nextElement(), uno::UNO_QUERY);
+        if (xNode->getType() != animations::AnimationNodeType::AUDIO)
+        {
+            continue;
+        }
+
+        uno::Reference<animations::XAudio> xAudio(xNode, uno::UNO_QUERY);
+        uno::Reference<drawing::XShape> xSource(xAudio->getSource(), 
uno::UNO_QUERY);
+        if (xSource != xShape)
+        {
+            continue;
+        }
+
+        animations::Timing eTiming{};
+        if ((xAudio->getRepeatCount() >>= eTiming) && eTiming == 
animations::Timing_INDEFINITE)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
 void AnimationCommandNode::activate_st()
 {
     switch( mxCommandNode->getCommand() ) {
@@ -71,6 +160,14 @@ void AnimationCommandNode::activate_st()
         if( mpShape )
         {
             mpShape->setMediaTime(fMediaTime/1000.0);
+
+            if (AnimationCommandNode::GetLoopingFromAnimation(mxCommandNode, 
mxShape))
+            {
+                // If looping is requested from the animation, then that has 
priority over the
+                // looping from the shape itself.
+                mpShape->setLooping(true);
+            }
+
             mpShape->play();
         }
         break;
diff --git a/slideshow/source/engine/animationnodes/animationcommandnode.hxx 
b/slideshow/source/engine/animationnodes/animationcommandnode.hxx
index bcb61f1ddd3f..96ca886f8f2c 100644
--- a/slideshow/source/engine/animationnodes/animationcommandnode.hxx
+++ b/slideshow/source/engine/animationnodes/animationcommandnode.hxx
@@ -20,6 +20,7 @@
 #ifndef 
INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX
 #define 
INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX
 
+#include <slideshowdllapi.h>
 #include <basecontainernode.hxx>
 #include <iexternalmediashapebase.hxx>
 #include <com/sun/star/animations/XCommand.hpp>
@@ -31,7 +32,7 @@ namespace slideshow::internal {
     This animation node encapsulates a command. Not yet implemented:
     verb & custom.
 */
-class AnimationCommandNode : public BaseNode
+class SLIDESHOW_DLLPUBLIC AnimationCommandNode : public BaseNode
 {
 public:
     AnimationCommandNode(
@@ -39,6 +40,12 @@ public:
         ::std::shared_ptr<BaseContainerNode> const& pParent,
         NodeContext const& rContext );
 
+    /// Assuming that xCommandNode is a play command, determines if an audio 
node wants looping when
+    /// xShape plays.
+    static bool
+    GetLoopingFromAnimation(const 
css::uno::Reference<css::animations::XCommand>& xCommandNode,
+                            const css::uno::Reference<css::drawing::XShape>& 
xShape);
+
 protected:
     virtual void dispose() override;
 
@@ -49,6 +56,7 @@ private:
 private:
     IExternalMediaShapeBaseSharedPtr mpShape;
     css::uno::Reference<css::animations::XCommand > mxCommandNode;
+    css::uno::Reference<css::drawing::XShape> mxShape;
 };
 
 } // namespace slideshow::internal
diff --git a/slideshow/source/engine/shapes/externalshapebase.cxx 
b/slideshow/source/engine/shapes/externalshapebase.cxx
index aeff0f412cc9..440b10d950ed 100644
--- a/slideshow/source/engine/shapes/externalshapebase.cxx
+++ b/slideshow/source/engine/shapes/externalshapebase.cxx
@@ -143,6 +143,7 @@ namespace slideshow::internal
             implSetIntrinsicAnimationTime(fTime);
         }
 
+        void ExternalShapeBase::setLooping(bool bLooping) { 
implSetLooping(bLooping); }
 
         bool ExternalShapeBase::update() const
         {
diff --git a/slideshow/source/engine/shapes/externalshapebase.hxx 
b/slideshow/source/engine/shapes/externalshapebase.hxx
index 260d8630e170..d8c208db2a99 100644
--- a/slideshow/source/engine/shapes/externalshapebase.hxx
+++ b/slideshow/source/engine/shapes/externalshapebase.hxx
@@ -65,6 +65,7 @@ namespace slideshow::internal
             virtual void pause() override;
             virtual bool isPlaying() const override;
             virtual void setMediaTime(double) override;
+            void setLooping(bool bLooping) override;
 
             // render methods
 
@@ -108,6 +109,7 @@ namespace slideshow::internal
             virtual bool implIsIntrinsicAnimationPlaying() const = 0;
             /// override in derived class to set media time
             virtual void implSetIntrinsicAnimationTime(double) = 0;
+            virtual void implSetLooping(bool /*bLooping*/) {}
 
 
             /// The associated XShape
diff --git a/slideshow/source/engine/shapes/mediashape.cxx 
b/slideshow/source/engine/shapes/mediashape.cxx
index 64dfd2d0548d..33dffd06447c 100644
--- a/slideshow/source/engine/shapes/mediashape.cxx
+++ b/slideshow/source/engine/shapes/mediashape.cxx
@@ -81,6 +81,7 @@ namespace slideshow::internal
             virtual void implPauseIntrinsicAnimation() override;
             virtual bool implIsIntrinsicAnimationPlaying() const override;
             virtual void implSetIntrinsicAnimationTime(double) override;
+            void implSetLooping(bool bLooping) override;
 
             /// the list of active view shapes (one for each registered view 
layer)
             typedef ::std::vector< ViewMediaShapeSharedPtr > 
ViewMediaShapeVector;
@@ -234,6 +235,13 @@ namespace slideshow::internal
                 pViewMediaShape->setMediaTime( fTime );
         }
 
+        void MediaShape::implSetLooping(bool bLooping)
+        {
+            for (const auto& pViewMediaShape : maViewMediaShapes)
+            {
+                pViewMediaShape->setLooping(bLooping);
+            }
+        }
 
         ShapeSharedPtr createMediaShape(
             const uno::Reference< drawing::XShape >& xShape,
diff --git a/slideshow/source/engine/shapes/viewmediashape.cxx 
b/slideshow/source/engine/shapes/viewmediashape.cxx
index 0b24b12b2812..229599fac296 100644
--- a/slideshow/source/engine/shapes/viewmediashape.cxx
+++ b/slideshow/source/engine/shapes/viewmediashape.cxx
@@ -143,6 +143,14 @@ namespace slideshow::internal
                 mxPlayer->setMediaTime(fTime);
         }
 
+        void ViewMediaShape::setLooping(bool bLooping)
+        {
+            if (mxPlayer.is())
+            {
+                mxPlayer->setPlaybackLoop(bLooping);
+            }
+        }
+
         bool ViewMediaShape::render( const ::basegfx::B2DRectangle& rBounds ) 
const
         {
 #if !HAVE_FEATURE_AVMEDIA
diff --git a/slideshow/source/engine/shapes/viewmediashape.hxx 
b/slideshow/source/engine/shapes/viewmediashape.hxx
index f8b672a00563..69445a8a57d6 100644
--- a/slideshow/source/engine/shapes/viewmediashape.hxx
+++ b/slideshow/source/engine/shapes/viewmediashape.hxx
@@ -112,6 +112,8 @@ namespace slideshow::internal
              */
             void setMediaTime(double fTime);
 
+            void setLooping(bool bLooping);
+
             // render methods
 
 
diff --git a/slideshow/source/inc/iexternalmediashapebase.hxx 
b/slideshow/source/inc/iexternalmediashapebase.hxx
index 156b0b2dd7ce..1a70ae093652 100644
--- a/slideshow/source/inc/iexternalmediashapebase.hxx
+++ b/slideshow/source/inc/iexternalmediashapebase.hxx
@@ -72,6 +72,8 @@ namespace slideshow::internal
             presented
              */
             virtual void setMediaTime(double fTime) = 0;
+
+            virtual void setLooping(bool /*bLooping*/){};
         };
 
         typedef ::std::shared_ptr< IExternalMediaShapeBase > 
IExternalMediaShapeBaseSharedPtr;
diff --git a/slideshow/source/inc/slideshowdllapi.h 
b/slideshow/source/inc/slideshowdllapi.h
new file mode 100644
index 000000000000..048a83dd271a
--- /dev/null
+++ b/slideshow/source/inc/slideshowdllapi.h
@@ -0,0 +1,20 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <sal/types.h>
+
+#if defined(SLIDESHOW_DLLIMPLEMENTATION)
+#define SLIDESHOW_DLLPUBLIC SAL_DLLPUBLIC_EXPORT
+#else
+#define SLIDESHOW_DLLPUBLIC SAL_DLLPUBLIC_IMPORT
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to