Added support for time stretch
diff --git a/LottieSample/screenshots/Tests_TimeStretch.png b/LottieSample/screenshots/Tests_TimeStretch.png
new file mode 100644
index 0000000..cb85c9e
--- /dev/null
+++ b/LottieSample/screenshots/Tests_TimeStretch.png
Binary files differ
diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java
index 3c32fc4..f26b082 100644
--- a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java
+++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java
@@ -78,6 +78,7 @@
     TestRobot.testLinearAnimation(activity, "Tests/ShapeTypes.json");
     TestRobot.testLinearAnimation(activity, "Tests/SplitDimensions.json");
     TestRobot.testLinearAnimation(activity, "Tests/Stroke.json");
+    TestRobot.testLinearAnimation(activity, "Tests/TimeStretch.json");
     TestRobot.testLinearAnimation(activity, "Tests/TrackMattes.json");
     TestRobot.testLinearAnimation(activity, "Tests/TrimPaths.json");
     TestRobot.testChangingCompositions(activity, "TwitterHeart.json", "PinJump.json");
diff --git a/LottieSample/src/main/assets/Tests/TimeStretch.json b/LottieSample/src/main/assets/Tests/TimeStretch.json
new file mode 100644
index 0000000..2cbcabe
--- /dev/null
+++ b/LottieSample/src/main/assets/Tests/TimeStretch.json
@@ -0,0 +1 @@
+{"v":"4.8.0","fr":29.9700012207031,"ip":0,"op":180.00000733155,"w":470,"h":470,"nm":"Work Area 2","ddd":0,"assets":[{"id":"comp_59","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[235,235,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[97.961,97.961]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect"},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":2},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[0.132077,0,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[-162.02,-164.02],"e":[167.762,175.637],"to":[54.9635429382324,56.609375],"ti":[-54.9635429382324,-56.609375]},{"t":179.000007290819}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":180.00000733155,"st":0,"bm":0,"sr":1}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Work Area","refId":"comp_59","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[235,235,0]},"a":{"a":0,"k":[235,235,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":470,"h":470,"ip":0,"op":90.0000036657751,"st":0,"bm":0,"sr":0.5}]}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/BaseLayer.java b/lottie/src/main/java/com/airbnb/lottie/BaseLayer.java
index 4c198e2..8d5439b 100644
--- a/lottie/src/main/java/com/airbnb/lottie/BaseLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/BaseLayer.java
@@ -349,6 +349,9 @@
   }
 
   void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
+    if (layerModel.getTimeStretch() != 0) {
+      progress /= layerModel.getTimeStretch();
+    }
     if (matteLayer != null) {
       matteLayer.setProgress(progress);
     }
diff --git a/lottie/src/main/java/com/airbnb/lottie/CompositionLayer.java b/lottie/src/main/java/com/airbnb/lottie/CompositionLayer.java
index 3ad8ad5..e9e1618 100644
--- a/lottie/src/main/java/com/airbnb/lottie/CompositionLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/CompositionLayer.java
@@ -119,6 +119,9 @@
       long remappedTime = (long) (timeRemapping.getValue() * 1000);
       progress = remappedTime / (float) duration;
     }
+    if (layerModel.getTimeStretch() != 0) {
+      progress /= layerModel.getTimeStretch();
+    }
 
     progress -= layerModel.getStartProgress();
     for (int i = layers.size() - 1; i >= 0; i--) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/Layer.java b/lottie/src/main/java/com/airbnb/lottie/Layer.java
index aeb78ce..f81dd9f 100644
--- a/lottie/src/main/java/com/airbnb/lottie/Layer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/Layer.java
@@ -89,7 +89,7 @@
     return composition;
   }
 
-  @SuppressWarnings("unused") float getTimeStretch() {
+  float getTimeStretch() {
     return timeStretch;
   }
 
@@ -304,8 +304,11 @@
         preCompHeight = (int) (json.optInt("h") * composition.getDpScale());
       }
 
-      float inFrame = json.optLong("ip");
-      float outFrame = json.optLong("op");
+      // Bodymovin pre-scales the in frame and out frame by the time stretch. However, that will
+      // cause the stretch to be double counted since the in out animation gets treated the same
+      // as all other animations and will have stretch applied to it again.
+      float inFrame = json.optLong("ip") / timeStretch;
+      float outFrame = json.optLong("op") / timeStretch;
 
       // Before the in frame
       if (inFrame > 0) {
@@ -319,11 +322,9 @@
           new Keyframe<>(composition, 1f, 1f, null, inFrame, outFrame);
       inOutKeyframes.add(visibleKeyframe);
 
-      if (outFrame <= composition.getDurationFrames()) {
-        Keyframe<Float> outKeyframe =
-            new Keyframe<>(composition, 0f, 0f, null, outFrame, (float) composition.getEndFrame());
-        inOutKeyframes.add(outKeyframe);
-      }
+      Keyframe<Float> outKeyframe = new Keyframe<>(
+          composition, 0f, 0f, null, outFrame, Float.MAX_VALUE);
+      inOutKeyframes.add(outKeyframe);
 
       AnimatableFloatValue timeRemapping = null;
       if (json.has("tm")) {