diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 207a9ca272ce..68393fd47db3 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.10.1+7 + +* Implemented playback speed feature. +* Bump the minimum Flutter version to 1.2.0. +* Add template type parameter to `invokeMethod` calls. + ## 0.10.1+6 * [iOS] Fixed a memory leak with notification observing. @@ -34,7 +40,6 @@ * Fix a few other IDE warnings. - ## 0.10.0+7 * Android: Fix issue where buffering status in percentage instead of milliseconds diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index 0be5e770101c..8c32af7d0edd 100644 --- a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -16,6 +16,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; @@ -232,6 +233,14 @@ long getPosition() { return exoPlayer.getCurrentPosition(); } + void setSpeed(double value) { + float bracketedValue = (float) value; + PlaybackParameters existingParam = exoPlayer.getPlaybackParameters(); + PlaybackParameters newParameter = + new PlaybackParameters(bracketedValue, existingParam.pitch, existingParam.skipSilence); + exoPlayer.setPlaybackParameters(newParameter); + } + @SuppressWarnings("SuspiciousNameCombination") private void sendInitialized() { if (isInitialized) { @@ -402,6 +411,10 @@ private void onMethodCall(MethodCall call, Result result, long textureId, VideoP videoPlayers.remove(textureId); result.success(null); break; + case "setSpeed": + player.setSpeed((Double) call.argument("speed")); + result.success(null); + break; default: result.notImplemented(); break; diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index bf449ec0e8e2..5014247d1e3b 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -354,6 +354,31 @@ - (void)setVolume:(double)volume { _player.volume = (float)((volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume)); } +- (void)setSpeed:(double)speed result:(FlutterResult)result { + if (speed == 1.0 || speed == 0.0) { + _player.rate = speed; + result(nil); + } else if (speed < 0 || speed > 2.0) { + result([FlutterError errorWithCode:@"unsupported_speed" + message:@"Speed must be >= 0.0 and <= 2.0" + details:nil]); + } else if ((speed > 1.0 && _player.currentItem.canPlayFastForward) || + (speed < 1.0 && _player.currentItem.canPlaySlowForward)) { + _player.rate = speed; + result(nil); + } else { + if (speed > 1.0) { + result([FlutterError errorWithCode:@"unsupported_fast_forward" + message:@"This video cannot be played fast forward" + details:nil]); + } else { + result([FlutterError errorWithCode:@"unsupported_slow_forward" + message:@"This video cannot be played slow forward" + details:nil]); + } + } +} + - (CVPixelBufferRef)copyPixelBuffer { CMTime outputItemTime = [_videoOutput itemTimeForHostTime:CACurrentMediaTime()]; if ([_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { @@ -506,6 +531,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([@"pause" isEqualToString:call.method]) { [player pause]; result(nil); + } else if ([@"setSpeed" isEqualToString:call.method]) { + [player setSpeed:[[argsMap objectForKey:@"speed"] doubleValue] result:result]; + return; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index 3990f53f0d1d..3e1b6ee77dd4 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -5,13 +5,13 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer') - // This will clear all open videos on the platform when a full restart is - // performed. +// This will clear all open videos on the platform when a full restart is +// performed. ..invokeMethod('init'); class DurationRange { @@ -45,6 +45,7 @@ class VideoPlayerValue { this.isBuffering = false, this.volume = 1.0, this.errorDescription, + this.speed = 1.0, }); VideoPlayerValue.uninitialized() : this(duration: null); @@ -85,8 +86,13 @@ class VideoPlayerValue { /// Is null when [initialized] is false. final Size size; + ///The Current speed of the playback. + final double speed; + bool get initialized => duration != null; + bool get hasError => errorDescription != null; + double get aspectRatio => size != null ? size.width / size.height : 1.0; VideoPlayerValue copyWith({ @@ -99,6 +105,7 @@ class VideoPlayerValue { bool isBuffering, double volume, String errorDescription, + double speed, }) { return VideoPlayerValue( duration: duration ?? this.duration, @@ -109,6 +116,7 @@ class VideoPlayerValue { isLooping: isLooping ?? this.isLooping, isBuffering: isBuffering ?? this.isBuffering, volume: volume ?? this.volume, + speed: speed ?? this.speed, errorDescription: errorDescription ?? this.errorDescription, ); } @@ -124,7 +132,8 @@ class VideoPlayerValue { 'isLooping: $isLooping, ' 'isBuffering: $isBuffering' 'volume: $volume, ' - 'errorDescription: $errorDescription)'; + 'errorDescription: $errorDescription' + 'speed: $speed)'; } } @@ -239,6 +248,7 @@ class VideoPlayerController extends ValueNotifier { _applyLooping(); _applyVolume(); _applyPlayPause(); + _applySpeed(); break; case 'completed': value = value.copyWith(isPlaying: false, position: value.duration); @@ -341,6 +351,9 @@ class VideoPlayerController extends ValueNotifier { value = value.copyWith(position: newPosition); }, ); + + // Ensure the video is played at the correct speed + await _applySpeed(); } else { _timer?.cancel(); await _channel.invokeMethod( @@ -397,6 +410,37 @@ class VideoPlayerController extends ValueNotifier { value = value.copyWith(volume: volume.clamp(0.0, 1.0)); await _applyVolume(); } + + Future _applySpeed() async { + if (!value.initialized || _isDisposed) { + return; + } + + // On iOS setting the speed on an AVPlayer starts playing + // the video straightaway. We avoid this surprising behaviour + // by not changing the speed of the player until after the video + // starts playing + if (!value.isPlaying) { + return; + } + + await _channel.invokeMethod( + 'setSpeed', + {'textureId': _textureId, 'speed': value.speed}, + ); + } + + /// Sets the playback speed of [this]. + /// + /// [speed] can be 0.5x, 1x, 2x + /// by default speed value is 1.0 + /// + /// Negative speeds are not supported + /// speeds above 2x are not supported on iOS + Future setSpeed(double speed) async { + value = value.copyWith(speed: speed); + await _applySpeed(); + } } class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index 218c50f64349..c53f06b2fe07 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.1+6 +version: 0.10.1+7 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: @@ -22,4 +22,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.2.0 <2.0.0" diff --git a/packages/video_player/test/video_player_test.dart b/packages/video_player/test/video_player_test.dart index 94bebbd331d8..3911b97bf283 100644 --- a/packages/video_player/test/video_player_test.dart +++ b/packages/video_player/test/video_player_test.dart @@ -37,6 +37,10 @@ class FakeController extends ValueNotifier Future play() async {} @override Future setLooping(bool looping) async {} + @override + Future setSpeed(double speed) async { + value = value.copyWith(speed: speed); + } } void main() { @@ -73,4 +77,19 @@ void main() { ), findsOneWidget); }); + + testWidgets('default playback speed', (WidgetTester tester) async { + final FakeController controller = FakeController(); + controller.textureId = 101; + await tester.pumpWidget(VideoPlayer(controller)); + expect(controller.value.speed, 1.0); + }); + + testWidgets('Changed playback speed', (WidgetTester tester) async { + final FakeController controller = FakeController(); + controller.textureId = 101; + controller.setSpeed(1.5); + await tester.pumpWidget(VideoPlayer(controller)); + expect(controller.value.speed, 1.5); + }); }