From 3dd5099b4d2d70038d02587a17e12506815663d5 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sat, 28 Jul 2018 10:08:28 +0100 Subject: [PATCH 01/32] * Added access to the video rotation in degrees (rotationDegrees). Possible values are: 0, 90, 180 and 270. * Updated the aspectRatio getter to use the video rotation. * Fixed the aspect ratio of videos in the video_player plugin example. It now properly displays 'rotated' videos. --- packages/video_player/CHANGELOG.md | 6 +++++ .../videoplayer/VideoPlayerPlugin.java | 1 + .../ios/Classes/VideoPlayerPlugin.h | 2 +- .../ios/Classes/VideoPlayerPlugin.m | 21 ++++++++++++++-- packages/video_player/lib/video_player.dart | 25 ++++++++++++++++--- packages/video_player/pubspec.yaml | 2 +- 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index b01360037cbe..20f369e812f1 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.6.5 + +* Added access to the video rotation in degrees (rotationDegrees). Possible values are: 0, 90, 180 and 270. +* Updated the aspectRatio getter to use the video rotation. +* Fixed the aspect ratio of videos in the video_player plugin example. It now properly displays 'rotated' videos. + ## 0.6.4 * Android: add support for hls, dash and ss video formats. 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 b65373ae0fe9..26b45b387fc8 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 @@ -217,6 +217,7 @@ private void sendInitialized() { if (exoPlayer.getVideoFormat() != null) { event.put("width", exoPlayer.getVideoFormat().width); event.put("height", exoPlayer.getVideoFormat().height); + event.put("rotationDegrees", exoPlayer.getVideoFormat().rotationDegrees); } eventSink.success(event); } diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.h b/packages/video_player/ios/Classes/VideoPlayerPlugin.h index d5166b1782ad..18fdcca6d54e 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.h +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.h @@ -4,5 +4,5 @@ #import -@interface FLTVideoPlayerPlugin : NSObject +@interface FLTVideoPlayerPlugin : NSObject @end diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 7699831710c5..3102f5a624f7 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -26,7 +26,7 @@ - (void)onDisplayLink:(CADisplayLink*)link { } @end -@interface FLTVideoPlayer : NSObject +@interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayer* player; @property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; @property(readonly, nonatomic) CADisplayLink* displayLink; @@ -198,14 +198,31 @@ - (void)updatePlayingState { _displayLink.paused = !_isPlaying; } +static inline CGFloat radiansToDegrees(CGFloat radians) { + // Input range [-pi, pi] or [-180, 180] + CGFloat degrees = radians * 180 / M_PI; + if (degrees < 0) { + // Convert -90 to 270 and -180 to 180 + return degrees + 360; + } + // Output degrees in between [0, 360[ + return degrees; +}; + - (void)sendInitialized { if (_eventSink && _isInitialized) { CGSize size = [self.player currentItem].presentationSize; + CGAffineTransform transform = [[self.player currentItem] asset].preferredTransform; + // atan2 returns values in the closed interval [-pi,pi]. See: + // https://www.mathworks.com/help/matlab/ref/atan2.html#buct8h0-4 + NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a))); + _eventSink(@{ @"event" : @"initialized", @"duration" : @([self duration]), @"width" : @(size.width), @"height" : @(size.height), + @"rotationDegrees" : @(rotationDegrees), }); } } @@ -352,7 +369,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; _players[@(textureId)] = player; - result(@{ @"textureId" : @(textureId) }); + result(@{@"textureId" : @(textureId)}); } else { NSDictionary* argsMap = call.arguments; int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue; diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index 6d59ff5422de..22041b7181c0 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -10,8 +10,8 @@ import 'package:flutter/material.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 { @@ -68,6 +68,13 @@ class VideoPlayerValue { /// Is null when [initialized] is false. final Size size; + /// The [rotationDegrees] of the currently loaded video. + /// Possible values are: 0 or 180 for videos recorded in landscape format, + /// 90 and 270 for videos taken in portrait mode. + /// + /// Is null when [initialized] is false. + final int rotationDegrees; + VideoPlayerValue({ @required this.duration, this.size, @@ -78,6 +85,7 @@ class VideoPlayerValue { this.isBuffering: false, this.volume: 1.0, this.errorDescription, + this.rotationDegrees, }); VideoPlayerValue.uninitialized() : this(duration: null); @@ -86,8 +94,12 @@ class VideoPlayerValue { : this(duration: null, errorDescription: errorDescription); bool get initialized => duration != null; + bool get hasError => errorDescription != null; - double get aspectRatio => size.width / size.height; + + double get aspectRatio => rotationDegrees == 0 || rotationDegrees == 180 + ? size.width / size.height + : size.height / size.width; VideoPlayerValue copyWith({ Duration duration, @@ -99,6 +111,7 @@ class VideoPlayerValue { bool isBuffering, double volume, String errorDescription, + int rotationDegrees, }) { return new VideoPlayerValue( duration: duration ?? this.duration, @@ -110,6 +123,7 @@ class VideoPlayerValue { isBuffering: isBuffering ?? this.isBuffering, volume: volume ?? this.volume, errorDescription: errorDescription ?? this.errorDescription, + rotationDegrees: rotationDegrees ?? this.rotationDegrees, ); } @@ -124,7 +138,8 @@ class VideoPlayerValue { 'isLooping: $isLooping, ' 'isBuffering: $isBuffering' 'volume: $volume, ' - 'errorDescription: $errorDescription)'; + 'errorDescription: $errorDescription,' + 'rotationDegrees: $rotationDegrees)'; } } @@ -223,6 +238,7 @@ class VideoPlayerController extends ValueNotifier { value = value.copyWith( duration: new Duration(milliseconds: map['duration']), size: new Size(map['width'].toDouble(), map['height'].toDouble()), + rotationDegrees: map['rotationDegrees'].toInt(), ); initializingCompleter.complete(null); _applyLooping(); @@ -544,6 +560,7 @@ class _VideoProgressIndicatorState extends State { } VideoPlayerController get controller => widget.controller; + VideoProgressColors get colors => widget.colors; @override diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index 7de6a44f372f..ef07788e231d 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.6.4 +version: 0.6.5 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: From bb8e0b56d2831fc78b0b66fa980705e57516797e Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sat, 28 Jul 2018 11:26:16 +0100 Subject: [PATCH 02/32] Bugfix: rotated videos will now be displayed with the correct aspect ratio in image_picker --- packages/image_picker/CHANGELOG.md | 4 ++++ packages/image_picker/example/lib/main.dart | 3 +-- packages/image_picker/example/pubspec.yaml | 4 +++- packages/image_picker/ios/Classes/ImagePickerPlugin.h | 2 +- packages/image_picker/ios/Classes/ImagePickerPlugin.m | 2 +- packages/image_picker/pubspec.yaml | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 7a78c2eab720..7295bc9c87c4 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.7 + +* Bugfix: rotated videos will now be displayed with the correct aspect ratio. + ## 0.4.6 * Added support for picking remote images. diff --git a/packages/image_picker/example/lib/main.dart b/packages/image_picker/example/lib/main.dart index e0312f9ed660..fdc63917833e 100755 --- a/packages/image_picker/example/lib/main.dart +++ b/packages/image_picker/example/lib/main.dart @@ -226,10 +226,9 @@ class AspectRatioVideoState extends State { @override Widget build(BuildContext context) { if (initialized) { - final Size size = controller.value.size; return new Center( child: new AspectRatio( - aspectRatio: size.width / size.height, + aspectRatio: controller.value.aspectRatio, child: new VideoPlayer(controller), ), ); diff --git a/packages/image_picker/example/pubspec.yaml b/packages/image_picker/example/pubspec.yaml index 8fbf4f47b6ad..d46b03ab40c1 100755 --- a/packages/image_picker/example/pubspec.yaml +++ b/packages/image_picker/example/pubspec.yaml @@ -2,12 +2,14 @@ name: image_picker_example description: Demonstrates how to use the image_picker plugin. dependencies: - video_player: "0.5.2" + video_player: + path: ../../video_player/ flutter: sdk: flutter image_picker: path: ../ + # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/packages/image_picker/ios/Classes/ImagePickerPlugin.h b/packages/image_picker/ios/Classes/ImagePickerPlugin.h index f070ece9918e..f7825ce7b6d9 100644 --- a/packages/image_picker/ios/Classes/ImagePickerPlugin.h +++ b/packages/image_picker/ios/Classes/ImagePickerPlugin.h @@ -4,5 +4,5 @@ #import -@interface FLTImagePickerPlugin : NSObject +@interface FLTImagePickerPlugin : NSObject @end diff --git a/packages/image_picker/ios/Classes/ImagePickerPlugin.m b/packages/image_picker/ios/Classes/ImagePickerPlugin.m index 8ddf86c5d5a9..c39d58f40cfe 100644 --- a/packages/image_picker/ios/Classes/ImagePickerPlugin.m +++ b/packages/image_picker/ios/Classes/ImagePickerPlugin.m @@ -8,7 +8,7 @@ #import #import -@interface FLTImagePickerPlugin () +@interface FLTImagePickerPlugin () @end static const int SOURCE_CAMERA = 0; diff --git a/packages/image_picker/pubspec.yaml b/packages/image_picker/pubspec.yaml index cc835ff335d8..e72d03784b4c 100755 --- a/packages/image_picker/pubspec.yaml +++ b/packages/image_picker/pubspec.yaml @@ -5,7 +5,7 @@ authors: - Flutter Team - Rhodes Davis Jr. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.4.6 +version: 0.4.7 flutter: plugin: From f3858494095fea995db7274e2c8474aec64430c1 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sat, 28 Jul 2018 13:30:05 +0100 Subject: [PATCH 03/32] Fix format. Use clang-format-5.0 instead of clang-format (7.0) --- packages/image_picker/ios/Classes/ImagePickerPlugin.h | 2 +- packages/image_picker/ios/Classes/ImagePickerPlugin.m | 2 +- packages/video_player/ios/Classes/VideoPlayerPlugin.h | 2 +- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/image_picker/ios/Classes/ImagePickerPlugin.h b/packages/image_picker/ios/Classes/ImagePickerPlugin.h index f7825ce7b6d9..f070ece9918e 100644 --- a/packages/image_picker/ios/Classes/ImagePickerPlugin.h +++ b/packages/image_picker/ios/Classes/ImagePickerPlugin.h @@ -4,5 +4,5 @@ #import -@interface FLTImagePickerPlugin : NSObject +@interface FLTImagePickerPlugin : NSObject @end diff --git a/packages/image_picker/ios/Classes/ImagePickerPlugin.m b/packages/image_picker/ios/Classes/ImagePickerPlugin.m index c39d58f40cfe..8ddf86c5d5a9 100644 --- a/packages/image_picker/ios/Classes/ImagePickerPlugin.m +++ b/packages/image_picker/ios/Classes/ImagePickerPlugin.m @@ -8,7 +8,7 @@ #import #import -@interface FLTImagePickerPlugin () +@interface FLTImagePickerPlugin () @end static const int SOURCE_CAMERA = 0; diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.h b/packages/video_player/ios/Classes/VideoPlayerPlugin.h index 18fdcca6d54e..d5166b1782ad 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.h +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.h @@ -4,5 +4,5 @@ #import -@interface FLTVideoPlayerPlugin : NSObject +@interface FLTVideoPlayerPlugin : NSObject @end diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 3102f5a624f7..2ee06404d52b 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -26,7 +26,7 @@ - (void)onDisplayLink:(CADisplayLink*)link { } @end -@interface FLTVideoPlayer : NSObject +@interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayer* player; @property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; @property(readonly, nonatomic) CADisplayLink* displayLink; @@ -369,7 +369,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; _players[@(textureId)] = player; - result(@{@"textureId" : @(textureId)}); + result(@{ @"textureId" : @(textureId) }); } else { NSDictionary* argsMap = call.arguments; int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue; From 7943a8f2751f2bbb680f6b6c644eab384aa009be Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sat, 28 Jul 2018 13:51:26 +0100 Subject: [PATCH 04/32] Fix format. Remove spurious extra line. --- packages/image_picker/example/pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/image_picker/example/pubspec.yaml b/packages/image_picker/example/pubspec.yaml index d46b03ab40c1..a305b20eb8c7 100755 --- a/packages/image_picker/example/pubspec.yaml +++ b/packages/image_picker/example/pubspec.yaml @@ -9,7 +9,6 @@ dependencies: image_picker: path: ../ - # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec From 9a7a974337b493e794b479a2b7c9764e1a8d7617 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sun, 29 Jul 2018 18:01:57 +0100 Subject: [PATCH 05/32] Fix race condition in video_player that prevented some videos from being displayed --- packages/video_player/CHANGELOG.md | 4 +++ packages/video_player/README.md | 13 +++------- .../videoplayer/VideoPlayerPlugin.java | 26 +++++++++++++++++-- packages/video_player/lib/video_player.dart | 8 ++++++ packages/video_player/pubspec.yaml | 2 +- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index b01360037cbe..4eaebf3d78ac 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.5 + +* Fixed race condition that prevented some videos from being displayed + ## 0.6.4 * Android: add support for hls, dash and ss video formats. diff --git a/packages/video_player/README.md b/packages/video_player/README.md index 3a2a399724f9..8bb4b4995dbb 100644 --- a/packages/video_player/README.md +++ b/packages/video_player/README.md @@ -64,17 +64,12 @@ class _VideoAppState extends State { 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4', ) ..addListener(() { - final bool isPlaying = _controller.value.isPlaying; - if (isPlaying != _isPlaying) { - setState(() { - _isPlaying = isPlaying; - }); - } - }) - ..initialize().then((_) { + print('completed listener: $_controller'); // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); - }); + }) + ..initialize() + .catchError((dynamic error) => print('Unexpected error: $error')); } @override 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 b65373ae0fe9..4b91bad16772 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 @@ -11,6 +11,7 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Build; +import android.util.Log; import android.view.Surface; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -49,6 +50,8 @@ public class VideoPlayerPlugin implements MethodCallHandler { + private static final String TAG = "VideoPlayerPlugin"; + private static class VideoPlayer { private SimpleExoPlayer exoPlayer; @@ -62,6 +65,7 @@ private static class VideoPlayer { private final EventChannel eventChannel; private boolean isInitialized = false; + private boolean isStateReady = false; VideoPlayer( Context context, @@ -126,6 +130,7 @@ private void setupVideoPlayer( new EventChannel.StreamHandler() { @Override public void onListen(Object o, EventChannel.EventSink sink) { + Log.d(TAG, "EventSink set to:" + sink); eventSink = sink; } @@ -155,8 +160,10 @@ public void onPlayerStateChanged(final boolean playWhenReady, final int playback eventSink.success(event); } } else if (playbackState == Player.STATE_READY && !isInitialized) { - isInitialized = true; + isStateReady = true; sendInitialized(); + } else { + Log.d(TAG, "Received unhandled state: " + playbackState); } } @@ -210,7 +217,9 @@ long getPosition() { } private void sendInitialized() { - if (isInitialized && eventSink != null) { + if (!isInitialized && isStateReady && eventSink != null) { + Log.d(TAG, "sending isInitialized"); + isInitialized = true; Map event = new HashMap<>(); event.put("event", "initialized"); event.put("duration", exoPlayer.getDuration()); @@ -219,6 +228,16 @@ private void sendInitialized() { event.put("height", exoPlayer.getVideoFormat().height); } eventSink.success(event); + } else { + Log.e( + TAG, + "failed sending sendInitialized(isInitialized: " + + isInitialized + + ",isStateReady: " + + isStateReady + + ", eventSink: " + + eventSink + + ")"); } } @@ -351,6 +370,9 @@ private void onMethodCall(MethodCall call, Result result, long textureId, VideoP videoPlayers.remove(textureId); result.success(null); break; + case "sendInitialized": + player.sendInitialized(); + break; default: result.notImplemented(); break; diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index 6d59ff5422de..43dfca21dcbb 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -257,6 +257,14 @@ class VideoPlayerController extends ValueNotifier { _eventSubscription = _eventChannelFor(_textureId) .receiveBroadcastStream() .listen(eventListener, onError: errorListener); + + // Ensure init is called regardless of whether the init event was sent + // before we could subscribe to the broadcast channel + await _channel.invokeMethod( + 'sendInitialized', + {'textureId': _textureId}, + ); + return initializingCompleter.future; } diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index 7de6a44f372f..ef07788e231d 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.6.4 +version: 0.6.5 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: From 0332538a9c7a43043ef1c0f7437b9a36cae9fdb7 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Mon, 13 Aug 2018 13:17:57 -0500 Subject: [PATCH 06/32] Ensure videos are not upside down nor streteched when using the video_player plugin on iOS devices. The fix sets the videoComposition of the respective AVPlayerItem to use the preferredTransform of the video (the rotation matrix for the video) --- .../ios/Classes/VideoPlayerPlugin.m | 106 +++++++++++++++--- 1 file changed, 90 insertions(+), 16 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 2ee06404d52b..faf867c656ab 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -32,6 +32,7 @@ @interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) CADisplayLink* displayLink; @property(nonatomic) FlutterEventChannel* eventChannel; @property(nonatomic) FlutterEventSink eventSink; +@property(nonatomic) CGAffineTransform preferredTransform; @property(nonatomic, readonly) bool disposed; @property(nonatomic, readonly) bool isPlaying; @property(nonatomic, readonly) bool isLooping; @@ -55,14 +56,7 @@ - (instancetype)initWithAsset:(NSString*)asset frameUpdater:(FLTFrameUpdater*)fr return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater]; } -- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _isInitialized = false; - _isPlaying = false; - _disposed = false; - - AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; +- (void)addObservers:(AVPlayerItem*)item { [item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew @@ -84,8 +78,6 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:playbackBufferFullContext]; - _player = [AVPlayer playerWithPlayerItem:item]; - _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:[_player currentItem] queue:[NSOperationQueue mainQueue] @@ -99,12 +91,67 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda } } }]; +} + +- (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransform)transform + withAsset:(AVAsset*)asset + withVideoTrack:(AVAssetTrack*)videoTrack { + // CGAffineTransform t = _preferredTransform; + // NSLog(@"Affine1 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, + // t.ty); + + AVMutableVideoCompositionInstruction* instruction = + [AVMutableVideoCompositionInstruction videoCompositionInstruction]; + instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [asset duration]); + AVMutableVideoCompositionLayerInstruction* layerInstruction = + [AVMutableVideoCompositionLayerInstruction + videoCompositionLayerInstructionWithAssetTrack:videoTrack]; + [layerInstruction setTransform:_preferredTransform atTime:kCMTimeZero]; + + AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition]; + instruction.layerInstructions = @[ layerInstruction ]; + videoComposition.instructions = @[ instruction ]; + + // If in portrait mode, switch the width and height of the video + float width = videoTrack.naturalSize.width; + float height = videoTrack.naturalSize.height; + NSInteger rotationDegrees = + (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); + if (rotationDegrees == 90 || rotationDegrees == 270) { + width = videoTrack.naturalSize.height; + height = videoTrack.naturalSize.width; + } + videoComposition.renderSize = CGSizeMake(width, height); + + // TODO use videoTrack.nominalFrameRate ? + // Currently set at 30 FPS + videoComposition.frameDuration = CMTimeMake(1, 30); + + return videoComposition; +} + +- (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater*)frameUpdater { NSDictionary* pixBuffAttributes = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), (id)kCVPixelBufferIOSurfacePropertiesKey : @{} }; _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; + _displayLink = + [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + _displayLink.paused = YES; +} + +- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _isInitialized = false; + _isPlaying = false; + _disposed = false; + + AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; + AVAsset* asset = [item asset]; void (^assetCompletionHandler)(void) = ^{ if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) { @@ -115,7 +162,17 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda if (_disposed) return; if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == AVKeyValueStatusLoaded) { + // Rotate the video by using a videoComposition and the preferredTransform + _preferredTransform = videoTrack.preferredTransform; + // Note: + // https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition + // Aideo composition can only be used with file-based media and is not supported for use + // with media served using HTTP Live Streaming. + item.videoComposition = [self getVideoCompositionWithTransform:_preferredTransform + withAsset:asset + withVideoTrack:videoTrack]; dispatch_async(dispatch_get_main_queue(), ^{ + // Without this line, videos are not always rendered in app. TODO explain why [_player replaceCurrentItemWithPlayerItem:item]; }); } @@ -125,11 +182,16 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda } } }; + + _player = [AVPlayer playerWithPlayerItem:item]; + _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; + + [self createVideoOutputAndDisplayLink:frameUpdater]; + + [self addObservers:item]; + [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler]; - _displayLink = - [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; - [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - _displayLink.paused = YES; + return self; } @@ -212,10 +274,22 @@ static inline CGFloat radiansToDegrees(CGFloat radians) { - (void)sendInitialized { if (_eventSink && _isInitialized) { CGSize size = [self.player currentItem].presentationSize; - CGAffineTransform transform = [[self.player currentItem] asset].preferredTransform; + // CGAffineTransform transform = [_player currentItem] asset].preferredTransform; + // CGAffineTransform transform = [[[_player currentItem] asset] preferredTransform]; + // NSArray* tracks = [[[_player currentItem] asset] tracksWithMediaType:AVMediaTypeVideo]; + // NSLog(@"Track lenght: %d", tracks.count); + // CGAffineTransform transform = [[tracks objectAtIndex:0] preferredTransform]; // atan2 returns values in the closed interval [-pi,pi]. See: // https://www.mathworks.com/help/matlab/ref/atan2.html#buct8h0-4 - NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a))); + // https://developer.apple.com/documentation/coregraphics/cgaffinetransform + // https://en.wikipedia.org/wiki/File:2D_affine_transformation_matrix.svg + NSInteger rotationDegrees = + (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); + + CGAffineTransform t = _preferredTransform; + NSLog(@"atan2: %f", atan2(_preferredTransform.b, _preferredTransform.a)); + NSLog(@"Rotation degrees: %d \tFrom (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", + (int)rotationDegrees, t.a, t.b, t.c, t.d, t.tx, t.ty); _eventSink(@{ @"event" : @"initialized", From 7ef3092c76faceda2845e68a0a4a9c8c4b5382b7 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Mon, 13 Aug 2018 13:17:57 -0500 Subject: [PATCH 07/32] Ensure videos are not upside down nor streteched when using the video_player plugin on iOS devices. The fix sets the videoComposition of the respective AVPlayerItem to use the preferredTransform of the video (the rotation matrix for the video) --- .../ios/Classes/VideoPlayerPlugin.m | 106 +++++++++++++++--- 1 file changed, 90 insertions(+), 16 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 2ee06404d52b..faf867c656ab 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -32,6 +32,7 @@ @interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) CADisplayLink* displayLink; @property(nonatomic) FlutterEventChannel* eventChannel; @property(nonatomic) FlutterEventSink eventSink; +@property(nonatomic) CGAffineTransform preferredTransform; @property(nonatomic, readonly) bool disposed; @property(nonatomic, readonly) bool isPlaying; @property(nonatomic, readonly) bool isLooping; @@ -55,14 +56,7 @@ - (instancetype)initWithAsset:(NSString*)asset frameUpdater:(FLTFrameUpdater*)fr return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater]; } -- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _isInitialized = false; - _isPlaying = false; - _disposed = false; - - AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; +- (void)addObservers:(AVPlayerItem*)item { [item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew @@ -84,8 +78,6 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:playbackBufferFullContext]; - _player = [AVPlayer playerWithPlayerItem:item]; - _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:[_player currentItem] queue:[NSOperationQueue mainQueue] @@ -99,12 +91,67 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda } } }]; +} + +- (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransform)transform + withAsset:(AVAsset*)asset + withVideoTrack:(AVAssetTrack*)videoTrack { + // CGAffineTransform t = _preferredTransform; + // NSLog(@"Affine1 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, + // t.ty); + + AVMutableVideoCompositionInstruction* instruction = + [AVMutableVideoCompositionInstruction videoCompositionInstruction]; + instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [asset duration]); + AVMutableVideoCompositionLayerInstruction* layerInstruction = + [AVMutableVideoCompositionLayerInstruction + videoCompositionLayerInstructionWithAssetTrack:videoTrack]; + [layerInstruction setTransform:_preferredTransform atTime:kCMTimeZero]; + + AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition]; + instruction.layerInstructions = @[ layerInstruction ]; + videoComposition.instructions = @[ instruction ]; + + // If in portrait mode, switch the width and height of the video + float width = videoTrack.naturalSize.width; + float height = videoTrack.naturalSize.height; + NSInteger rotationDegrees = + (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); + if (rotationDegrees == 90 || rotationDegrees == 270) { + width = videoTrack.naturalSize.height; + height = videoTrack.naturalSize.width; + } + videoComposition.renderSize = CGSizeMake(width, height); + + // TODO use videoTrack.nominalFrameRate ? + // Currently set at 30 FPS + videoComposition.frameDuration = CMTimeMake(1, 30); + + return videoComposition; +} + +- (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater*)frameUpdater { NSDictionary* pixBuffAttributes = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), (id)kCVPixelBufferIOSurfacePropertiesKey : @{} }; _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; + _displayLink = + [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + _displayLink.paused = YES; +} + +- (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { + self = [super init]; + NSAssert(self, @"super init cannot be nil"); + _isInitialized = false; + _isPlaying = false; + _disposed = false; + + AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; + AVAsset* asset = [item asset]; void (^assetCompletionHandler)(void) = ^{ if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) { @@ -115,7 +162,17 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda if (_disposed) return; if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == AVKeyValueStatusLoaded) { + // Rotate the video by using a videoComposition and the preferredTransform + _preferredTransform = videoTrack.preferredTransform; + // Note: + // https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition + // Aideo composition can only be used with file-based media and is not supported for use + // with media served using HTTP Live Streaming. + item.videoComposition = [self getVideoCompositionWithTransform:_preferredTransform + withAsset:asset + withVideoTrack:videoTrack]; dispatch_async(dispatch_get_main_queue(), ^{ + // Without this line, videos are not always rendered in app. TODO explain why [_player replaceCurrentItemWithPlayerItem:item]; }); } @@ -125,11 +182,16 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda } } }; + + _player = [AVPlayer playerWithPlayerItem:item]; + _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; + + [self createVideoOutputAndDisplayLink:frameUpdater]; + + [self addObservers:item]; + [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler]; - _displayLink = - [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; - [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - _displayLink.paused = YES; + return self; } @@ -212,10 +274,22 @@ static inline CGFloat radiansToDegrees(CGFloat radians) { - (void)sendInitialized { if (_eventSink && _isInitialized) { CGSize size = [self.player currentItem].presentationSize; - CGAffineTransform transform = [[self.player currentItem] asset].preferredTransform; + // CGAffineTransform transform = [_player currentItem] asset].preferredTransform; + // CGAffineTransform transform = [[[_player currentItem] asset] preferredTransform]; + // NSArray* tracks = [[[_player currentItem] asset] tracksWithMediaType:AVMediaTypeVideo]; + // NSLog(@"Track lenght: %d", tracks.count); + // CGAffineTransform transform = [[tracks objectAtIndex:0] preferredTransform]; // atan2 returns values in the closed interval [-pi,pi]. See: // https://www.mathworks.com/help/matlab/ref/atan2.html#buct8h0-4 - NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a))); + // https://developer.apple.com/documentation/coregraphics/cgaffinetransform + // https://en.wikipedia.org/wiki/File:2D_affine_transformation_matrix.svg + NSInteger rotationDegrees = + (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); + + CGAffineTransform t = _preferredTransform; + NSLog(@"atan2: %f", atan2(_preferredTransform.b, _preferredTransform.a)); + NSLog(@"Rotation degrees: %d \tFrom (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", + (int)rotationDegrees, t.a, t.b, t.c, t.d, t.tx, t.ty); _eventSink(@{ @"event" : @"initialized", From 9e74b423ba15bb6feab4039d93d4b2f8a20c259c Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Mon, 13 Aug 2018 15:18:55 -0500 Subject: [PATCH 08/32] Add support for 'sendInitialized' flutter event that fixes race condition in Android (possibly happening in iOS too) --- .../video_player/ios/Classes/VideoPlayerPlugin.m | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 7699831710c5..57ebb0111ce5 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -35,6 +35,7 @@ @interface FLTVideoPlayer : NSObject @property(nonatomic, readonly) bool disposed; @property(nonatomic, readonly) bool isPlaying; @property(nonatomic, readonly) bool isLooping; +@property(nonatomic, readonly) bool isReady; @property(nonatomic, readonly) bool isInitialized; - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater; - (void)play; @@ -58,6 +59,7 @@ - (instancetype)initWithAsset:(NSString*)asset frameUpdater:(FLTFrameUpdater*)fr - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { self = [super init]; NSAssert(self, @"super init cannot be nil"); + _isReady = false; _isInitialized = false; _isPlaying = false; _disposed = false; @@ -162,7 +164,7 @@ - (void)observeValueForKeyPath:(NSString*)path case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: - _isInitialized = true; + _isReady = true; [item addOutput:_videoOutput]; [self sendInitialized]; [self updatePlayingState]; @@ -187,7 +189,7 @@ - (void)observeValueForKeyPath:(NSString*)path } - (void)updatePlayingState { - if (!_isInitialized) { + if (!_isReady) { return; } if (_isPlaying) { @@ -199,7 +201,8 @@ - (void)updatePlayingState { } - (void)sendInitialized { - if (_eventSink && _isInitialized) { + if (_eventSink && !_isInitialized && isReady) { + _isInitialized = true; CGSize size = [self.player currentItem].presentationSize; _eventSink(@{ @"event" : @"initialized", @@ -379,6 +382,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([@"pause" isEqualToString:call.method]) { [player pause]; result(nil); + } else if ([@"sendInitialized" isEqualToString:call.method]) { + [player sendInitialized]; + result(nil); } else { result(FlutterMethodNotImplemented); } From fd5c298e939beb5186479ca0321991c7bb93f0af Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Wed, 15 Aug 2018 15:52:36 -0500 Subject: [PATCH 09/32] Ensure with and height of a video is given after rotation. i.e. If video is in portratit mode then height > width --- .../plugins/videoplayer/VideoPlayerPlugin.java | 15 ++++++++++++--- packages/video_player/lib/video_player.dart | 9 +++++---- 2 files changed, 17 insertions(+), 7 deletions(-) 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 673b35c90446..512730a90473 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 @@ -220,13 +220,22 @@ private void sendInitialized() { if (!isInitialized && isStateReady && eventSink != null) { Log.d(TAG, "sending isInitialized"); isInitialized = true; + + int width = exoPlayer.getVideoFormat().width; + int height = exoPlayer.getVideoFormat().height; + int rotationDegrees = exoPlayer.getVideoFormat().rotationDegrees; + if (rotationDegrees == 90 || rotationDegrees == 270) { + width = exoPlayer.getVideoFormat().height; + height = exoPlayer.getVideoFormat().width; + } + Map event = new HashMap<>(); event.put("event", "initialized"); event.put("duration", exoPlayer.getDuration()); if (exoPlayer.getVideoFormat() != null) { - event.put("width", exoPlayer.getVideoFormat().width); - event.put("height", exoPlayer.getVideoFormat().height); - event.put("rotationDegrees", exoPlayer.getVideoFormat().rotationDegrees); + event.put("width", width); + event.put("height", height); + event.put("rotationDegrees", rotationDegrees); } eventSink.success(event); } else { diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index c3ae64e50385..4806012e3967 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -97,9 +97,7 @@ class VideoPlayerValue { bool get hasError => errorDescription != null; - double get aspectRatio => rotationDegrees == 0 || rotationDegrees == 180 - ? size.width / size.height - : size.height / size.width; + double get aspectRatio => size.width / size.height; VideoPlayerValue copyWith({ Duration duration, @@ -193,7 +191,9 @@ class VideoPlayerController extends ValueNotifier { /// This will load the file from the file-URI given by: /// `'file://${file.path}'`. VideoPlayerController.file(File file) - : dataSource = 'file://${file.path}', + : dataSource = file.path.startsWith('phasset://') + ? file.path + : 'file://${file.path}', dataSourceType = DataSourceType.file, super(new VideoPlayerValue(duration: null)); @@ -214,6 +214,7 @@ class VideoPlayerController extends ValueNotifier { break; case DataSourceType.file: dataSourceDescription = {'uri': dataSource}; + // dataSourceDescription = {'phAsset': dataSource}; } final Map response = await _channel.invokeMethod( 'create', From f0ade3cf3e366ec1e93139458f2bdf32d243b2be Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Wed, 15 Aug 2018 15:56:03 -0500 Subject: [PATCH 10/32] - Merge fix_race_condition branch so sendInitialized is only sent once. Plus remove extra sendInitialized call - Add support for playing PHAssets from localIdentifier. Currently used from flutter like File('phasset://LOCAL_IDENTIFIER') --- .../ios/Classes/VideoPlayerPlugin.m | 125 +++++++++++++----- 1 file changed, 90 insertions(+), 35 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 44953f20dfce..82b0099528aa 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -4,6 +4,7 @@ #import "VideoPlayerPlugin.h" #import +#import int64_t FLTCMTimeToMillis(CMTime time) { return time.value * 1000 / time.timescale; } @@ -57,8 +58,29 @@ - (instancetype)initWithAsset:(NSString*)asset frameUpdater:(FLTFrameUpdater*)fr return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater]; } +// resultHandler:(void (^)(FLTVideoPlayer *playerItem, FLTFrameUpdater *frameUpdater))resultHandler + +- (void)initWithPHAssetLocalIdentifier:(NSString*)localIdentifier + frameUpdater:(FLTFrameUpdater*)frameUpdater + onPlayerSetup:(void (^)(FLTVideoPlayer* playerItem))onPlayerSetup { + PHFetchResult* phFetchResult = + [PHAsset fetchAssetsWithLocalIdentifiers:@[ localIdentifier ] options:nil]; + // TODO what to do if the asset cannot be loaded? Send an error to flutter? + PHAsset* phAsset = [phFetchResult firstObject]; + NSLog(@"PHFetchResult loaded: %@", phFetchResult); + NSLog(@"PHAsset loaded: %@", phAsset); + PHCachingImageManager* imageManager = [[PHCachingImageManager alloc] init]; + [imageManager requestPlayerItemForVideo:phAsset + options:nil + resultHandler:^(AVPlayerItem* _Nullable playerItem, + NSDictionary* _Nullable info) { + FLTVideoPlayer* player = + [self initWithPlayerItem:playerItem frameUpdater:frameUpdater]; + onPlayerSetup(player); + }]; +} + - (void)addObservers:(AVPlayerItem*)item { - [item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew @@ -98,9 +120,8 @@ - (void)addObservers:(AVPlayerItem*)item { - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransform)transform withAsset:(AVAsset*)asset withVideoTrack:(AVAssetTrack*)videoTrack { - // CGAffineTransform t = _preferredTransform; - // NSLog(@"Affine1 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, - // t.ty); + CGAffineTransform t = _preferredTransform; + NSLog(@"Affine1 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, t.ty); AVMutableVideoCompositionInstruction* instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; @@ -123,6 +144,7 @@ - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransfor width = videoTrack.naturalSize.height; height = videoTrack.naturalSize.width; } + NSLog(@"Using width, height: %f, %f", width, height); videoComposition.renderSize = CGSizeMake(width, height); // TODO use videoTrack.nominalFrameRate ? @@ -146,6 +168,11 @@ - (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater*)frameUpdater { } - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater { + AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; + return [self initWithPlayerItem:item frameUpdater:frameUpdater]; +} + +- (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpdater*)frameUpdater { self = [super init]; NSAssert(self, @"super init cannot be nil"); _isInitialized = false; @@ -153,8 +180,6 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda _isPlaying = false; _disposed = false; - AVPlayerItem* item = [AVPlayerItem playerItemWithURL:url]; - AVAsset* asset = [item asset]; void (^assetCompletionHandler)(void) = ^{ if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) { @@ -165,6 +190,9 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda if (_disposed) return; if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == AVKeyValueStatusLoaded) { + CGSize size = item.presentationSize; + NSLog(@"preferredTransform width, height: %f, %f", size.width, size.height); + // Rotate the video by using a videoComposition and the preferredTransform _preferredTransform = videoTrack.preferredTransform; // Note: @@ -174,10 +202,11 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda item.videoComposition = [self getVideoCompositionWithTransform:_preferredTransform withAsset:asset withVideoTrack:videoTrack]; - dispatch_async(dispatch_get_main_queue(), ^{ - // Without this line, videos are not always rendered in app. TODO explain why - [_player replaceCurrentItemWithPlayerItem:item]; - }); + dispatch_async( + dispatch_get_main_queue(), ^{ + // Without this line, videos are not always rendered in app. TODO explain why + // [_player replaceCurrentItemWithPlayerItem:item]; + }); } }; [videoTrack loadValuesAsynchronouslyForKeys:@[ @"preferredTransform" ] @@ -189,6 +218,9 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda _player = [AVPlayer playerWithPlayerItem:item]; _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; + CGSize size = item.presentationSize; + NSLog(@"_player create width, height: %f, %f", size.width, size.height); + [self createVideoOutputAndDisplayLink:frameUpdater]; [self addObservers:item]; @@ -277,7 +309,6 @@ static inline CGFloat radiansToDegrees(CGFloat radians) { - (void)sendInitialized { if (_eventSink && !_isInitialized && _isReady) { _isInitialized = true; - CGSize size = [self.player currentItem].presentationSize; // CGAffineTransform transform = [_player currentItem] asset].preferredTransform; // CGAffineTransform transform = [[[_player currentItem] asset] preferredTransform]; // NSArray* tracks = [[[_player currentItem] asset] tracksWithMediaType:AVMediaTypeVideo]; @@ -290,16 +321,17 @@ - (void)sendInitialized { NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); - CGAffineTransform t = _preferredTransform; - NSLog(@"atan2: %f", atan2(_preferredTransform.b, _preferredTransform.a)); - NSLog(@"Rotation degrees: %d \tFrom (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", - (int)rotationDegrees, t.a, t.b, t.c, t.d, t.tx, t.ty); + CGSize size = [self.player currentItem].presentationSize; + CGFloat width = size.width; + CGFloat height = size.height; + + NSLog(@"sendInitialized width, height: %f, %f", width, height); _eventSink(@{ @"event" : @"initialized", @"duration" : @([self duration]), - @"width" : @(size.width), - @"height" : @(size.height), + @"width" : @(width), + @"height" : @(height), @"rotationDegrees" : @(rotationDegrees), }); } @@ -354,7 +386,8 @@ - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { _eventSink = events; - [self sendInitialized]; + // TODO do we need this? + // [self sendInitialized]; return nil; } @@ -408,6 +441,21 @@ - (instancetype)initWithRegistrar:(NSObject*)registrar { return self; } +- (void)onPlayerSetup:(FLTVideoPlayer*)player + frameUpdater:(FLTFrameUpdater*)frameUpdater + result:(FlutterResult)result { + int64_t textureId = [_registry registerTexture:player]; + frameUpdater.textureId = textureId; + FlutterEventChannel* eventChannel = [FlutterEventChannel + eventChannelWithName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%lld", + textureId] + binaryMessenger:_messenger]; + [eventChannel setStreamHandler:player]; + player.eventChannel = eventChannel; + _players[@(textureId)] = player; + result(@{ @"textureId" : @(textureId) }); +} + - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@"init" isEqualToString:call.method]) { // Allow audio playback when the Ring/Silent switch is set to silent @@ -422,32 +470,39 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([@"create" isEqualToString:call.method]) { NSDictionary* argsMap = call.arguments; FLTFrameUpdater* frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; - NSString* dataSource = argsMap[@"asset"]; + NSString* assetArg = argsMap[@"asset"]; + NSString* uriArg = argsMap[@"uri"]; FLTVideoPlayer* player; - if (dataSource) { + if (assetArg) { NSString* assetPath; NSString* package = argsMap[@"package"]; if (![package isEqual:[NSNull null]]) { - assetPath = [_registrar lookupKeyForAsset:dataSource fromPackage:package]; + assetPath = [_registrar lookupKeyForAsset:assetArg fromPackage:package]; } else { - assetPath = [_registrar lookupKeyForAsset:dataSource]; + assetPath = [_registrar lookupKeyForAsset:assetArg]; } player = [[FLTVideoPlayer alloc] initWithAsset:assetPath frameUpdater:frameUpdater]; + [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; + } else if (uriArg) { + NSString* phAssetPrefix = @"phasset://"; + if ([uriArg hasPrefix:phAssetPrefix]) { + NSString* phAssetArg = [uriArg substringFromIndex:[phAssetPrefix length]]; + NSLog(@"Loading PHAsset localIdentifier: %@", phAssetArg); + [[FLTVideoPlayer alloc] + initWithPHAssetLocalIdentifier:phAssetArg + frameUpdater:frameUpdater + onPlayerSetup:^(FLTVideoPlayer* player) { + [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; + }]; + } else { + player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:uriArg] + frameUpdater:frameUpdater]; + [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; + } } else { - dataSource = argsMap[@"uri"]; - player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:dataSource] - frameUpdater:frameUpdater]; + result(FlutterMethodNotImplemented); } - int64_t textureId = [_registry registerTexture:player]; - frameUpdater.textureId = textureId; - FlutterEventChannel* eventChannel = [FlutterEventChannel - eventChannelWithName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%lld", - textureId] - binaryMessenger:_messenger]; - [eventChannel setStreamHandler:player]; - player.eventChannel = eventChannel; - _players[@(textureId)] = player; - result(@{ @"textureId" : @(textureId) }); + } else { NSDictionary* argsMap = call.arguments; int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue; From c55c5c28247ce250b826ef828b66ab8dba9e16cb Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Thu, 16 Aug 2018 12:44:03 -0500 Subject: [PATCH 11/32] Sometimes, the preferredTransformed returned by the videoTrack does not have the tx and ty values populated, which in turn renders black videos. We work around this by settings the tx and ty values ourselves --- .../ios/Classes/VideoPlayerPlugin.m | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 82b0099528aa..40dc785a208b 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -144,7 +144,7 @@ - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransfor width = videoTrack.naturalSize.height; height = videoTrack.naturalSize.width; } - NSLog(@"Using width, height: %f, %f", width, height); + NSLog(@"Using width, height: %f, %f, %d", width, height, rotationDegrees); videoComposition.renderSize = CGSizeMake(width, height); // TODO use videoTrack.nominalFrameRate ? @@ -172,6 +172,29 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda return [self initWithPlayerItem:item frameUpdater:frameUpdater]; } +- (CGAffineTransform)fixTransform:(AVAssetTrack*)videoTrack { + CGAffineTransform transform = videoTrack.preferredTransform; + // TODO: why do we need to do this? Why is the preferredTransform incorrect? + // At least 2 videos show a black screen otherwise when in portrait mode + // Setting tx to the height of the video, properly displays the video + // https://github.com/flutter/flutter/issues/17606#issuecomment-413473181 + if (transform.tx == 0 && transform.ty == 0) { + NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a))); + NSLog(@"TX and TY are 0. Rotation: %d. Natural width,height: %f, %f", rotationDegrees, + videoTrack.naturalSize.width, videoTrack.naturalSize.height); + if (rotationDegrees == 90) { + NSLog(@"Setting transform tx"); + transform.tx = videoTrack.naturalSize.height; + transform.ty = 0; + } else if (rotationDegrees == 270) { + NSLog(@"Setting transform ty"); + transform.tx = 0; + transform.ty = videoTrack.naturalSize.width; + } + } + return transform; +} + - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpdater*)frameUpdater { self = [super init]; NSAssert(self, @"super init cannot be nil"); @@ -190,21 +213,24 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd if (_disposed) return; if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == AVKeyValueStatusLoaded) { - CGSize size = item.presentationSize; + CGSize size = videoTrack.naturalSize; NSLog(@"preferredTransform width, height: %f, %f", size.width, size.height); // Rotate the video by using a videoComposition and the preferredTransform - _preferredTransform = videoTrack.preferredTransform; + _preferredTransform = [self fixTransform:videoTrack]; // Note: // https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition - // Aideo composition can only be used with file-based media and is not supported for use - // with media served using HTTP Live Streaming. - item.videoComposition = [self getVideoCompositionWithTransform:_preferredTransform - withAsset:asset - withVideoTrack:videoTrack]; + // Aideo composition can only be used with file-based media and is not supported for + // use with media served using HTTP Live Streaming. + AVMutableVideoComposition* videoComposition = + [self getVideoCompositionWithTransform:_preferredTransform + withAsset:asset + withVideoTrack:videoTrack]; + item.videoComposition = videoComposition; dispatch_async( dispatch_get_main_queue(), ^{ // Without this line, videos are not always rendered in app. TODO explain why + // Even with this line, videos are sometimes blank in app // [_player replaceCurrentItemWithPlayerItem:item]; }); } @@ -325,7 +351,11 @@ - (void)sendInitialized { CGFloat width = size.width; CGFloat height = size.height; - NSLog(@"sendInitialized width, height: %f, %f", width, height); + CGAffineTransform t = _preferredTransform; + NSLog(@"Affine2 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, + t.ty); + NSLog(@"sendInitialized width, height, rotationDegrees: %f, %f, %d", width, height, + rotationDegrees); _eventSink(@{ @"event" : @"initialized", From 0943513be5f98366aeaf238b944896ff992559d1 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Wed, 22 Aug 2018 11:18:42 -0500 Subject: [PATCH 12/32] - Removed support for phasset:// files. Support for phAssets will be added on a different branch (phAsset) --- .../ios/Classes/VideoPlayerPlugin.m | 40 ++----------------- packages/video_player/lib/video_player.dart | 5 +-- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 40dc785a208b..66b4348012bb 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -58,28 +58,6 @@ - (instancetype)initWithAsset:(NSString*)asset frameUpdater:(FLTFrameUpdater*)fr return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater]; } -// resultHandler:(void (^)(FLTVideoPlayer *playerItem, FLTFrameUpdater *frameUpdater))resultHandler - -- (void)initWithPHAssetLocalIdentifier:(NSString*)localIdentifier - frameUpdater:(FLTFrameUpdater*)frameUpdater - onPlayerSetup:(void (^)(FLTVideoPlayer* playerItem))onPlayerSetup { - PHFetchResult* phFetchResult = - [PHAsset fetchAssetsWithLocalIdentifiers:@[ localIdentifier ] options:nil]; - // TODO what to do if the asset cannot be loaded? Send an error to flutter? - PHAsset* phAsset = [phFetchResult firstObject]; - NSLog(@"PHFetchResult loaded: %@", phFetchResult); - NSLog(@"PHAsset loaded: %@", phAsset); - PHCachingImageManager* imageManager = [[PHCachingImageManager alloc] init]; - [imageManager requestPlayerItemForVideo:phAsset - options:nil - resultHandler:^(AVPlayerItem* _Nullable playerItem, - NSDictionary* _Nullable info) { - FLTVideoPlayer* player = - [self initWithPlayerItem:playerItem frameUpdater:frameUpdater]; - onPlayerSetup(player); - }]; -} - - (void)addObservers:(AVPlayerItem*)item { [item addObserver:self forKeyPath:@"loadedTimeRanges" @@ -514,21 +492,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { player = [[FLTVideoPlayer alloc] initWithAsset:assetPath frameUpdater:frameUpdater]; [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; } else if (uriArg) { - NSString* phAssetPrefix = @"phasset://"; - if ([uriArg hasPrefix:phAssetPrefix]) { - NSString* phAssetArg = [uriArg substringFromIndex:[phAssetPrefix length]]; - NSLog(@"Loading PHAsset localIdentifier: %@", phAssetArg); - [[FLTVideoPlayer alloc] - initWithPHAssetLocalIdentifier:phAssetArg - frameUpdater:frameUpdater - onPlayerSetup:^(FLTVideoPlayer* player) { - [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; - }]; - } else { - player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:uriArg] - frameUpdater:frameUpdater]; - [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; - } + player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:uriArg] + frameUpdater:frameUpdater]; + [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index 4806012e3967..1a2424fd2a62 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -191,9 +191,7 @@ class VideoPlayerController extends ValueNotifier { /// This will load the file from the file-URI given by: /// `'file://${file.path}'`. VideoPlayerController.file(File file) - : dataSource = file.path.startsWith('phasset://') - ? file.path - : 'file://${file.path}', + : dataSource = 'file://${file.path}', dataSourceType = DataSourceType.file, super(new VideoPlayerValue(duration: null)); @@ -214,7 +212,6 @@ class VideoPlayerController extends ValueNotifier { break; case DataSourceType.file: dataSourceDescription = {'uri': dataSource}; - // dataSourceDescription = {'phAsset': dataSource}; } final Map response = await _channel.invokeMethod( 'create', From 0490800803962adb3e0b967c8b67ad559b6d0cb2 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 11 Sep 2018 16:19:50 +0100 Subject: [PATCH 13/32] Remove bad merge line from CHANGELOG.md --- packages/video_player/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 8eca8a23ad82..e8d02545d322 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -17,7 +17,6 @@ * Eliminate race conditions around initialization: now initialization events are queued and guaranteed to be delivered to the Dart side. VideoPlayer widget is rebuilt upon completion of initialization. ->>>>>>> 8178bf113b461ab3b940a69c537c2db508dbf49b ## 0.6.4 From 9b21739b7e839decafb44afc6f13bbe666d67f4b Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 11 Sep 2018 16:24:36 +0100 Subject: [PATCH 14/32] Format files --- .../io/flutter/plugins/videoplayer/VideoPlayerPlugin.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 e9310daa46f8..874bb459acf2 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 @@ -229,10 +229,7 @@ private void sendInitialized() { } eventSink.success(event); } else { - Log.e( - TAG, - "failed sending sendInitialized(isInitialized: " - + isInitialized + ")"); + Log.e(TAG, "failed sending sendInitialized(isInitialized: " + isInitialized + ")"); } } From 10df65e75803e0f9852f138997405e02f8d91366 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 11 Sep 2018 16:38:53 +0100 Subject: [PATCH 15/32] Ensure width/height is properly set when videos are rotated --- .../plugins/videoplayer/VideoPlayerPlugin.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) 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 874bb459acf2..7a48f0d0b446 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 @@ -221,11 +221,20 @@ private void sendInitialized() { Map event = new HashMap<>(); event.put("event", "initialized"); event.put("duration", exoPlayer.getDuration()); + if (exoPlayer.getVideoFormat() != null) { Format videoFormat = exoPlayer.getVideoFormat(); - event.put("width", videoFormat.width); - event.put("height", videoFormat.height); - event.put("rotationDegrees", videoFormat.rotationDegrees); + int width = videoFormat.width; + int height = videoFormat.height; + int rotationDegrees = videoFormat.rotationDegrees; + // Switch the width/height if video was taken in portrait mode + if (rotationDegrees == 90 || rotationDegrees == 270) { + width = exoPlayer.getVideoFormat().height; + height = exoPlayer.getVideoFormat().width; + } + event.put("width", width); + event.put("height", height); + event.put("rotationDegrees", rotationDegrees); } eventSink.success(event); } else { From 57a65406dd007fd29528f8887b0ca37307605b7d Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Wed, 12 Sep 2018 18:25:09 +0100 Subject: [PATCH 16/32] Ensure that when the aspectRatio getter is called on a video controller instance, the dart code does not throw a null pointer exception when size is null --- packages/video_player/lib/video_player.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index adf794968917..f8bac0b9e04e 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -97,7 +97,7 @@ class VideoPlayerValue { bool get hasError => errorDescription != null; - double get aspectRatio => size.width / size.height; + double get aspectRatio => size != null ? size.width / size.height : 1.0; VideoPlayerValue copyWith({ Duration duration, From a57cf89c6a58b85de1793b35d2e94e4d97631978 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Wed, 12 Sep 2018 18:37:27 +0100 Subject: [PATCH 17/32] Remove unused variable --- packages/image_picker/example/lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/image_picker/example/lib/main.dart b/packages/image_picker/example/lib/main.dart index 56a66841499c..1250b205b493 100755 --- a/packages/image_picker/example/lib/main.dart +++ b/packages/image_picker/example/lib/main.dart @@ -226,7 +226,6 @@ class AspectRatioVideoState extends State { @override Widget build(BuildContext context) { if (initialized) { - final Size size = controller.value.size; return Center( child: AspectRatio( aspectRatio: controller.value?.aspectRatio, From 7796db7836ee1313abac94775d3a5fba8af01592 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sat, 22 Sep 2018 16:20:03 +0100 Subject: [PATCH 18/32] - Removed dead code and unused comments --- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 66b4348012bb..79be61831368 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -207,7 +207,8 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd item.videoComposition = videoComposition; dispatch_async( dispatch_get_main_queue(), ^{ - // Without this line, videos are not always rendered in app. TODO explain why + // TODO explain why this line was here in the first place + // The plugin seems to work OK without it // Even with this line, videos are sometimes blank in app // [_player replaceCurrentItemWithPlayerItem:item]; }); @@ -313,11 +314,6 @@ static inline CGFloat radiansToDegrees(CGFloat radians) { - (void)sendInitialized { if (_eventSink && !_isInitialized && _isReady) { _isInitialized = true; - // CGAffineTransform transform = [_player currentItem] asset].preferredTransform; - // CGAffineTransform transform = [[[_player currentItem] asset] preferredTransform]; - // NSArray* tracks = [[[_player currentItem] asset] tracksWithMediaType:AVMediaTypeVideo]; - // NSLog(@"Track lenght: %d", tracks.count); - // CGAffineTransform transform = [[tracks objectAtIndex:0] preferredTransform]; // atan2 returns values in the closed interval [-pi,pi]. See: // https://www.mathworks.com/help/matlab/ref/atan2.html#buct8h0-4 // https://developer.apple.com/documentation/coregraphics/cgaffinetransform @@ -525,9 +521,6 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([@"pause" isEqualToString:call.method]) { [player pause]; result(nil); - } else if ([@"sendInitialized" isEqualToString:call.method]) { - [player sendInitialized]; - result(nil); } else { result(FlutterMethodNotImplemented); } From 291ff1e940c19473403cbd6a28882c7b4fd89ca3 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sat, 22 Sep 2018 16:27:11 +0100 Subject: [PATCH 19/32] Fix style --- .../ios/Classes/VideoPlayerPlugin.h | 2 +- .../ios/Classes/VideoPlayerPlugin.m | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.h b/packages/video_player/ios/Classes/VideoPlayerPlugin.h index d5166b1782ad..18fdcca6d54e 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.h +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.h @@ -4,5 +4,5 @@ #import -@interface FLTVideoPlayerPlugin : NSObject +@interface FLTVideoPlayerPlugin : NSObject @end diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 79be61831368..4ca419959c95 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -27,7 +27,7 @@ - (void)onDisplayLink:(CADisplayLink*)link { } @end -@interface FLTVideoPlayer : NSObject +@interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayer* player; @property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; @property(readonly, nonatomic) CADisplayLink* displayLink; @@ -205,13 +205,12 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd withAsset:asset withVideoTrack:videoTrack]; item.videoComposition = videoComposition; - dispatch_async( - dispatch_get_main_queue(), ^{ - // TODO explain why this line was here in the first place - // The plugin seems to work OK without it - // Even with this line, videos are sometimes blank in app - // [_player replaceCurrentItemWithPlayerItem:item]; - }); + dispatch_async(dispatch_get_main_queue(), ^{ + // TODO explain why this line was here in the first place + // The plugin seems to work OK without it + // Even with this line, videos are sometimes blank in app + // [_player replaceCurrentItemWithPlayerItem:item]; + }); } }; [videoTrack loadValuesAsynchronouslyForKeys:@[ @"preferredTransform" ] @@ -457,7 +456,7 @@ - (void)onPlayerSetup:(FLTVideoPlayer*)player [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; _players[@(textureId)] = player; - result(@{ @"textureId" : @(textureId) }); + result(@{@"textureId" : @(textureId)}); } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { From 74b5d766aee969cbd0f42acecbd6e6e5295ac128 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Sat, 22 Sep 2018 16:30:52 +0100 Subject: [PATCH 20/32] Fix style --- packages/video_player/ios/Classes/VideoPlayerPlugin.h | 2 +- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.h b/packages/video_player/ios/Classes/VideoPlayerPlugin.h index 18fdcca6d54e..d5166b1782ad 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.h +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.h @@ -4,5 +4,5 @@ #import -@interface FLTVideoPlayerPlugin : NSObject +@interface FLTVideoPlayerPlugin : NSObject @end diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 4ca419959c95..18aa7fc0ad8f 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -27,7 +27,7 @@ - (void)onDisplayLink:(CADisplayLink*)link { } @end -@interface FLTVideoPlayer : NSObject +@interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayer* player; @property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; @property(readonly, nonatomic) CADisplayLink* displayLink; @@ -456,7 +456,7 @@ - (void)onPlayerSetup:(FLTVideoPlayer*)player [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; _players[@(textureId)] = player; - result(@{@"textureId" : @(textureId)}); + result(@{ @"textureId" : @(textureId) }); } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { From 42ad92ffeab0f78baae289757e0ea121491b0c60 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Thu, 1 Nov 2018 16:20:02 +0000 Subject: [PATCH 21/32] Add missing space to toString method --- packages/video_player/lib/video_player.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index df377a9d185c..f31dc10546c5 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -136,7 +136,7 @@ class VideoPlayerValue { 'isLooping: $isLooping, ' 'isBuffering: $isBuffering' 'volume: $volume, ' - 'errorDescription: $errorDescription,' + 'errorDescription: $errorDescription, ' 'rotationDegrees: $rotationDegrees)'; } } From 9d7f57cddc123604ebc54dae0c4ef2a5d905f277 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Fri, 16 Nov 2018 20:54:07 +0000 Subject: [PATCH 22/32] - Removed _isInitialized = true in swithc case AVPlayerItemStatusReadyToPlay, as otherwise the 'initialized' event might never be fired - Removed unnecessary Photos/Photos.h import - Removed unnecessary variable _isReady - Use TODO(name): for TODO comments - Better comments - Explain why sendInitialized() is needed in onListenWithArguments() - Nit: add back missing spaces --- .../ios/Classes/VideoPlayerPlugin.m | 46 ++++++++++--------- packages/video_player/lib/video_player.dart | 4 +- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 18aa7fc0ad8f..c6ee734c543c 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -4,7 +4,6 @@ #import "VideoPlayerPlugin.h" #import -#import int64_t FLTCMTimeToMillis(CMTime time) { return time.value * 1000 / time.timescale; } @@ -37,7 +36,6 @@ @interface FLTVideoPlayer : NSObject @property(nonatomic, readonly) bool disposed; @property(nonatomic, readonly) bool isPlaying; @property(nonatomic, readonly) bool isLooping; -@property(nonatomic, readonly) bool isReady; @property(nonatomic, readonly) bool isInitialized; - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpdater; - (void)play; @@ -125,8 +123,8 @@ - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransfor NSLog(@"Using width, height: %f, %f, %d", width, height, rotationDegrees); videoComposition.renderSize = CGSizeMake(width, height); - // TODO use videoTrack.nominalFrameRate ? - // Currently set at 30 FPS + // TODO(@recastrodiaz): should we use videoTrack.nominalFrameRate ? + // Currently set at a constant 30 FPS videoComposition.frameDuration = CMTimeMake(1, 30); return videoComposition; @@ -152,10 +150,10 @@ - (instancetype)initWithURL:(NSURL*)url frameUpdater:(FLTFrameUpdater*)frameUpda - (CGAffineTransform)fixTransform:(AVAssetTrack*)videoTrack { CGAffineTransform transform = videoTrack.preferredTransform; - // TODO: why do we need to do this? Why is the preferredTransform incorrect? - // At least 2 videos show a black screen otherwise when in portrait mode - // Setting tx to the height of the video, properly displays the video - // https://github.com/flutter/flutter/issues/17606#issuecomment-413473181 + // TODO(@recastrodiaz): why do we need to do this? Why is the preferredTransform incorrect? + // At least 2 user videos show a black screen when in portrait mode if we directly use the + // videoTrack.preferredTransform Setting tx to the height of the video instead of 0, properly + // displays the video https://github.com/flutter/flutter/issues/17606#issuecomment-413473181 if (transform.tx == 0 && transform.ty == 0) { NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a))); NSLog(@"TX and TY are 0. Rotation: %d. Natural width,height: %f, %f", rotationDegrees, @@ -177,7 +175,6 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd self = [super init]; NSAssert(self, @"super init cannot be nil"); _isInitialized = false; - _isReady = false; _isPlaying = false; _disposed = false; @@ -198,7 +195,7 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd _preferredTransform = [self fixTransform:videoTrack]; // Note: // https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition - // Aideo composition can only be used with file-based media and is not supported for + // Video composition can only be used with file-based media and is not supported for // use with media served using HTTP Live Streaming. AVMutableVideoComposition* videoComposition = [self getVideoCompositionWithTransform:_preferredTransform @@ -206,11 +203,13 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd withVideoTrack:videoTrack]; item.videoComposition = videoComposition; dispatch_async(dispatch_get_main_queue(), ^{ - // TODO explain why this line was here in the first place - // The plugin seems to work OK without it - // Even with this line, videos are sometimes blank in app - // [_player replaceCurrentItemWithPlayerItem:item]; - }); + // TODO(@recastrodiaz): explain why this line is required. + // The plugin seems to work OK without it. + // Even with this line, videos are sometimes not shown (black screen with sound). + // which is probably due to a race condition in the video player: + // https://github.com/flutter/flutter/issues/21483 + [_player replaceCurrentItemWithPlayerItem:item]; + }); } }; [videoTrack loadValuesAsynchronouslyForKeys:@[ @"preferredTransform" ] @@ -263,7 +262,6 @@ - (void)observeValueForKeyPath:(NSString*)path case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: - _isReady = true; [item addOutput:_videoOutput]; [self sendInitialized]; [self updatePlayingState]; @@ -288,7 +286,7 @@ - (void)observeValueForKeyPath:(NSString*)path } - (void)updatePlayingState { - if (!_isReady) { + if (!_isInitialized) { return; } if (_isPlaying) { @@ -311,7 +309,7 @@ static inline CGFloat radiansToDegrees(CGFloat radians) { }; - (void)sendInitialized { - if (_eventSink && !_isInitialized && _isReady) { + if (_eventSink && !_isInitialized) { _isInitialized = true; // atan2 returns values in the closed interval [-pi,pi]. See: // https://www.mathworks.com/help/matlab/ref/atan2.html#buct8h0-4 @@ -327,8 +325,8 @@ - (void)sendInitialized { CGAffineTransform t = _preferredTransform; NSLog(@"Affine2 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, t.ty); - NSLog(@"sendInitialized width, height, rotationDegrees: %f, %f, %d", width, height, - rotationDegrees); + NSLog(@"sendInitialized width, height, rotationDegrees: %f, %f, %ld", width, height, + (long)rotationDegrees); _eventSink(@{ @"event" : @"initialized", @@ -389,8 +387,12 @@ - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { _eventSink = events; - // TODO do we need this? - // [self sendInitialized]; + // TODO(@recastrodiaz): remove the line below when the race condition is resolved: + // https://github.com/flutter/flutter/issues/21483 + // This line ensures the 'initialized' event is sent when the event + // 'AVPlayerItemStatusReadyToPlay' fires before _eventSink is set (this function, + // onListenWithArguments, is called) + [self sendInitialized]; return nil; } diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index f31dc10546c5..a23b5af5cd30 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -10,8 +10,8 @@ import 'package:flutter/material.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 { From 4be9e38a4a2d702f7dc8c9fc53e81bedc1dfec0b Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Fri, 16 Nov 2018 21:20:32 +0000 Subject: [PATCH 23/32] - Revert back to original _isInitialized logic --- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index c6ee734c543c..4b5e898cab37 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -262,6 +262,7 @@ - (void)observeValueForKeyPath:(NSString*)path case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: + _isInitialized = true; [item addOutput:_videoOutput]; [self sendInitialized]; [self updatePlayingState]; @@ -309,8 +310,7 @@ static inline CGFloat radiansToDegrees(CGFloat radians) { }; - (void)sendInitialized { - if (_eventSink && !_isInitialized) { - _isInitialized = true; + if (_eventSink && _isInitialized) { // atan2 returns values in the closed interval [-pi,pi]. See: // https://www.mathworks.com/help/matlab/ref/atan2.html#buct8h0-4 // https://developer.apple.com/documentation/coregraphics/cgaffinetransform @@ -390,8 +390,8 @@ - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments // TODO(@recastrodiaz): remove the line below when the race condition is resolved: // https://github.com/flutter/flutter/issues/21483 // This line ensures the 'initialized' event is sent when the event - // 'AVPlayerItemStatusReadyToPlay' fires before _eventSink is set (this function, - // onListenWithArguments, is called) + // 'AVPlayerItemStatusReadyToPlay' fires before _eventSink is set (this function + // onListenWithArguments is called) [self sendInitialized]; return nil; } From 3cba0f6cf02d79a4fc67330691228c62d487b14e Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Mon, 19 Nov 2018 12:37:27 +0000 Subject: [PATCH 24/32] Remove dispatch_async as it is no longer needed. c.f. https://github.com/flutter/plugins/pull/690#pullrequestreview-175928937 --- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 4b5e898cab37..a5b5f99106b9 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -202,14 +202,6 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd withAsset:asset withVideoTrack:videoTrack]; item.videoComposition = videoComposition; - dispatch_async(dispatch_get_main_queue(), ^{ - // TODO(@recastrodiaz): explain why this line is required. - // The plugin seems to work OK without it. - // Even with this line, videos are sometimes not shown (black screen with sound). - // which is probably due to a race condition in the video player: - // https://github.com/flutter/flutter/issues/21483 - [_player replaceCurrentItemWithPlayerItem:item]; - }); } }; [videoTrack loadValuesAsynchronouslyForKeys:@[ @"preferredTransform" ] From 6e2d195f86c6e35a3b756a22f09dec66f7d06409 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 20 Nov 2018 12:49:13 +0000 Subject: [PATCH 25/32] - Remove catchError in README - Reduce the use of NSLog and Log.e/d - Use GLKMathRadiansToDegrees to transform radians into degrees --- packages/video_player/README.md | 3 +-- .../plugins/videoplayer/VideoPlayerPlugin.java | 6 ------ .../video_player/ios/Classes/VideoPlayerPlugin.m | 12 ++---------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/packages/video_player/README.md b/packages/video_player/README.md index 8bb4b4995dbb..edefa3e41989 100644 --- a/packages/video_player/README.md +++ b/packages/video_player/README.md @@ -68,8 +68,7 @@ class _VideoAppState extends State { // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); }) - ..initialize() - .catchError((dynamic error) => print('Unexpected error: $error')); + ..initialize(); } @override 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 2e8cbc54d1e3..5af8b4f8753d 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 @@ -11,7 +11,6 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Build; -import android.util.Log; import android.view.Surface; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -167,8 +166,6 @@ public void onPlayerStateChanged(final boolean playWhenReady, final int playback } else if (playbackState == Player.STATE_READY && !isInitialized) { isInitialized = true; sendInitialized(); - } else { - Log.d(TAG, "Received unhandled state: " + playbackState); } } @@ -223,7 +220,6 @@ long getPosition() { private void sendInitialized() { if (isInitialized) { - Log.d(TAG, "sending isInitialized"); Map event = new HashMap<>(); event.put("event", "initialized"); event.put("duration", exoPlayer.getDuration()); @@ -243,8 +239,6 @@ private void sendInitialized() { event.put("rotationDegrees", rotationDegrees); } eventSink.success(event); - } else { - Log.e(TAG, "failed sending sendInitialized(isInitialized: " + isInitialized + ")"); } } diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index a5b5f99106b9..342886675a4d 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -4,6 +4,7 @@ #import "VideoPlayerPlugin.h" #import +#import int64_t FLTCMTimeToMillis(CMTime time) { return time.value * 1000 / time.timescale; } @@ -96,8 +97,6 @@ - (void)addObservers:(AVPlayerItem*)item { - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransform)transform withAsset:(AVAsset*)asset withVideoTrack:(AVAssetTrack*)videoTrack { - CGAffineTransform t = _preferredTransform; - NSLog(@"Affine1 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, t.ty); AVMutableVideoCompositionInstruction* instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; @@ -120,7 +119,6 @@ - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransfor width = videoTrack.naturalSize.height; height = videoTrack.naturalSize.width; } - NSLog(@"Using width, height: %f, %f, %d", width, height, rotationDegrees); videoComposition.renderSize = CGSizeMake(width, height); // TODO(@recastrodiaz): should we use videoTrack.nominalFrameRate ? @@ -189,7 +187,6 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == AVKeyValueStatusLoaded) { CGSize size = videoTrack.naturalSize; - NSLog(@"preferredTransform width, height: %f, %f", size.width, size.height); // Rotate the video by using a videoComposition and the preferredTransform _preferredTransform = [self fixTransform:videoTrack]; @@ -214,7 +211,6 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; CGSize size = item.presentationSize; - NSLog(@"_player create width, height: %f, %f", size.width, size.height); [self createVideoOutputAndDisplayLink:frameUpdater]; @@ -292,7 +288,7 @@ - (void)updatePlayingState { static inline CGFloat radiansToDegrees(CGFloat radians) { // Input range [-pi, pi] or [-180, 180] - CGFloat degrees = radians * 180 / M_PI; + CGFloat degrees = GLKMathRadiansToDegrees(radians); if (degrees < 0) { // Convert -90 to 270 and -180 to 180 return degrees + 360; @@ -315,10 +311,6 @@ - (void)sendInitialized { CGFloat height = size.height; CGAffineTransform t = _preferredTransform; - NSLog(@"Affine2 (a, b, c, d, tx, ty): (%f, %f, %f, %f, %f, %f)", t.a, t.b, t.c, t.d, t.tx, - t.ty); - NSLog(@"sendInitialized width, height, rotationDegrees: %f, %f, %ld", width, height, - (long)rotationDegrees); _eventSink(@{ @"event" : @"initialized", From 9b622edbf5ab190856e9a87d9abfcd5f2b428923 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 20 Nov 2018 13:12:31 +0000 Subject: [PATCH 26/32] - Use clang-format 8.0 --- .../video_player/ios/Classes/VideoPlayerPlugin.h | 2 +- .../video_player/ios/Classes/VideoPlayerPlugin.m | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.h b/packages/video_player/ios/Classes/VideoPlayerPlugin.h index d5166b1782ad..18fdcca6d54e 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.h +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.h @@ -4,5 +4,5 @@ #import -@interface FLTVideoPlayerPlugin : NSObject +@interface FLTVideoPlayerPlugin : NSObject @end diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 342886675a4d..f77b36bf2596 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -27,7 +27,7 @@ - (void)onDisplayLink:(CADisplayLink*)link { } @end -@interface FLTVideoPlayer : NSObject +@interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayer* player; @property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; @property(readonly, nonatomic) CADisplayLink* displayLink; @@ -97,7 +97,6 @@ - (void)addObservers:(AVPlayerItem*)item { - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransform)transform withAsset:(AVAsset*)asset withVideoTrack:(AVAssetTrack*)videoTrack { - AVMutableVideoCompositionInstruction* instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [asset duration]); @@ -135,8 +134,8 @@ - (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater*)frameUpdater { }; _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; - _displayLink = - [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; + _displayLink = [CADisplayLink displayLinkWithTarget:frameUpdater + selector:@selector(onDisplayLink:)]; [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; _displayLink.paused = YES; } @@ -184,8 +183,8 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem*)item frameUpdater:(FLTFrameUpd AVAssetTrack* videoTrack = [tracks objectAtIndex:0]; void (^trackCompletionHandler)(void) = ^{ if (_disposed) return; - if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == - AVKeyValueStatusLoaded) { + if ([videoTrack statusOfValueForKey:@"preferredTransform" + error:nil] == AVKeyValueStatusLoaded) { CGSize size = videoTrack.naturalSize; // Rotate the video by using a videoComposition and the preferredTransform @@ -288,7 +287,7 @@ - (void)updatePlayingState { static inline CGFloat radiansToDegrees(CGFloat radians) { // Input range [-pi, pi] or [-180, 180] - CGFloat degrees = GLKMathRadiansToDegrees(radians); + CGFloat degrees = GLKMathRadiansToDegrees(radians); if (degrees < 0) { // Convert -90 to 270 and -180 to 180 return degrees + 360; @@ -442,7 +441,7 @@ - (void)onPlayerSetup:(FLTVideoPlayer*)player [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; _players[@(textureId)] = player; - result(@{ @"textureId" : @(textureId) }); + result(@{@"textureId" : @(textureId)}); } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { From a0d2b253b423d6924c5f0ec9339f45a3c96d372b Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 20 Nov 2018 13:42:42 +0000 Subject: [PATCH 27/32] Remove unused variable --- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index f77b36bf2596..12d603f026af 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -309,8 +309,6 @@ - (void)sendInitialized { CGFloat width = size.width; CGFloat height = size.height; - CGAffineTransform t = _preferredTransform; - _eventSink(@{ @"event" : @"initialized", @"duration" : @([self duration]), From 664aab78d229282dea8fd9b8bca3c67d52e482b6 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 20 Nov 2018 15:36:54 +0000 Subject: [PATCH 28/32] Remove variable rotationDegrees --- packages/video_player/CHANGELOG.md | 3 +-- .../plugins/videoplayer/VideoPlayerPlugin.java | 1 - .../video_player/ios/Classes/VideoPlayerPlugin.m | 10 +--------- packages/video_player/lib/video_player.dart | 15 +-------------- 4 files changed, 3 insertions(+), 26 deletions(-) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 8e808b012bc7..9e52fd8ec8a5 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,7 +1,6 @@ ## 0.7.3 -* Added access to the video rotation in degrees (rotationDegrees). Possible values are: 0, 90, 180 and 270. -* Updated the aspectRatio getter to use the video rotation. +* Take into account the video rotation when computing the aspect ratio. * Fixed the aspect ratio of videos in the video_player plugin example. It now properly displays videos taken in portrait mode. ## 0.7.2 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 5af8b4f8753d..de4f44cdb567 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 @@ -236,7 +236,6 @@ private void sendInitialized() { } event.put("width", width); event.put("height", height); - event.put("rotationDegrees", rotationDegrees); } eventSink.success(event); } diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 12d603f026af..16110a5e4e47 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -298,13 +298,6 @@ static inline CGFloat radiansToDegrees(CGFloat radians) { - (void)sendInitialized { if (_eventSink && _isInitialized) { - // atan2 returns values in the closed interval [-pi,pi]. See: - // https://www.mathworks.com/help/matlab/ref/atan2.html#buct8h0-4 - // https://developer.apple.com/documentation/coregraphics/cgaffinetransform - // https://en.wikipedia.org/wiki/File:2D_affine_transformation_matrix.svg - NSInteger rotationDegrees = - (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); - CGSize size = [self.player currentItem].presentationSize; CGFloat width = size.width; CGFloat height = size.height; @@ -313,8 +306,7 @@ - (void)sendInitialized { @"event" : @"initialized", @"duration" : @([self duration]), @"width" : @(width), - @"height" : @(height), - @"rotationDegrees" : @(rotationDegrees), + @"height" : @(height) }); } } diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index a23b5af5cd30..8920fda4485f 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -45,7 +45,6 @@ class VideoPlayerValue { this.isBuffering = false, this.volume = 1.0, this.errorDescription, - this.rotationDegrees, }); VideoPlayerValue.uninitialized() : this(duration: null); @@ -86,17 +85,9 @@ class VideoPlayerValue { /// Is null when [initialized] is false. final Size size; - /// The [rotationDegrees] of the currently loaded video. - /// Possible values are: 0 or 180 for videos recorded in landscape format, - /// 90 and 270 for videos taken in portrait mode. - /// - /// Is null when [initialized] is false. - final int rotationDegrees; bool get initialized => duration != null; - bool get hasError => errorDescription != null; - double get aspectRatio => size != null ? size.width / size.height : 1.0; VideoPlayerValue copyWith({ @@ -109,7 +100,6 @@ class VideoPlayerValue { bool isBuffering, double volume, String errorDescription, - int rotationDegrees, }) { return VideoPlayerValue( duration: duration ?? this.duration, @@ -121,7 +111,6 @@ class VideoPlayerValue { isBuffering: isBuffering ?? this.isBuffering, volume: volume ?? this.volume, errorDescription: errorDescription ?? this.errorDescription, - rotationDegrees: rotationDegrees ?? this.rotationDegrees, ); } @@ -136,8 +125,7 @@ class VideoPlayerValue { 'isLooping: $isLooping, ' 'isBuffering: $isBuffering' 'volume: $volume, ' - 'errorDescription: $errorDescription, ' - 'rotationDegrees: $rotationDegrees)'; + 'errorDescription: $errorDescription)'; } } @@ -242,7 +230,6 @@ class VideoPlayerController extends ValueNotifier { duration: Duration(milliseconds: map['duration']), size: Size(map['width']?.toDouble() ?? 0.0, map['height']?.toDouble() ?? 0.0), - rotationDegrees: map['rotationDegrees'].toInt() ?? 0.0, ); initializingCompleter.complete(null); _applyLooping(); From b5b87e8a145b0a9e557269e9ad720f8f69191a67 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Tue, 20 Nov 2018 15:43:09 +0000 Subject: [PATCH 29/32] -Remove extra empty new lines --- packages/video_player/lib/video_player.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index 8920fda4485f..926553eb0be4 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -85,7 +85,6 @@ class VideoPlayerValue { /// Is null when [initialized] is false. final Size size; - bool get initialized => duration != null; bool get hasError => errorDescription != null; double get aspectRatio => size != null ? size.width / size.height : 1.0; @@ -264,7 +263,6 @@ class VideoPlayerController extends ValueNotifier { _eventSubscription = _eventChannelFor(_textureId) .receiveBroadcastStream() .listen(eventListener, onError: errorListener); - return initializingCompleter.future; } From 8d571fbdddaaee603c3a010a1f21f4c1211971a8 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Wed, 23 Jan 2019 17:52:49 -0600 Subject: [PATCH 30/32] video_player examples: - addListener is not required to render the first video frame - call setState() on play/pause to update the UI - dispose() the controller when the widget is disposed - fix video urls --- packages/video_player/README.md | 25 ++++++++++++--------- packages/video_player/example/lib/main.dart | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/video_player/README.md b/packages/video_player/README.md index edefa3e41989..63e99a06e185 100644 --- a/packages/video_player/README.md +++ b/packages/video_player/README.md @@ -55,20 +55,16 @@ class VideoApp extends StatefulWidget { class _VideoAppState extends State { VideoPlayerController _controller; - bool _isPlaying = false; @override void initState() { super.initState(); _controller = VideoPlayerController.network( - 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4', - ) - ..addListener(() { - print('completed listener: $_controller'); + 'http://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_20mb.mp4') + ..initialize().then((_) { // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); - }) - ..initialize(); + }); } @override @@ -85,9 +81,12 @@ class _VideoAppState extends State { : Container(), ), floatingActionButton: FloatingActionButton( - onPressed: _controller.value.isPlaying - ? _controller.pause - : _controller.play, + onPressed: () { + _controller.value.isPlaying + ? _controller.pause() + : _controller.play(); + setState(() {}); + }, child: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, ), @@ -95,5 +94,11 @@ class _VideoAppState extends State { ), ); } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } } ``` diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart index d1cec7fcc68a..696373b0a5b3 100644 --- a/packages/video_player/example/lib/main.dart +++ b/packages/video_player/example/lib/main.dart @@ -380,7 +380,7 @@ void main() { Column( children: [ NetworkPlayerLifeCycle( - 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4', + 'http://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_20mb.mp4', (BuildContext context, VideoPlayerController controller) => AspectRatioVideo(controller), ), @@ -395,7 +395,7 @@ void main() { ], ), NetworkPlayerLifeCycle( - 'http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4', + 'http://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_20mb.mp4', (BuildContext context, VideoPlayerController controller) => AspectRatioVideo(controller), ), From 40f756cfd68d1aaa99eb3c3ba03653cec9c6329f Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Wed, 23 Jan 2019 21:46:59 -0600 Subject: [PATCH 31/32] - Bump video_player version to 0.9.0 - Move play/pause logic within setState closure in video_player example --- packages/video_player/CHANGELOG.md | 2 +- packages/video_player/README.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 3fdc2715aeaa..338fb22395ef 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.8.1 +## 0.9.0 * Fixed the aspect ratio and orientation of videos. Videos are now properly displayed when recorded in portrait mode both in iOS and Android. diff --git a/packages/video_player/README.md b/packages/video_player/README.md index 63e99a06e185..fe9a8accff65 100644 --- a/packages/video_player/README.md +++ b/packages/video_player/README.md @@ -82,10 +82,11 @@ class _VideoAppState extends State { ), floatingActionButton: FloatingActionButton( onPressed: () { - _controller.value.isPlaying - ? _controller.pause() - : _controller.play(); - setState(() {}); + setState(() { + _controller.value.isPlaying + ? _controller.pause() + : _controller.play(); + }); }, child: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, From 055cb53ab2ca1d2dad7a66de3a194f42d4024ce3 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Thu, 24 Jan 2019 08:31:21 -0600 Subject: [PATCH 32/32] - Bump plugin to 0.9.0 (not just the changelog) --- packages/video_player/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index bbb0452ee071..bdc0da7c0033 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.8.1 +version: 0.9.0 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: