Merge branch 'master' of github.com:airbnb/lottie-web
diff --git a/player/index.html b/player/index.html
index 63e079f..b09e35f 100644
--- a/player/index.html
+++ b/player/index.html
@@ -75,6 +75,7 @@
     <script src="js/utils/pooling/shapeCollection_pool.js"></script>
     <script src="js/utils/pooling/segments_length_pool.js"></script>
     <script src="js/utils/pooling/bezier_length_pool.js"></script>
+    <script src="js/utils/markers/markerParser.js"></script>
     <script src="js/renderers/BaseRenderer.js"></script>
     <script src="js/renderers/SVGRenderer.js"></script>
     <script src="js/renderers/CanvasRenderer.js" data-builds="full,canvas,canvas_light,canvas_worker"></script>
diff --git a/player/js/animation/AnimationItem.js b/player/js/animation/AnimationItem.js
index a9af1ff..9f15981 100644
--- a/player/js/animation/AnimationItem.js
+++ b/player/js/animation/AnimationItem.js
@@ -1,7 +1,7 @@
 /* global createElementID, subframeEnabled, ProjectInterface, ImagePreloader, audioControllerFactory, extendPrototype, BaseEvent,
 CanvasRenderer, SVGRenderer, HybridRenderer, assetLoader, dataManager, expressionsPlugin, BMEnterFrameEvent, BMCompleteLoopEvent,
 BMCompleteEvent, BMSegmentStartEvent, BMDestroyEvent, BMEnterFrameEvent, BMCompleteLoopEvent, BMCompleteEvent, BMSegmentStartEvent,
-BMDestroyEvent, BMRenderFrameErrorEvent, BMConfigErrorEvent */
+BMDestroyEvent, BMRenderFrameErrorEvent, BMConfigErrorEvent, markerParser */
 
 var AnimationItem = function () {
   this._cbs = [];
@@ -34,6 +34,7 @@
   this.projectInterface = ProjectInterface();
   this.imagePreloader = new ImagePreloader();
   this.audioController = audioControllerFactory();
+  this.markers = [];
 };
 
 extendPrototype([BaseEvent], AnimationItem);
@@ -270,6 +271,7 @@
     this.frameRate = this.animationData.fr;
     this.frameMult = this.animationData.fr / 1000;
     this.renderer.searchExtraCompositions(animData.assets);
+    this.markers = markerParser(animData.markers || []);
     this.trigger('config_ready');
     this.preloadImages();
     this.loadSegments();
@@ -334,7 +336,7 @@
 };
 
 AnimationItem.prototype.renderFrame = function () {
-  if (this.isLoaded === false) {
+  if (this.isLoaded === false || !this.renderer) {
     return;
   }
   try {
@@ -391,11 +393,28 @@
   this.setCurrentRawFrameValue(0);
 };
 
+AnimationItem.prototype.getMarkerData = function (markerName) {
+  var marker;
+  for (var i = 0; i < this.markers.length; i += 1) {
+    marker = this.markers[i];
+    if (marker.payload && marker.payload.name === markerName) {
+      return marker;
+    }
+  }
+  return null;
+};
+
 AnimationItem.prototype.goToAndStop = function (value, isFrame, name) {
   if (name && this.name !== name) {
     return;
   }
-  if (isFrame) {
+  var numValue = Number(value);
+  if (isNaN(numValue)) {
+    var marker = this.getMarkerData(value);
+    if (marker) {
+      this.goToAndStop(marker.time, true);
+    }
+  } else if (isFrame) {
     this.setCurrentRawFrameValue(value);
   } else {
     this.setCurrentRawFrameValue(value * this.frameModifier);
@@ -404,7 +423,22 @@
 };
 
 AnimationItem.prototype.goToAndPlay = function (value, isFrame, name) {
-  this.goToAndStop(value, isFrame, name);
+  if (name && this.name !== name) {
+    return;
+  }
+  var numValue = Number(value);
+  if (isNaN(numValue)) {
+    var marker = this.getMarkerData(value);
+    if (marker) {
+      if (!marker.duration) {
+        this.goToAndStop(marker.time, true);
+      } else {
+        this.playSegments([marker.time, marker.time + marker.duration], true);
+      }
+    }
+  } else {
+    this.goToAndStop(numValue, isFrame, name);
+  }
   this.play();
 };
 
diff --git a/player/js/utils/asset_loader.js b/player/js/utils/asset_loader.js
index 7c5b8c9..bf84bc5 100644
--- a/player/js/utils/asset_loader.js
+++ b/player/js/utils/asset_loader.js
@@ -21,7 +21,6 @@
       // This crashes on Android WebView prior to KitKat
       xhr.responseType = 'json';
     } catch (err) {} // eslint-disable-line no-empty
-    xhr.send();
     xhr.onreadystatechange = function () {
       if (xhr.readyState === 4) {
         if (xhr.status === 200) {
@@ -39,6 +38,7 @@
         }
       }
     };
+    xhr.send();
   }
   return {
     load: loadAsset,
diff --git a/player/js/utils/imagePreloader.js b/player/js/utils/imagePreloader.js
index 2ea32fc..b0d3266 100644
--- a/player/js/utils/imagePreloader.js
+++ b/player/js/utils/imagePreloader.js
@@ -64,7 +64,11 @@
       this._imageLoaded();
     }.bind(this), false);
     img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path);
-    this._elementHelper.append(img);
+    if (this._elementHelper.append) {
+      this._elementHelper.append(img);
+    } else {
+      this._elementHelper.appendChild(img);
+    }
     var ob = {
       img: img,
       assetData: assetData,
diff --git a/player/js/utils/markers/markerParser.js b/player/js/utils/markers/markerParser.js
new file mode 100644
index 0000000..89d6ea4
--- /dev/null
+++ b/player/js/utils/markers/markerParser.js
@@ -0,0 +1,47 @@
+/* exported markerParser */
+
+var markerParser = (
+
+  function () {
+    function parsePayloadLines(payload) {
+      var lines = payload.split('\r\n');
+      var keys = {};
+      var line;
+      var keysCount = 0;
+      for (var i = 0; i < lines.length; i += 1) {
+        line = lines[i].split(':');
+        if (line.length === 2) {
+          keys[line[0]] = line[1].trim();
+          keysCount += 1;
+        }
+      }
+      if (keysCount === 0) {
+        throw new Error();
+      }
+      return keys;
+    }
+
+    return function (_markers) {
+      var markers = [];
+      for (var i = 0; i < _markers.length; i += 1) {
+        var _marker = _markers[i];
+        var markerData = {
+          time: _marker.tm,
+          duration: _marker.dr,
+        };
+        try {
+          markerData.payload = JSON.parse(_markers[i].cm);
+        } catch (_) {
+          try {
+            markerData.payload = parsePayloadLines(_markers[i].cm);
+          } catch (__) {
+            markerData.payload = {
+              name: _markers[i],
+            };
+          }
+        }
+        markers.push(markerData);
+      }
+      return markers;
+    };
+  }());
diff --git a/player/js/utils/shapes/RepeaterModifier.js b/player/js/utils/shapes/RepeaterModifier.js
index e028c94..062f3cf 100644
--- a/player/js/utils/shapes/RepeaterModifier.js
+++ b/player/js/utils/shapes/RepeaterModifier.js
@@ -118,6 +118,16 @@
       renderFlag = cont < copies;
       this._groups[i]._render = renderFlag;
       this.changeGroupRender(this._groups[i].it, renderFlag);
+      if (!renderFlag) {
+        var elems = this.elemsData[i].it;
+        var transformData = elems[elems.length - 1];
+        if (transformData.transform.op.v !== 0) {
+          transformData.transform.op._mdf = true;
+          transformData.transform.op.v = 0;
+        } else {
+          transformData.transform.op._mdf = false;
+        }
+      }
       cont += 1;
     }
 
@@ -167,7 +177,10 @@
       jLen = itemsTransform.length;
       items[items.length - 1].transform.mProps._mdf = true;
       items[items.length - 1].transform.op._mdf = true;
-      items[items.length - 1].transform.op.v = this.so.v + (this.eo.v - this.so.v) * (i / (this._currentCopies - 1));
+      items[items.length - 1].transform.op.v = this._currentCopies === 1
+        ? this.so.v
+        : this.so.v + (this.eo.v - this.so.v) * (i / (this._currentCopies - 1));
+
       if (iteration !== 0) {
         if ((i !== 0 && dir === 1) || (i !== this._currentCopies - 1 && dir === -1)) {
           this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false);
diff --git a/tasks/build.js b/tasks/build.js
index d4720da..940966d 100644
--- a/tasks/build.js
+++ b/tasks/build.js
@@ -212,6 +212,10 @@
 		builds: defaultBuilds
 	},
 	{
+		src: 'js/utils/markers/markerParser.js',
+		builds: defaultBuilds
+	},
+	{
 		src: 'js/renderers/BaseRenderer.js',
 		builds: defaultBuilds
 	},