[skottie] Expose composition markers to embedders

Add a MarkerObserver interface (to replace the current
AnnotationObserver), and update CustomPropertyManager to intercept both
properties and markers.

TBR=
Change-Id: If79de419066916bc596316f0a551c75564069239
Reviewed-on: https://skia-review.googlesource.com/c/173766
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 611e67f..5d11347 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -26,6 +26,7 @@
 #include "SkGradientShader.h"
 #include "SkImageInfo.h"
 #include "SkImageShader.h"
+#include "SkMakeUnique.h"
 #include "SkMaskFilter.h"
 #include "SkPaint.h"
 #include "SkParsePath.h"
@@ -167,14 +168,13 @@
 class ManagedAnimation final : public SkRefCnt {
 public:
     static sk_sp<ManagedAnimation> Make(const std::string& json) {
-        auto mgrBuilder = sk_make_sp<skottie_utils::CustomPropertyManagerBuilder>();
+        auto mgr = skstd::make_unique<skottie_utils::CustomPropertyManager>();
         auto animation = skottie::Animation::Builder()
-                            .setPropertyObserver(mgrBuilder)
+                            .setPropertyObserver(mgr->getPropertyObserver())
                             .make(json.c_str(), json.size());
 
         return animation
-            ? sk_sp<ManagedAnimation>(
-                  new ManagedAnimation(std::move(animation), mgrBuilder->build()))
+            ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
             : nullptr;
     }
 
diff --git a/modules/skottie/gm/SkottieGM.cpp b/modules/skottie/gm/SkottieGM.cpp
index 8febf25..c8a4a27 100644
--- a/modules/skottie/gm/SkottieGM.cpp
+++ b/modules/skottie/gm/SkottieGM.cpp
@@ -104,11 +104,10 @@
 
     void onOnceBeforeDraw() override {
         if (auto stream = GetResourceAsStream("skottie/skottie_sample_search.json")) {
-            auto propBuilder = sk_make_sp<CustomPropertyManagerBuilder>();
+            fPropManager = skstd::make_unique<CustomPropertyManager>();
             fAnimation   = Animation::Builder()
-                              .setPropertyObserver(propBuilder)
+                              .setPropertyObserver(fPropManager->getPropertyObserver())
                               .make(stream.get());
-            fPropManager = propBuilder->build();
             fColors      = fPropManager->getColorProps();
         }
     }
diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h
index 2f832bc..15b175e 100644
--- a/modules/skottie/include/Skottie.h
+++ b/modules/skottie/include/Skottie.h
@@ -104,25 +104,12 @@
 };
 
 /**
- * Interface for receiving custom annotation events at Animation build time.
- *
- * Annotations are parsed as a top-level key-value string dictionary, e.g.:
- *
- * {
- *   ...
- *
- *   "annotations": {
- *     "key1": "foo",
- *     "key2": "bar",
- *     "key3": "baz"
- *   },
- *
- *   ...
- * }
+ * Interface for receiving AE composition markers at Animation build time.
  */
