[video_player_platform_interface] Add setPlaybackSpeed method (#3032)

diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md
index 2af6f01..8af22f7 100644
--- a/packages/video_player/video_player_platform_interface/CHANGELOG.md
+++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+* Added option to set the video playback speed on the video controller.
+
 ## 2.1.1
 
 * Fix mixWithOthers test channel.
diff --git a/packages/video_player/video_player_platform_interface/lib/messages.dart b/packages/video_player/video_player_platform_interface/lib/messages.dart
index c5e8cd4..bfe65f1 100644
--- a/packages/video_player/video_player_platform_interface/lib/messages.dart
+++ b/packages/video_player/video_player_platform_interface/lib/messages.dart
@@ -1,8 +1,10 @@
-// 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
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
+// @dart = 2.8
 import 'dart:async';
 import 'package:flutter/services.dart';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
 
 class TextureMessage {
   int textureId;
@@ -15,6 +17,9 @@
 
   // ignore: unused_element
   static TextureMessage _fromMap(Map<dynamic, dynamic> pigeonMap) {
+    if (pigeonMap == null) {
+      return null;
+    }
     final TextureMessage result = TextureMessage();
     result.textureId = pigeonMap['textureId'];
     return result;
@@ -38,6 +43,9 @@
 
   // ignore: unused_element
   static CreateMessage _fromMap(Map<dynamic, dynamic> pigeonMap) {
+    if (pigeonMap == null) {
+      return null;
+    }
     final CreateMessage result = CreateMessage();
     result.asset = pigeonMap['asset'];
     result.uri = pigeonMap['uri'];
@@ -60,6 +68,9 @@
 
   // ignore: unused_element
   static LoopingMessage _fromMap(Map<dynamic, dynamic> pigeonMap) {
+    if (pigeonMap == null) {
+      return null;
+    }
     final LoopingMessage result = LoopingMessage();
     result.textureId = pigeonMap['textureId'];
     result.isLooping = pigeonMap['isLooping'];
@@ -80,6 +91,9 @@
 
   // ignore: unused_element
   static VolumeMessage _fromMap(Map<dynamic, dynamic> pigeonMap) {
+    if (pigeonMap == null) {
+      return null;
+    }
     final VolumeMessage result = VolumeMessage();
     result.textureId = pigeonMap['textureId'];
     result.volume = pigeonMap['volume'];
@@ -87,6 +101,29 @@
   }
 }
 
+class PlaybackSpeedMessage {
+  int textureId;
+  double speed;
+  // ignore: unused_element
+  Map<dynamic, dynamic> _toMap() {
+    final Map<dynamic, dynamic> pigeonMap = <dynamic, dynamic>{};
+    pigeonMap['textureId'] = textureId;
+    pigeonMap['speed'] = speed;
+    return pigeonMap;
+  }
+
+  // ignore: unused_element
+  static PlaybackSpeedMessage _fromMap(Map<dynamic, dynamic> pigeonMap) {
+    if (pigeonMap == null) {
+      return null;
+    }
+    final PlaybackSpeedMessage result = PlaybackSpeedMessage();
+    result.textureId = pigeonMap['textureId'];
+    result.speed = pigeonMap['speed'];
+    return result;
+  }
+}
+
 class PositionMessage {
   int textureId;
   int position;
@@ -100,6 +137,9 @@
 
   // ignore: unused_element
   static PositionMessage _fromMap(Map<dynamic, dynamic> pigeonMap) {
+    if (pigeonMap == null) {
+      return null;
+    }
     final PositionMessage result = PositionMessage();
     result.textureId = pigeonMap['textureId'];
     result.position = pigeonMap['position'];
@@ -118,128 +158,15 @@
 
   // ignore: unused_element
   static MixWithOthersMessage _fromMap(Map<dynamic, dynamic> pigeonMap) {
+    if (pigeonMap == null) {
+      return null;
+    }
     final MixWithOthersMessage result = MixWithOthersMessage();
     result.mixWithOthers = pigeonMap['mixWithOthers'];
     return result;
   }
 }
 
-abstract class VideoPlayerApiTest {
-  void initialize();
-  TextureMessage create(CreateMessage arg);
-  void dispose(TextureMessage arg);
-  void setLooping(LoopingMessage arg);
-  void setVolume(VolumeMessage arg);
-  void play(TextureMessage arg);
-  PositionMessage position(TextureMessage arg);
-  void seekTo(PositionMessage arg);
-  void pause(TextureMessage arg);
-  void setMixWithOthers(MixWithOthersMessage arg);
-}
-
-void VideoPlayerApiTestSetup(VideoPlayerApiTest api) {
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.initialize', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      api.initialize();
-      return {};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.create', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final CreateMessage input = CreateMessage._fromMap(mapMessage);
-      final TextureMessage output = api.create(input);
-      return {'result': output._toMap()};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.dispose', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final TextureMessage input = TextureMessage._fromMap(mapMessage);
-      api.dispose(input);
-      return {};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.setLooping', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final LoopingMessage input = LoopingMessage._fromMap(mapMessage);
-      api.setLooping(input);
-      return {};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.setVolume', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final VolumeMessage input = VolumeMessage._fromMap(mapMessage);
-      api.setVolume(input);
-      return {};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.play', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final TextureMessage input = TextureMessage._fromMap(mapMessage);
-      api.play(input);
-      return {};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final TextureMessage input = TextureMessage._fromMap(mapMessage);
-      final PositionMessage output = api.position(input);
-      return {'result': output._toMap()};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.seekTo', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final PositionMessage input = PositionMessage._fromMap(mapMessage);
-      api.seekTo(input);
-      return {};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.pause', StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final TextureMessage input = TextureMessage._fromMap(mapMessage);
-      api.pause(input);
-      return {};
-    });
-  }
-  {
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-        'dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers',
-        StandardMessageCodec());
-    channel.setMockMessageHandler((dynamic message) async {
-      final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;
-      final MixWithOthersMessage input =
-          MixWithOthersMessage._fromMap(mapMessage);
-      api.setMixWithOthers(input);
-      return {};
-    });
-  }
-}
-
 class VideoPlayerApi {
   Future<void> initialize() async {
     const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
@@ -350,6 +277,29 @@
     }
   }
 
+  Future<void> setPlaybackSpeed(PlaybackSpeedMessage arg) async {
+    final Map<dynamic, dynamic> requestMap = arg._toMap();
+    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+        'dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed',
+        StandardMessageCodec());
+
+    final Map<dynamic, dynamic> replyMap = await channel.send(requestMap);
+    if (replyMap == null) {
+      throw PlatformException(
+          code: 'channel-error',
+          message: 'Unable to establish connection on channel.',
+          details: null);
+    } else if (replyMap['error'] != null) {
+      final Map<dynamic, dynamic> error = replyMap['error'];
+      throw PlatformException(
+          code: error['code'],
+          message: error['message'],
+          details: error['details']);
+    } else {
+      // noop
+    }
+  }
+
   Future<void> play(TextureMessage arg) async {
     final Map<dynamic, dynamic> requestMap = arg._toMap();
     const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
@@ -461,3 +411,144 @@
     }
   }
 }
