[video_player] Set playback speed (#3084)

diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md
index 935de9b..f7fad26 100644
--- a/packages/video_player/video_player/CHANGELOG.md
+++ b/packages/video_player/video_player/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.11.0
+
+* Added option to set the video playback speed on the video controller.
+* **Minor breaking change**: fixed `VideoPlayerValue.toString` to insert a comma after `isBuffering`.
+
 ## 0.10.12+5
 
 * Depend on `video_player_platform_interface` version that contains the new `TestHostVideoPlayerApi`
diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md
index ef522a4..9c0e6b1 100644
--- a/packages/video_player/video_player/README.md
+++ b/packages/video_player/video_player/README.md
@@ -127,3 +127,22 @@
   }
 }
 ```
+
+## Usage
+
+The following section contains usage information that goes beyond what is included in the
+documentation in order to give a more elaborate overview of the API.
+
+This is not complete as of now. You can contribute to this section by [opening a pull request](https://github.com/flutter/plugins/pulls).
+
+### Playback speed
+
+You can set the playback speed on your `_controller` (instance of `VideoPlayerController`) by
+calling `_controller.setPlaybackSpeed`. `setPlaybackSpeed` takes a `double` speed value indicating 
+the rate of playback for your video.  
+For example, when given a value of `2.0`, your video will play at 2x the regular playback speed
+and so on.
+
+To learn about playback speed limitations, see the [`setPlaybackSpeed` method documentation](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController/setPlaybackSpeed.html).
+
+Furthermore, see the example app for an example playback speed implementation.
diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java
index 003ca18..78da715 100644
--- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java
+++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java
@@ -1,4 +1,4 @@
-// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
+// Autogenerated from Pigeon (v0.1.7), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 package io.flutter.plugins.videoplayer;
@@ -9,6 +9,7 @@
 import java.util.HashMap;
 
 /** Generated class from Pigeon. */
+@SuppressWarnings("unused")
 public class Messages {
 
   /** Generated class from Pigeon that represents data sent in messages. */
@@ -24,17 +25,18 @@
     }
 
     HashMap toMap() {
-      HashMap<String, Object> toMapResult = new HashMap<String, Object>();
+      HashMap<String, Object> toMapResult = new HashMap<>();
       toMapResult.put("textureId", textureId);
       return toMapResult;
     }
 
     static TextureMessage fromMap(HashMap map) {
       TextureMessage fromMapResult = new TextureMessage();
+      Object textureId = map.get("textureId");
       fromMapResult.textureId =
-          (map.get("textureId") instanceof Integer)
-              ? (Integer) map.get("textureId")
-              : (Long) map.get("textureId");
+          (textureId == null)
+              ? null
+              : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId);
       return fromMapResult;
     }
   }
@@ -82,7 +84,7 @@
     }
 
     HashMap toMap() {
-      HashMap<String, Object> toMapResult = new HashMap<String, Object>();
+      HashMap<String, Object> toMapResult = new HashMap<>();
       toMapResult.put("asset", asset);
       toMapResult.put("uri", uri);
       toMapResult.put("packageName", packageName);
@@ -92,10 +94,14 @@
 
     static CreateMessage fromMap(HashMap map) {
       CreateMessage fromMapResult = new CreateMessage();
-      fromMapResult.asset = (String) map.get("asset");
-      fromMapResult.uri = (String) map.get("uri");
-      fromMapResult.packageName = (String) map.get("packageName");
-      fromMapResult.formatHint = (String) map.get("formatHint");
+      Object asset = map.get("asset");
+      fromMapResult.asset = (String) asset;
+      Object uri = map.get("uri");
+      fromMapResult.uri = (String) uri;
+      Object packageName = map.get("packageName");
+      fromMapResult.packageName = (String) packageName;
+      Object formatHint = map.get("formatHint");
+      fromMapResult.formatHint = (String) formatHint;
       return fromMapResult;
     }
   }
@@ -123,7 +129,7 @@
     }
 
     HashMap toMap() {
-      HashMap<String, Object> toMapResult = new HashMap<String, Object>();
+      HashMap<String, Object> toMapResult = new HashMap<>();
       toMapResult.put("textureId", textureId);
       toMapResult.put("isLooping", isLooping);
       return toMapResult;
@@ -131,11 +137,13 @@
 
     static LoopingMessage fromMap(HashMap map) {
       LoopingMessage fromMapResult = new LoopingMessage();
+      Object textureId = map.get("textureId");
       fromMapResult.textureId =
-          (map.get("textureId") instanceof Integer)
-              ? (Integer) map.get("textureId")
-              : (Long) map.get("textureId");
-      fromMapResult.isLooping = (Boolean) map.get("isLooping");
+          (textureId == null)
+              ? null
+              : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId);
+      Object isLooping = map.get("isLooping");
+      fromMapResult.isLooping = (Boolean) isLooping;
       return fromMapResult;
     }
   }
@@ -163,7 +171,7 @@
     }
 
     HashMap toMap() {
-      HashMap<String, Object> toMapResult = new HashMap<String, Object>();
+      HashMap<String, Object> toMapResult = new HashMap<>();
       toMapResult.put("textureId", textureId);
       toMapResult.put("volume", volume);
       return toMapResult;
@@ -171,11 +179,55 @@
 
     static VolumeMessage fromMap(HashMap map) {
       VolumeMessage fromMapResult = new VolumeMessage();
+      Object textureId = map.get("textureId");
       fromMapResult.textureId =
-          (map.get("textureId") instanceof Integer)
-              ? (Integer) map.get("textureId")
-              : (Long) map.get("textureId");
-      fromMapResult.volume = (Double) map.get("volume");
+          (textureId == null)
+              ? null
+              : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId);
+      Object volume = map.get("volume");
+      fromMapResult.volume = (Double) volume;
+      return fromMapResult;
+    }
+  }
+
+  /** Generated class from Pigeon that represents data sent in messages. */
+  public static class PlaybackSpeedMessage {
+    private Long textureId;
+
+    public Long getTextureId() {
+      return textureId;
+    }
+
+    public void setTextureId(Long setterArg) {
+      this.textureId = setterArg;
+    }
+
+    private Double speed;
+
+    public Double getSpeed() {
+      return speed;
+    }
+
+    public void setSpeed(Double setterArg) {
+      this.speed = setterArg;
+    }
+
+    HashMap toMap() {
+      HashMap<String, Object> toMapResult = new HashMap<>();
+      toMapResult.put("textureId", textureId);
+      toMapResult.put("speed", speed);
+      return toMapResult;
+    }
+
+    static PlaybackSpeedMessage fromMap(HashMap map) {
+      PlaybackSpeedMessage fromMapResult = new PlaybackSpeedMessage();
+      Object textureId = map.get("textureId");
+      fromMapResult.textureId =
+          (textureId == null)
+              ? null
+              : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId);
+      Object speed = map.get("speed");
+      fromMapResult.speed = (Double) speed;
       return fromMapResult;
     }
   }
@@ -203,7 +255,7 @@
     }
 
     HashMap toMap() {
-      HashMap<String, Object> toMapResult = new HashMap<String, Object>();
+      HashMap<String, Object> toMapResult = new HashMap<>();
       toMapResult.put("textureId", textureId);
       toMapResult.put("position", position);
       return toMapResult;
@@ -211,14 +263,16 @@
 
     static PositionMessage fromMap(HashMap map) {
       PositionMessage fromMapResult = new PositionMessage();
+      Object textureId = map.get("textureId");
       fromMapResult.textureId =
-          (map.get("textureId") instanceof Integer)
-              ? (Integer) map.get("textureId")
-              : (Long) map.get("textureId");
+          (textureId == null)
+              ? null
+              : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId);
+      Object position = map.get("position");
       fromMapResult.position =
-          (map.get("position") instanceof Integer)
-              ? (Integer) map.get("position")
-              : (Long) map.get("position");
+          (position == null)
+              ? null
+              : ((position instanceof Integer) ? (Integer) position : (Long) position);
       return fromMapResult;
     }
   }
@@ -236,14 +290,15 @@
     }
 
     HashMap toMap() {
-      HashMap<String, Object> toMapResult = new HashMap<String, Object>();
+      HashMap<String, Object> toMapResult = new HashMap<>();
       toMapResult.put("mixWithOthers", mixWithOthers);
       return toMapResult;
     }
 
     static MixWithOthersMessage fromMap(HashMap map) {
       MixWithOthersMessage fromMapResult = new MixWithOthersMessage();
-      fromMapResult.mixWithOthers = (Boolean) map.get("mixWithOthers");
+      Object mixWithOthers = map.get("mixWithOthers");
+      fromMapResult.mixWithOthers = (Boolean) mixWithOthers;
       return fromMapResult;
     }
   }
@@ -260,6 +315,8 @@
 
     void setVolume(VolumeMessage arg);
 
+    void setPlaybackSpeed(PlaybackSpeedMessage arg);
+
     void play(TextureMessage arg);
 
     PositionMessage position(TextureMessage arg);
@@ -271,26 +328,24 @@
     void setMixWithOthers(MixWithOthersMessage arg);
 
     /** Sets up an instance of `VideoPlayerApi` to handle messages through the `binaryMessenger` */
-    public static void setup(BinaryMessenger binaryMessenger, VideoPlayerApi api) {
+    static void setup(BinaryMessenger binaryMessenger, VideoPlayerApi api) {
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.initialize",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.initialize();
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  api.initialize();
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -298,24 +353,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.create",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   CreateMessage input = CreateMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    TextureMessage output = api.create(input);
-                    wrapped.put("result", output.toMap());
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  TextureMessage output = api.create(input);
+                  wrapped.put("result", output.toMap());
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -323,24 +377,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.dispose",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   TextureMessage input = TextureMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.dispose(input);
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  api.dispose(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -348,24 +401,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.setLooping",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   LoopingMessage input = LoopingMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.setLooping(input);
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  api.setLooping(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -373,24 +425,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.setVolume",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   VolumeMessage input = VolumeMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.setVolume(input);
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  api.setVolume(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -398,24 +449,47 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed",
+                new StandardMessageCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
+                  PlaybackSpeedMessage input = PlaybackSpeedMessage.fromMap((HashMap) message);
+                  api.setPlaybackSpeed(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.play",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   TextureMessage input = TextureMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.play(input);
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  api.play(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -423,24 +497,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.position",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   TextureMessage input = TextureMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    PositionMessage output = api.position(input);
-                    wrapped.put("result", output.toMap());
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  PositionMessage output = api.position(input);
+                  wrapped.put("result", output.toMap());
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -448,24 +521,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.seekTo",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   PositionMessage input = PositionMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.seekTo(input);
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  api.seekTo(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -473,24 +545,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.pause",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   TextureMessage input = TextureMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.pause(input);
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  api.pause(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -498,24 +569,23 @@
       }
       {
         BasicMessageChannel<Object> channel =
-            new BasicMessageChannel<Object>(
+            new BasicMessageChannel<>(
                 binaryMessenger,
                 "dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers",
                 new StandardMessageCodec());
         if (api != null) {
           channel.setMessageHandler(
-              new BasicMessageChannel.MessageHandler<Object>() {
-                public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
+              (message, reply) -> {
+                HashMap<String, HashMap> wrapped = new HashMap<>();
+                try {
+                  @SuppressWarnings("ConstantConditions")
                   MixWithOthersMessage input = MixWithOthersMessage.fromMap((HashMap) message);
-                  HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
-                  try {
-                    api.setMixWithOthers(input);
-                    wrapped.put("result", null);
-                  } catch (Exception exception) {
-                    wrapped.put("error", wrapError(exception));
-                  }
-                  reply.reply(wrapped);
+                  api.setMixWithOthers(input);
+                  wrapped.put("result", null);
+                } catch (Exception exception) {
+                  wrapped.put("error", wrapError(exception));
                 }
+                reply.reply(wrapped);
               });
         } else {
           channel.setMessageHandler(null);
@@ -525,7 +595,7 @@
   }
 
   private static HashMap wrapError(Exception exception) {
-    HashMap<String, Object> errorMap = new HashMap<String, Object>();
+    HashMap<String, Object> errorMap = new HashMap<>();
     errorMap.put("message", exception.toString());
     errorMap.put("code", null);
     errorMap.put("details", null);
diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
index 801c2ca..8f8c898 100644
--- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
+++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java
@@ -11,6 +11,7 @@
 import com.google.android.exoplayer2.ExoPlaybackException;
 import com.google.android.exoplayer2.ExoPlayerFactory;
 import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
 import com.google.android.exoplayer2.Player;
 import com.google.android.exoplayer2.Player.EventListener;
 import com.google.android.exoplayer2.SimpleExoPlayer;
@@ -233,6 +234,14 @@
     exoPlayer.setVolume(bracketedValue);
   }
 
+  void setPlaybackSpeed(double value) {
+    // We do not need to consider pitch and skipSilence for now as we do not handle them and
+    // therefore never diverge from the default values.
+    final PlaybackParameters playbackParameters = new PlaybackParameters(((float) value));
+
+    exoPlayer.setPlaybackParameters(playbackParameters);
+  }
+
   void seekTo(int location) {
     exoPlayer.seekTo(location);
   }
diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
index 329370a..7f86305 100644
--- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
+++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
@@ -14,6 +14,7 @@
 import io.flutter.plugins.videoplayer.Messages.CreateMessage;
 import io.flutter.plugins.videoplayer.Messages.LoopingMessage;
 import io.flutter.plugins.videoplayer.Messages.MixWithOthersMessage;
+import io.flutter.plugins.videoplayer.Messages.PlaybackSpeedMessage;
 import io.flutter.plugins.videoplayer.Messages.PositionMessage;
 import io.flutter.plugins.videoplayer.Messages.TextureMessage;
 import io.flutter.plugins.videoplayer.Messages.VideoPlayerApi;
@@ -154,6 +155,11 @@
     player.setVolume(arg.getVolume());
   }
 
+  public void setPlaybackSpeed(PlaybackSpeedMessage arg) {
+    VideoPlayer player = videoPlayers.get(arg.getTextureId());
+    player.setPlaybackSpeed(arg.getSpeed());
+  }
+
   public void play(TextureMessage arg) {
     VideoPlayer player = videoPlayers.get(arg.getTextureId());
     player.play();
diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart
index ee2fcbd..a99b9da 100644
--- a/packages/video_player/video_player/example/lib/main.dart
+++ b/packages/video_player/video_player/example/lib/main.dart
@@ -188,7 +188,7 @@
                 alignment: Alignment.bottomCenter,
                 children: <Widget>[
                   VideoPlayer(_controller),
-                  _PlayPauseOverlay(controller: _controller),
+                  _ControlsOverlay(controller: _controller),
                   VideoProgressIndicator(_controller, allowScrubbing: true),
                 ],
               ),
@@ -252,7 +252,7 @@
                 children: <Widget>[
                   VideoPlayer(_controller),
                   ClosedCaption(text: _controller.value.caption.text),
-                  _PlayPauseOverlay(controller: _controller),
+                  _ControlsOverlay(controller: _controller),
                   VideoProgressIndicator(_controller, allowScrubbing: true),
                 ],
               ),
@@ -264,8 +264,19 @@
   }
 }
 
-class _PlayPauseOverlay extends StatelessWidget {
-  const _PlayPauseOverlay({Key key, this.controller}) : super(key: key);
+class _ControlsOverlay extends StatelessWidget {
+  const _ControlsOverlay({Key key, this.controller}) : super(key: key);
+
+  static const _examplePlaybackRates = [
+    0.25,
+    0.5,
+    1.0,
+    1.5,
+    2.0,
+    3.0,
+    5.0,
+    10.0,
+  ];
 
   final VideoPlayerController controller;
 
@@ -294,6 +305,35 @@
             controller.value.isPlaying ? controller.pause() : controller.play();
           },
         ),
+        Align(
+          alignment: Alignment.topRight,
+          child: PopupMenuButton<double>(
+            initialValue: controller.value.playbackSpeed,
+            tooltip: 'Playback speed',
+            onSelected: (speed) {
+              controller.setPlaybackSpeed(speed);
+            },
+            itemBuilder: (context) {
+              return [
+                for (final speed in _examplePlaybackRates)
+                  PopupMenuItem(
+                    value: speed,
+                    child: Text('${speed}x'),
+                  )
+              ];
+            },
+            child: Padding(
+              padding: const EdgeInsets.symmetric(
+                // Using less vertical padding as the text is also longer
+                // horizontally, so it feels like it would need more spacing
+                // horizontally (matching the aspect ratio of the video).
+                vertical: 12,
+                horizontal: 16,
+              ),
+              child: Text('${controller.value.playbackSpeed}x'),
+            ),
+          ),
+        ),
       ],
     );
   }
diff --git a/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m b/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m
index a834fe3..e6a4f6c 100644
--- a/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m
+++ b/packages/video_player/video_player/ios/Classes/FLTVideoPlayerPlugin.m
@@ -359,6 +359,30 @@
   _player.volume = (float)((volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume));
 }
 
+- (void)setPlaybackSpeed:(double)speed {
+  // See https://developer.apple.com/library/archive/qa/qa1772/_index.html for an explanation of
+  // these checks.
+  if (speed > 2.0 && !_player.currentItem.canPlayFastForward) {
+    if (_eventSink != nil) {
+      _eventSink([FlutterError errorWithCode:@"VideoError"
+                                     message:@"Video cannot be fast-forwarded beyond 2.0x"
+                                     details:nil]);
+    }
+    return;
+  }
+
+  if (speed < 1.0 && !_player.currentItem.canPlaySlowForward) {
+    if (_eventSink != nil) {
+      _eventSink([FlutterError errorWithCode:@"VideoError"
+                                     message:@"Video cannot be slow-forwarded"
+                                     details:nil]);
+    }
+    return;
+  }
+
+  _player.rate = speed;
+}
+
 - (CVPixelBufferRef)copyPixelBuffer {
   CMTime outputItemTime = [_videoOutput itemTimeForHostTime:CACurrentMediaTime()];
   if ([_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) {
@@ -538,6 +562,11 @@
   [player setVolume:[input.volume doubleValue]];
 }
 
+- (void)setPlaybackSpeed:(FLTPlaybackSpeedMessage*)input error:(FlutterError**)error {
+  FLTVideoPlayer* player = _players[input.textureId];
+  [player setPlaybackSpeed:[input.speed doubleValue]];
+}
+
 - (void)play:(FLTTextureMessage*)input error:(FlutterError**)error {
   FLTVideoPlayer* player = _players[input.textureId];
   [player play];
diff --git a/packages/video_player/video_player/ios/Classes/messages.h b/packages/video_player/video_player/ios/Classes/messages.h
index 27025ef..3c68b3d 100644
--- a/packages/video_player/video_player/ios/Classes/messages.h
+++ b/packages/video_player/video_player/ios/Classes/messages.h
@@ -1,4 +1,4 @@
-// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
+// Autogenerated from Pigeon (v0.1.7), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 #import <Foundation/Foundation.h>
 @protocol FlutterBinaryMessenger;
@@ -11,6 +11,7 @@
 @class FLTCreateMessage;
 @class FLTLoopingMessage;
 @class FLTVolumeMessage;
+@class FLTPlaybackSpeedMessage;
 @class FLTPositionMessage;
 @class FLTMixWithOthersMessage;
 
@@ -35,6 +36,11 @@
 @property(nonatomic, strong, nullable) NSNumber *volume;
 @end
 
+@interface FLTPlaybackSpeedMessage : NSObject
+@property(nonatomic, strong, nullable) NSNumber *textureId;
+@property(nonatomic, strong, nullable) NSNumber *speed;
+@end
+
 @interface FLTPositionMessage : NSObject
 @property(nonatomic, strong, nullable) NSNumber *textureId;
 @property(nonatomic, strong, nullable) NSNumber *position;
@@ -51,6 +57,8 @@
 - (void)dispose:(FLTTextureMessage *)input error:(FlutterError *_Nullable *_Nonnull)error;
 - (void)setLooping:(FLTLoopingMessage *)input error:(FlutterError *_Nullable *_Nonnull)error;
 - (void)setVolume:(FLTVolumeMessage *)input error:(FlutterError *_Nullable *_Nonnull)error;
+- (void)setPlaybackSpeed:(FLTPlaybackSpeedMessage *)input
+                   error:(FlutterError *_Nullable *_Nonnull)error;
 - (void)play:(FLTTextureMessage *)input error:(FlutterError *_Nullable *_Nonnull)error;
 - (nullable FLTPositionMessage *)position:(FLTTextureMessage *)input
                                     error:(FlutterError *_Nullable *_Nonnull)error;
diff --git a/packages/video_player/video_player/ios/Classes/messages.m b/packages/video_player/video_player/ios/Classes/messages.m
index 64fcd75..e71f8b8 100644
--- a/packages/video_player/video_player/ios/Classes/messages.m
+++ b/packages/video_player/video_player/ios/Classes/messages.m
@@ -1,4 +1,4 @@
-// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
+// Autogenerated from Pigeon (v0.1.7), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 #import "messages.h"
 #import <Flutter/Flutter.h>
@@ -36,6 +36,10 @@
 + (FLTVolumeMessage *)fromMap:(NSDictionary *)dict;
 - (NSDictionary *)toMap;
 @end
+@interface FLTPlaybackSpeedMessage ()
++ (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict;
+- (NSDictionary *)toMap;
+@end
 @interface FLTPositionMessage ()
 + (FLTPositionMessage *)fromMap:(NSDictionary *)dict;
 - (NSDictionary *)toMap;
@@ -86,9 +90,9 @@
   return [NSDictionary
       dictionaryWithObjectsAndKeys:(self.asset ? self.asset : [NSNull null]), @"asset",
                                    (self.uri ? self.uri : [NSNull null]), @"uri",
-                                   (self.packageName != nil ? self.packageName : [NSNull null]),
+                                   (self.packageName ? self.packageName : [NSNull null]),
                                    @"packageName",
-                                   (self.formatHint != nil ? self.formatHint : [NSNull null]),
+                                   (self.formatHint ? self.formatHint : [NSNull null]),
                                    @"formatHint", nil];
 }
 @end
@@ -136,6 +140,27 @@
 }
 @end
 
+@implementation FLTPlaybackSpeedMessage
++ (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict {
+  FLTPlaybackSpeedMessage *result = [[FLTPlaybackSpeedMessage alloc] init];
+  result.textureId = dict[@"textureId"];
+  if ((NSNull *)result.textureId == [NSNull null]) {
+    result.textureId = nil;
+  }
+  result.speed = dict[@"speed"];
+  if ((NSNull *)result.speed == [NSNull null]) {
+    result.speed = nil;
+  }
+  return result;
+}
+- (NSDictionary *)toMap {
+  return [NSDictionary
+      dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]),
+                                   @"textureId", (self.speed != nil ? self.speed : [NSNull null]),
+                                   @"speed", nil];
+}
+@end
+
 @implementation FLTPositionMessage
 + (FLTPositionMessage *)fromMap:(NSDictionary *)dict {
   FLTPositionMessage *result = [[FLTPositionMessage alloc] init];
@@ -250,6 +275,21 @@
     }
   }
   {
+    FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
+        messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed"
+               binaryMessenger:binaryMessenger];
+    if (api) {
+      [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+        FlutterError *error;
+        FLTPlaybackSpeedMessage *input = [FLTPlaybackSpeedMessage fromMap:message];
+        [api setPlaybackSpeed:input error:&error];
+        callback(wrapResult(nil, error));
+      }];
+    } else {
+      [channel setMessageHandler:nil];
+    }
+  }
+  {
     FlutterBasicMessageChannel *channel =
         [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.VideoPlayerApi.play"
                                            binaryMessenger:binaryMessenger];
diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart
index 8cd0561..bf98096 100644
--- a/packages/video_player/video_player/lib/video_player.dart
+++ b/packages/video_player/video_player/lib/video_player.dart
@@ -6,11 +6,11 @@
 import 'dart:io';
 
 import 'package:flutter/foundation.dart';
-import 'package:flutter/services.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:meta/meta.dart';
-
 import 'package:video_player_platform_interface/video_player_platform_interface.dart';
+
 export 'package:video_player_platform_interface/video_player_platform_interface.dart'
     show DurationRange, DataSourceType, VideoFormat, VideoPlayerOptions;
 
@@ -37,6 +37,7 @@
     this.isLooping = false,
     this.isBuffering = false,
     this.volume = 1.0,
+    this.playbackSpeed = 1.0,
     this.errorDescription,
   });
 
@@ -77,6 +78,9 @@
   /// The current volume of the playback.
   final double volume;
 
+  /// The current speed of the playback.
+  final double playbackSpeed;
+
   /// A description of the error if present.
   ///
   /// If [hasError] is false this is [null].
@@ -119,6 +123,7 @@
     bool isLooping,
     bool isBuffering,
     double volume,
+    double playbackSpeed,
     String errorDescription,
   }) {
     return VideoPlayerValue(
@@ -131,6 +136,7 @@
       isLooping: isLooping ?? this.isLooping,
       isBuffering: isBuffering ?? this.isBuffering,
       volume: volume ?? this.volume,
+      playbackSpeed: playbackSpeed ?? this.playbackSpeed,
       errorDescription: errorDescription ?? this.errorDescription,
     );
   }
@@ -145,8 +151,9 @@
         'buffered: [${buffered.join(', ')}], '
         'isPlaying: $isPlaying, '
         'isLooping: $isLooping, '
-        'isBuffering: $isBuffering'
+        'isBuffering: $isBuffering, '
         'volume: $volume, '
+        'playbackSpeed: $playbackSpeed, '
         'errorDescription: $errorDescription)';
   }
 }
@@ -397,6 +404,11 @@
           _updatePosition(newPosition);
         },
       );
+
+      // This ensures that the correct playback speed is always applied when
+      // playing back. This is necessary because we do not set playback speed
+      // when paused.
+      await _applyPlaybackSpeed();
     } else {
       _timer?.cancel();
       await _videoPlayerPlatform.pause(_textureId);
@@ -410,6 +422,22 @@
     await _videoPlayerPlatform.setVolume(_textureId, value.volume);
   }
 
+  Future<void> _applyPlaybackSpeed() async {
+    if (!value.initialized || _isDisposed) {
+      return;
+    }
+
+    // Setting the playback speed on iOS will trigger the video to play. We
+    // prevent this from happening by not applying the playback speed until
+    // the video is manually played from Flutter.
+    if (!value.isPlaying) return;
+
+    await _videoPlayerPlatform.setPlaybackSpeed(
+      _textureId,
+      value.playbackSpeed,
+    );
+  }
+
   /// The position in the current video.
   Future<Duration> get position async {
     if (_isDisposed) {
@@ -445,6 +473,40 @@
     await _applyVolume();
   }
 
+  /// Sets the playback speed of [this].
+  ///
+  /// [speed] indicates a speed value with different platforms accepting
+  /// different ranges for speed values. The [speed] must be greater than 0.
+  ///
+  /// The values will be handled as follows:
+  /// * On web, the audio will be muted at some speed when the browser
+  ///   determines that the sound would not be useful anymore. For example,
+  ///   "Gecko mutes the sound outside the range `0.25` to `5.0`" (see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playbackRate).
+  /// * On Android, some very extreme speeds will not be played back accurately.
+  ///   Instead, your video will still be played back, but the speed will be
+  ///   clamped by ExoPlayer (but the values are allowed by the player, like on
+  ///   web).
+  /// * On iOS, you can sometimes not go above `2.0` playback speed on a video.
+  ///   An error will be thrown for if the option is unsupported. It is also
+  ///   possible that your specific video cannot be slowed down, in which case
+  ///   the plugin also reports errors.
+  Future<void> setPlaybackSpeed(double speed) async {
+    if (speed < 0) {
+      throw ArgumentError.value(
+        speed,
+        'Negative playback speeds are generally unsupported.',
+      );
+    } else if (speed == 0) {
+      throw ArgumentError.value(
+        speed,
+        'Zero playback speed is generally unsupported. Consider using [pause].',
+      );
+    }
+
+    value = value.copyWith(playbackSpeed: speed);
+    await _applyPlaybackSpeed();
+  }
+
   /// The closed caption based on the current [position] in the video.
   ///
   /// If there are no closed captions at the current [position], this will
diff --git a/packages/video_player/video_player/pigeons/messages.dart b/packages/video_player/video_player/pigeons/messages.dart
index 074eef0..427aea2 100644
--- a/packages/video_player/video_player/pigeons/messages.dart
+++ b/packages/video_player/video_player/pigeons/messages.dart
@@ -14,6 +14,11 @@
   double volume;
 }
 
+class PlaybackSpeedMessage {
+  int textureId;
+  double speed;
+}
+
 class PositionMessage {
   int textureId;
   int position;
@@ -30,13 +35,14 @@
   bool mixWithOthers;
 }
 
-@HostApi()
+@HostApi(dartHostTestHandler: 'TestHostVideoPlayerApi')
 abstract class VideoPlayerApi {
   void initialize();
   TextureMessage create(CreateMessage msg);
   void dispose(TextureMessage msg);
   void setLooping(LoopingMessage msg);
   void setVolume(VolumeMessage msg);
+  void setPlaybackSpeed(PlaybackSpeedMessage msg);
   void play(TextureMessage msg);
   PositionMessage position(TextureMessage msg);
   void seekTo(PositionMessage msg);
diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml
index bd6d451..973c0bc 100644
--- a/packages/video_player/video_player/pubspec.yaml
+++ b/packages/video_player/video_player/pubspec.yaml
@@ -1,10 +1,10 @@
 name: video_player
 description: Flutter plugin for displaying inline video with other Flutter
-  widgets on Android and iOS.
+  widgets on Android, iOS, and web.
 # 0.10.y+z is compatible with 1.0.0, if you land a breaking change bump
 # the version to 2.0.0.
 # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
-version: 0.10.12+5
+version: 0.11.0
 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player
 
 flutter:
@@ -21,12 +21,13 @@
 dependencies:
   meta: ^1.0.5
   video_player_platform_interface: ^2.2.0
+
   # The design on https://flutter.dev/go/federated-plugins was to leave
   # this constraint as "any". We cannot do it right now as it fails pub publish
   # validation, so we set a ^ constraint.
   # TODO(amirh): Revisit this (either update this part in the  design or the pub tool).
   # https://github.com/flutter/flutter/issues/46264
-  video_player_web: '>=0.1.1 <2.0.0'
+  video_player_web: '>=0.1.4 <2.0.0'
 
   flutter:
     sdk: flutter
@@ -35,8 +36,8 @@
   flutter_test:
     sdk: flutter
   pedantic: ^1.8.0
-  pigeon: 0.1.0-experimental.11
+  pigeon: 0.1.7
 
 environment:
-  sdk: ">=2.1.0 <3.0.0"
+  sdk: ">=2.8.0 <3.0.0"
   flutter: ">=1.12.13+hotfix.5 <2.0.0"
diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart
index 3cc0879..35cab62 100644
--- a/packages/video_player/video_player/test/video_player_test.dart
+++ b/packages/video_player/video_player/test/video_player_test.dart
@@ -4,12 +4,13 @@
 
 import 'dart:async';
 import 'dart:io';
+
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/widgets.dart';
-import 'package:video_player/video_player.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:video_player/video_player.dart';
 import 'package:video_player_platform_interface/messages.dart';
 import 'package:video_player_platform_interface/video_player_platform_interface.dart';
 
@@ -27,23 +28,34 @@
 
   @override
   String get dataSource => '';
+
   @override
   DataSourceType get dataSourceType => DataSourceType.file;
+
   @override
   String get package => null;
+
   @override
   Future<Duration> get position async => value.position;
 
   @override
   Future<void> seekTo(Duration moment) async {}
+
   @override
   Future<void> setVolume(double volume) async {}
+
+  @override
+  Future<void> setPlaybackSpeed(double speed) async {}
+
   @override
   Future<void> initialize() async {}
+
   @override
   Future<void> pause() async {}
+
   @override
   Future<void> play() async {}
+
   @override
   Future<void> setLooping(bool looping) async {}
 
@@ -250,7 +262,14 @@
       await controller.play();
 
       expect(controller.value.isPlaying, isTrue);
-      expect(fakeVideoPlayerPlatform.calls.last, 'play');
+
+      // The two last calls will be "play" and then "setPlaybackSpeed". The
+      // reason for this is that "play" calls "setPlaybackSpeed" internally.
+      expect(
+          fakeVideoPlayerPlatform
+              .calls[fakeVideoPlayerPlatform.calls.length - 2],
+          'play');
+      expect(fakeVideoPlayerPlatform.calls.last, 'setPlaybackSpeed');
     });
 
     test('setLooping', () async {
@@ -335,6 +354,31 @@
       });
     });
 
+    group('setPlaybackSpeed', () {
+      test('works', () async {
+        final VideoPlayerController controller = VideoPlayerController.network(
+          'https://127.0.0.1',
+        );
+        await controller.initialize();
+        expect(controller.value.playbackSpeed, 1.0);
+
+        const double speed = 1.5;
+        await controller.setPlaybackSpeed(speed);
+
+        expect(controller.value.playbackSpeed, speed);
+      });
+
+      test('rejects negative values', () async {
+        final VideoPlayerController controller = VideoPlayerController.network(
+          'https://127.0.0.1',
+        );
+        await controller.initialize();
+        expect(controller.value.playbackSpeed, 1.0);
+
+        expect(() => controller.setPlaybackSpeed(-1), throwsArgumentError);
+      });
+    });
+
     group('caption', () {
       test('works when seeking', () async {
         final VideoPlayerController controller = VideoPlayerController.network(
@@ -458,6 +502,7 @@
       expect(uninitialized.isLooping, isFalse);
       expect(uninitialized.isBuffering, isFalse);
       expect(uninitialized.volume, 1.0);
+      expect(uninitialized.playbackSpeed, 1.0);
       expect(uninitialized.errorDescription, isNull);
       expect(uninitialized.size, isNull);
       expect(uninitialized.size, isNull);
@@ -478,6 +523,7 @@
       expect(error.isLooping, isFalse);
       expect(error.isBuffering, isFalse);
       expect(error.volume, 1.0);
+      expect(error.playbackSpeed, 1.0);
       expect(error.errorDescription, errorMessage);
       expect(error.size, isNull);
       expect(error.size, isNull);
@@ -498,20 +544,34 @@
       const bool isLooping = true;
       const bool isBuffering = true;
       const double volume = 0.5;
+      const double playbackSpeed = 1.5;
 
       final VideoPlayerValue value = VideoPlayerValue(
-          duration: duration,
-          size: size,
-          position: position,
-          caption: caption,
-          buffered: buffered,
-          isPlaying: isPlaying,
-          isLooping: isLooping,
-          isBuffering: isBuffering,
-          volume: volume);
+        duration: duration,
+        size: size,
+        position: position,
+        caption: caption,
+        buffered: buffered,
+        isPlaying: isPlaying,
+        isLooping: isLooping,
+        isBuffering: isBuffering,
+        volume: volume,
+        playbackSpeed: playbackSpeed,
+      );
 
-      expect(value.toString(),
-          'VideoPlayerValue(duration: 0:00:05.000000, size: Size(400.0, 300.0), position: 0:00:01.000000, caption: Instance of \'Caption\', buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], isPlaying: true, isLooping: true, isBuffering: truevolume: 0.5, errorDescription: null)');
+      expect(
+          value.toString(),
+          'VideoPlayerValue(duration: 0:00:05.000000, '
+          'size: Size(400.0, 300.0), '
+          'position: 0:00:01.000000, '
+          'caption: Instance of \'Caption\', '
+          'buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], '
+          'isPlaying: true, '
+          'isLooping: true, '
+          'isBuffering: true, '
+          'volume: 0.5, '
+          'playbackSpeed: 1.5, '
+          'errorDescription: null)');
     });
 
     test('copyWith()', () {
@@ -659,7 +719,7 @@
 
   @override
   void setPlaybackSpeed(PlaybackSpeedMessage arg) {
-    // todo: implement as part of completing https://github.com/flutter/plugins/pull/3031
+    calls.add('setPlaybackSpeed');
   }
 
   @override