-class SK_API AnnotationObserver : public SkRefCnt {
+class SK_API MarkerObserver : public SkRefCnt {
 public:
-    virtual void onAnnotation(const char key[], const char value[]) = 0;
+    // t0,t1 are in the Animation::seek() domain.
+    virtual void onMarker(const char name[], float t0, float t1) = 0;
 };
 
 class SK_API Animation : public SkNVRefCnt<Animation> {
@@ -172,9 +159,9 @@
         Builder& setLogger(sk_sp<Logger>);
 
         /**
-         * Register an AnnotationObserver with this builder.
+         * Register a MarkerObserver with this builder.
          */
-        Builder& setAnnotationObserver(sk_sp<AnnotationObserver>);
+        Builder& setMarkerObserver(sk_sp<MarkerObserver>);
 
         /**
          * Animation factories.
@@ -184,12 +171,12 @@
         sk_sp<Animation> makeFromFile(const char path[]);
 
     private:
-        sk_sp<ResourceProvider>   fResourceProvider;
-        sk_sp<SkFontMgr>          fFontMgr;
-        sk_sp<PropertyObserver>   fPropertyObserver;
-        sk_sp<Logger>             fLogger;
-        sk_sp<AnnotationObserver> fAnnotationObserver;
-        Stats                     fStats;
+        sk_sp<ResourceProvider> fResourceProvider;
+        sk_sp<SkFontMgr>        fFontMgr;
+        sk_sp<PropertyObserver> fPropertyObserver;
+        sk_sp<Logger>           fLogger;
+        sk_sp<MarkerObserver>   fMarkerObserver;
+        Stats                   fStats;
     };
 
     /**
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 66aee73..7b31d99 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -155,20 +155,20 @@
 
 AnimationBuilder::AnimationBuilder(sk_sp<ResourceProvider> rp, sk_sp<SkFontMgr> fontmgr,
                                    sk_sp<PropertyObserver> pobserver, sk_sp<Logger> logger,
-                                   sk_sp<AnnotationObserver> aobserver,
+                                   sk_sp<MarkerObserver> mobserver,
                                    Animation::Builder::Stats* stats,
                                    float duration, float framerate)
     : fResourceProvider(std::move(rp))
     , fLazyFontMgr(std::move(fontmgr))
     , fPropertyObserver(std::move(pobserver))
     , fLogger(std::move(logger))
-    , fAnnotationObserver(std::move(aobserver))
+    , fMarkerObserver(std::move(mobserver))
     , fStats(stats)
     , fDuration(duration)
     , fFrameRate(framerate) {}
 
 std::unique_ptr<sksg::Scene> AnimationBuilder::parse(const skjson::ObjectValue& jroot) {
-    this->dispatchAnnotations(jroot["annotations"]);
+    this->dispatchMarkers(jroot["markers"]);
 
     this->parseAssets(jroot["assets"]);
     this->parseFonts(jroot["fonts"], jroot["chars"]);
@@ -193,16 +193,31 @@
     }
 }
 
-void AnimationBuilder::dispatchAnnotations(const skjson::ObjectValue* jannotations) const {
-    if (!fAnnotationObserver || !jannotations) {
+void AnimationBuilder::dispatchMarkers(const skjson::ArrayValue* jmarkers) const {
+    if (!fMarkerObserver || !jmarkers) {
         return;
     }
 
-    for (const auto& a : *jannotations) {
-        if (const skjson::StringValue* value = a.fValue) {
-            fAnnotationObserver->onAnnotation(a.fKey.begin(), value->begin());
+    // For frame-number -> t conversions.
+    const auto frameRatio = 1 / (fFrameRate * fDuration);
+
+    for (const skjson::ObjectValue* m : *jmarkers) {
+        if (!m) continue;
+
+        const skjson::StringValue* name = (*m)["cm"];
+        const auto time = ParseDefault((*m)["tm"], -1.0f),
+               duration = ParseDefault((*m)["dr"], -1.0f);
+
+        if (name && time >= 0 && duration >= 0) {
+            fMarkerObserver->onMarker(
+                        name->begin(),
+                        // "tm" is in frames
+                        time * frameRatio,
+                        // ... as is "dr"
+                        (time + duration) * frameRatio
+            );
         } else {
-            this->log(Logger::Level::kWarning, &a.fValue, "Ignoring unexpected annotation value.");
+            this->log(Logger::Level::kWarning, m, "Ignoring unexpected marker.");
         }
     }
 }
@@ -296,8 +311,8 @@
     return *this;
 }
 
-Animation::Builder& Animation::Builder::setAnnotationObserver(sk_sp<AnnotationObserver> aobserver) {
-    fAnnotationObserver = std::move(aobserver);
+Animation::Builder& Animation::Builder::setMarkerObserver(sk_sp<MarkerObserver> mobserver) {
+    fMarkerObserver = std::move(mobserver);
     return *this;
 }
 
@@ -371,7 +386,7 @@
     internal::AnimationBuilder builder(std::move(resolvedProvider), fFontMgr,
                                        std::move(fPropertyObserver),
                                        std::move(fLogger),
-                                       std::move(fAnnotationObserver),
+                                       std::move(fMarkerObserver),
                                        &fStats, duration, fps);
     auto scene = builder.parse(json);
 
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 0c50c91..b3c15e5 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -44,7 +44,7 @@
 class AnimationBuilder final : public SkNoncopyable {
 public:
     AnimationBuilder(sk_sp<ResourceProvider>, sk_sp<SkFontMgr>, sk_sp<PropertyObserver>,
-                     sk_sp<Logger>, sk_sp<AnnotationObserver>,
+                     sk_sp<Logger>, sk_sp<MarkerObserver>,
                      Animation::Builder::Stats*, float duration, float framerate);
 
     std::unique_ptr<sksg::Scene> parse(const skjson::ObjectValue&);
@@ -87,7 +87,7 @@
     void parseFonts (const skjson::ObjectValue* jfonts,
                      const skjson::ArrayValue* jchars);
 
-    void dispatchAnnotations(const skjson::ObjectValue*) const;
+    void dispatchMarkers(const skjson::ArrayValue*) const;
 
     sk_sp<sksg::RenderNode> attachComposition(const skjson::ObjectValue&, AnimatorScope*) const;
     sk_sp<sksg::RenderNode> attachLayer(const skjson::ObjectValue*, AttachLayerContext*) const;
@@ -165,7 +165,7 @@
     LazyResolveFontMgr         fLazyFontMgr;
     sk_sp<PropertyObserver>    fPropertyObserver;
     sk_sp<Logger>              fLogger;
-    sk_sp<AnnotationObserver>  fAnnotationObserver;
+    sk_sp<MarkerObserver>      fMarkerObserver;
     Animation::Builder::Stats* fStats;
     const float                fDuration,
                                fFrameRate;
diff --git a/modules/skottie/src/SkottieTest.cpp b/modules/skottie/src/SkottieTest.cpp
index 268b5dd..b29d735 100644
--- a/modules/skottie/src/SkottieTest.cpp
+++ b/modules/skottie/src/SkottieTest.cpp
@@ -160,9 +160,9 @@
                                      "v": "5.2.1",
                                      "w": 100,
                                      "h": 100,
-                                     "fr": 1,
+                                     "fr": 10,
                                      "ip": 0,
-                                     "op": 1,
+                                     "op": 100,
                                      "layers": [
                                        {
                                          "ty": 1,
@@ -177,36 +177,43 @@
                                          "sc": "#ffffff"
                                        }
                                      ],
-                                     "annotations": {
-                                       "key1": "foo",
-                                       "key2": "bar",
-                                       "key3": "baz"
-                                     }
+                                     "markers": [
+                                       {
+                                           "cm": "marker_1",
+                                           "dr": 25,
+                                           "tm": 25
+                                       },
+                                       {
+                                           "cm": "marker_2",
+                                           "dr": 0,
+                                           "tm": 75
+                                       }
+                                     ]
                                    })";
 
-    class TestAnnotationObserver final : public AnnotationObserver {
+    class TestMarkerObserver final : public MarkerObserver {
     public:
-        void onAnnotation(const char key[], const char value[]) override {
-            fAnnotations.push_back(std::make_tuple(key, value));
+        void onMarker(const char name[], float t0, float t1) override {
+            fMarkers.push_back(std::make_tuple(name, t0, t1));
         }
 
-        std::vector<std::tuple<std::string, std::string>> fAnnotations;
+        std::vector<std::tuple<std::string, float, float>> fMarkers;
     };
 
     SkMemoryStream stream(json, strlen(json));
-    auto observer = sk_make_sp<TestAnnotationObserver>();
+    auto observer = sk_make_sp<TestMarkerObserver>();
 
     auto animation = skottie::Animation::Builder()
-            .setAnnotationObserver(observer)
+            .setMarkerObserver(observer)
             .make(&stream);
 
     REPORTER_ASSERT(reporter, animation);
 
-    REPORTER_ASSERT(reporter, observer->fAnnotations.size() == 3ul);
-    REPORTER_ASSERT(reporter, std::get<0>(observer->fAnnotations[0]) == "key1");
-    REPORTER_ASSERT(reporter, std::get<1>(observer->fAnnotations[0]) == "foo");
-    REPORTER_ASSERT(reporter, std::get<0>(observer->fAnnotations[1]) == "key2");
-    REPORTER_ASSERT(reporter, std::get<1>(observer->fAnnotations[1]) == "bar");
-    REPORTER_ASSERT(reporter, std::get<0>(observer->fAnnotations[2]) == "key3");
-    REPORTER_ASSERT(reporter, std::get<1>(observer->fAnnotations[2]) == "baz");
+    REPORTER_ASSERT(reporter, observer->fMarkers.size() == 2ul);
+    REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[0]) == "marker_1");
+    REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[0]) == 0.25f);
+    REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[0]) == 0.50f);
+    REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[1]) == "marker_2");
+    REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[1]) == 0.75f);
+    REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[1]) == 0.75f);
 }
diff --git a/modules/skottie/utils/SkottieUtils.cpp b/modules/skottie/utils/SkottieUtils.cpp
index 92efc2f..4e28a41 100644
--- a/modules/skottie/utils/SkottieUtils.cpp
+++ b/modules/skottie/utils/SkottieUtils.cpp
@@ -60,52 +60,67 @@
     return MultiFrameImageAsset::Make(this->load(resource_path, resource_name));
 }
 
-CustomPropertyManagerBuilder::CustomPropertyManagerBuilder() = default;
-CustomPropertyManagerBuilder::~CustomPropertyManagerBuilder() = default;
+class CustomPropertyManager::PropertyInterceptor final : public skottie::PropertyObserver {
+public:
+    explicit PropertyInterceptor(CustomPropertyManager* mgr) : fMgr(mgr) {}
 
-std::unique_ptr<CustomPropertyManager> CustomPropertyManagerBuilder::build() {
-    return std::unique_ptr<CustomPropertyManager>(
-                new CustomPropertyManager(std::move(fColorMap),
-                                          std::move(fOpacityMap),
-                                          std::move(fTransformMap)));
-}
-
-void CustomPropertyManagerBuilder::onColorProperty(
-        const char node_name[],
-        const LazyHandle<skottie::ColorPropertyHandle>& c) {
-    const auto key = this->acceptProperty(node_name);
-    if (!key.empty()) {
-        fColorMap[key].push_back(c());
+    void onColorProperty(const char node_name[],
+                         const LazyHandle<skottie::ColorPropertyHandle>& c) override {
+        const auto key = fMgr->acceptKey(node_name);
+        if (!key.empty()) {
+            fMgr->fColorMap[key].push_back(c());
+        }
     }
-}
 
-void CustomPropertyManagerBuilder::onOpacityProperty(
-        const char node_name[],
-        const LazyHandle<skottie::OpacityPropertyHandle>& o) {
-    const auto key = this->acceptProperty(node_name);
-    if (!key.empty()) {
-        fOpacityMap[key].push_back(o());
+    void onOpacityProperty(const char node_name[],
+                           const LazyHandle<skottie::OpacityPropertyHandle>& o) override {
+        const auto key = fMgr->acceptKey(node_name);
+        if (!key.empty()) {
+            fMgr->fOpacityMap[key].push_back(o());
+        }
     }
-}
 
-void CustomPropertyManagerBuilder::onTransformProperty(
-        const char node_name[],
-        const LazyHandle<skottie::TransformPropertyHandle>& t) {
-    const auto key = this->acceptProperty(node_name);
-    if (!key.empty()) {
-        fTransformMap[key].push_back(t());
+    void onTransformProperty(const char node_name[],
+                             const LazyHandle<skottie::TransformPropertyHandle>& t) override {
+        const auto key = fMgr->acceptKey(node_name);
+        if (!key.empty()) {
+            fMgr->fTransformMap[key].push_back(t());
+        }
     }
-}
 
-CustomPropertyManager::CustomPropertyManager(PropMap<skottie::ColorPropertyHandle> cmap,
-                                             PropMap<skottie::OpacityPropertyHandle> omap,
-                                             PropMap<skottie::TransformPropertyHandle> tmap)
-    : fColorMap(std::move(cmap))
-    , fOpacityMap(std::move(omap))
-    , fTransformMap(std::move(tmap)) {}
+private:
+    CustomPropertyManager* fMgr;
+};
+
+class CustomPropertyManager::MarkerInterceptor final : public skottie::MarkerObserver {
+public:
+    explicit MarkerInterceptor(CustomPropertyManager* mgr) : fMgr(mgr) {}
+
+    void onMarker(const char name[], float t0, float t1) override {
+        const auto key = fMgr->acceptKey(name);
+        if (!key.empty()) {
+            fMgr->fMarkers.push_back({ std::move(key), t0, t1 });
+        }
+    }
+
+private:
+    CustomPropertyManager* fMgr;
+};
+
+CustomPropertyManager::CustomPropertyManager()
+    : fPropertyInterceptor(sk_make_sp<PropertyInterceptor>(this))
+    , fMarkerInterceptor(sk_make_sp<MarkerInterceptor>(this)) {}
 
 CustomPropertyManager::~CustomPropertyManager() = default;
 
+sk_sp<skottie::PropertyObserver> CustomPropertyManager::getPropertyObserver() const {
+    return fPropertyInterceptor;
+}
+
+sk_sp<skottie::MarkerObserver> CustomPropertyManager::getMarkerObserver() const {
+    return fMarkerInterceptor;
+}
+
 template <typename T>
 std::vector<CustomPropertyManager::PropKey>
 CustomPropertyManager::getProps(const PropMap<T>& container) const {
diff --git a/modules/skottie/utils/SkottieUtils.h b/modules/skottie/utils/SkottieUtils.h
index 23dec6c..cb28b5d 100644
--- a/modules/skottie/utils/SkottieUtils.h
+++ b/modules/skottie/utils/SkottieUtils.h
@@ -66,11 +66,12 @@
  *   - getters return all the managed property groups, and the first value within each of them
  *     (unchecked assumption: all properties within the same group have the same value)
  *
- * Use CustomPropertyManagerBuilder to filter nodes at animation build time, and instantiate a
- * CustomPropertyManager.
+ * Attach to an Animation::Builder using the utility methods below to intercept properties and
+ * markers at build time.
  */
 class CustomPropertyManager final {
 public:
+    CustomPropertyManager();
     ~CustomPropertyManager();
 
     using PropKey = std::string;
@@ -87,8 +88,32 @@
     skottie::TransformPropertyValue getTransform(const PropKey&) const;
     bool setTransform(const PropKey&, const skottie::TransformPropertyValue&);
 
+    struct MarkerInfo {
+        std::string name;
+        float       t0, t1;
+    };
+    const std::vector<MarkerInfo>& markers() const { return fMarkers; }
+
+    // Returns a property observer to be attached to an animation builder.
+    sk_sp<skottie::PropertyObserver> getPropertyObserver() const;
+
+    // Returns a marker observer to be attached to an animation builder.
+    sk_sp<skottie::MarkerObserver> getMarkerObserver() const;
+
 private:
-    friend class CustomPropertyManagerBuilder;
+    class PropertyInterceptor;
+    class MarkerInterceptor;
+
+    static std::string acceptKey(const char* name) {
+        static constexpr char kPrefix = '$';
+
+        return (name[0] == kPrefix && name[1] != '\0')
+            ? std::string(name + 1)
+            : std::string();
+    }
+
+    sk_sp<PropertyInterceptor> fPropertyInterceptor;
+    sk_sp<MarkerInterceptor>   fMarkerInterceptor;
 
     template <typename T>
     using PropGroup = std::vector<std::unique_ptr<T>>;
@@ -105,46 +130,10 @@
     template <typename V, typename T>
     bool set(const PropKey&, const V&, const PropMap<T>& container);
 
-    CustomPropertyManager(PropMap<skottie::ColorPropertyHandle>,
-                          PropMap<skottie::OpacityPropertyHandle>,
-                          PropMap<skottie::TransformPropertyHandle>);
-
     PropMap<skottie::ColorPropertyHandle>     fColorMap;
     PropMap<skottie::OpacityPropertyHandle>   fOpacityMap;
     PropMap<skottie::TransformPropertyHandle> fTransformMap;
-};
-
-/**
- * A builder for CustomPropertyManager.  Only accepts node names starting with '$'.
- */
-class CustomPropertyManagerBuilder final : public skottie::PropertyObserver {
-public:
-    CustomPropertyManagerBuilder();
-    ~CustomPropertyManagerBuilder() override;
-
-    std::unique_ptr<CustomPropertyManager> build();
-
-    void onColorProperty    (const char node_name[],
-                             const LazyHandle<skottie::ColorPropertyHandle>&) override;
-    void onOpacityProperty  (const char node_name[],
-                             const LazyHandle<skottie::OpacityPropertyHandle>&) override;
-    void onTransformProperty(const char node_name[],
-                             const LazyHandle<skottie::TransformPropertyHandle>&) override;
-
-private:
-    std::string acceptProperty(const char* name) const {
-        static constexpr char kPrefix = '$';
-
-        return (name[0] == kPrefix && name[1] != '\0')
-            ? std::string(name + 1)
-            : std::string();
-    }
-
-    CustomPropertyManager::PropMap<skottie::ColorPropertyHandle>     fColorMap;
-    CustomPropertyManager::PropMap<skottie::OpacityPropertyHandle>   fOpacityMap;
-    CustomPropertyManager::PropMap<skottie::TransformPropertyHandle> fTransformMap;
-
-    using INHERITED = skottie::PropertyObserver;
+    std::vector<MarkerInfo>                   fMarkers;
 };
 
 } // namespace skottie_utils