+
+abstract class TestHostVideoPlayerApi {
+  void initialize();
+  TextureMessage create(CreateMessage arg);
+  void dispose(TextureMessage arg);
+  void setLooping(LoopingMessage arg);
+  void setVolume(VolumeMessage arg);
+  void setPlaybackSpeed(PlaybackSpeedMessage arg);
+  void play(TextureMessage arg);
+  PositionMessage position(TextureMessage arg);
+  void seekTo(PositionMessage arg);
+  void pause(TextureMessage arg);
+  void setMixWithOthers(MixWithOthersMessage arg);
+  static void setup(TestHostVideoPlayerApi api) {
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.initialize',
+          StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        api.initialize();
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.create', StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final CreateMessage input = CreateMessage._fromMap(mapMessage);
+        final TextureMessage output = api.create(input);
+        return <dynamic, dynamic>{'result': output._toMap()};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.dispose', StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final TextureMessage input = TextureMessage._fromMap(mapMessage);
+        api.dispose(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.setLooping',
+          StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final LoopingMessage input = LoopingMessage._fromMap(mapMessage);
+        api.setLooping(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.setVolume',
+          StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final VolumeMessage input = VolumeMessage._fromMap(mapMessage);
+        api.setVolume(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed',
+          StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final PlaybackSpeedMessage input =
+            PlaybackSpeedMessage._fromMap(mapMessage);
+        api.setPlaybackSpeed(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.play', StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final TextureMessage input = TextureMessage._fromMap(mapMessage);
+        api.play(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final TextureMessage input = TextureMessage._fromMap(mapMessage);
+        final PositionMessage output = api.position(input);
+        return <dynamic, dynamic>{'result': output._toMap()};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.seekTo', StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final PositionMessage input = PositionMessage._fromMap(mapMessage);
+        api.seekTo(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.pause', StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final TextureMessage input = TextureMessage._fromMap(mapMessage);
+        api.pause(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+    {
+      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+          'dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers',
+          StandardMessageCodec());
+      channel.setMockMessageHandler((dynamic message) async {
+        final Map<dynamic, dynamic> mapMessage =
+            message as Map<dynamic, dynamic>;
+        final MixWithOthersMessage input =
+            MixWithOthersMessage._fromMap(mapMessage);
+        api.setMixWithOthers(input);
+        return <dynamic, dynamic>{};
+      });
+    }
+  }
+}
diff --git a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart
index 8c0f1de..0ea443f 100644
--- a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart
+++ b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart
@@ -72,6 +72,15 @@
   }
 
   @override
+  Future<void> setPlaybackSpeed(int textureId, double speed) {
+    assert(speed > 0);
+
+    return _api.setPlaybackSpeed(PlaybackSpeedMessage()
+      ..textureId = textureId
+      ..speed = speed);
+  }
+
+  @override
   Future<void> seekTo(int textureId, Duration position) {
     return _api.seekTo(PositionMessage()
       ..textureId = textureId
diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart
index 279810a..2757fb1 100644
--- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart
+++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart
@@ -100,6 +100,11 @@
     throw UnimplementedError('seekTo() has not been implemented.');
   }
 
+  /// Sets the playback speed to a [speed] value indicating the playback rate.
+  Future<void> setPlaybackSpeed(int textureId, double speed) {
+    throw UnimplementedError('setPlaybackSpeed() has not been implemented.');
+  }
+
   /// Gets the video position as [Duration] from the start.
   Future<Duration> getPosition(int textureId) {
     throw UnimplementedError('getPosition() has not been implemented.');
@@ -184,7 +189,7 @@
   network,
 
   /// The video was loaded off of the local filesystem.
-  file
+  file,
 }
 
 /// The file format of the given video.
@@ -199,7 +204,7 @@
   ss,
 
   /// Any format other than the other ones defined in this enum.
-  other
+  other,
 }
 
 /// Event emitted from the platform implementation.
diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml
index 38bc685..cc3cd79 100644
--- a/packages/video_player/video_player_platform_interface/pubspec.yaml
+++ b/packages/video_player/video_player_platform_interface/pubspec.yaml
@@ -3,7 +3,7 @@
 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_platform_interface
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.1.1
+version: 2.2.0
 
 dependencies:
   flutter:
@@ -17,5 +17,5 @@
   pedantic: ^1.8.0
 
 environment:
-  sdk: ">=2.1.0 <3.0.0"
+  sdk: ">=2.8.0 <3.0.0"
   flutter: ">=1.10.0 <2.0.0"
diff --git a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart
index 1859531..c479100 100644
--- a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart
+++ b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart
@@ -4,21 +4,21 @@
 
 import 'dart:ui';
 
-import 'package:mockito/mockito.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
-
+import 'package:mockito/mockito.dart';
+import 'package:video_player_platform_interface/messages.dart';
 import 'package:video_player_platform_interface/method_channel_video_player.dart';
 import 'package:video_player_platform_interface/video_player_platform_interface.dart';
-import 'package:video_player_platform_interface/messages.dart';
 
-class _ApiLogger implements VideoPlayerApiTest {
+class _ApiLogger implements TestHostVideoPlayerApi {
   final List<String> log = [];
   TextureMessage textureMessage;
   CreateMessage createMessage;
   PositionMessage positionMessage;
   LoopingMessage loopingMessage;
   VolumeMessage volumeMessage;
+  PlaybackSpeedMessage playbackSpeedMessage;
   MixWithOthersMessage mixWithOthersMessage;
 
   @override
@@ -81,6 +81,12 @@
     log.add('setVolume');
     volumeMessage = arg;
   }
+
+  @override
+  void setPlaybackSpeed(PlaybackSpeedMessage arg) {
+    log.add('setPlaybackSpeed');
+    playbackSpeedMessage = arg;
+  }
 }
 
 void main() {
@@ -116,7 +122,7 @@
 
     setUp(() {
       log = _ApiLogger();
-      VideoPlayerApiTestSetup(log);
+      TestHostVideoPlayerApi.setup(log);
     });
 
     test('init', () async {
@@ -203,6 +209,13 @@
       expect(log.volumeMessage.volume, 0.7);
     });
 
+    test('setPlaybackSpeed', () async {
+      await player.setPlaybackSpeed(1, 1.5);
+      expect(log.log.last, 'setPlaybackSpeed');
+      expect(log.playbackSpeedMessage.textureId, 1);
+      expect(log.playbackSpeedMessage.speed, 1.5);
+    });
+
     test('seekTo', () async {
       await player.seekTo(1, const Duration(milliseconds: 12345));
       expect(log.log.last, 'seekTo');