Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 06257dd

Browse files
authored
[video_player_avfoundation] Applies the standardized transform for videos with different orientations (#5069)
1 parent 4a59768 commit 06257dd

File tree

6 files changed

+162
-25
lines changed

6 files changed

+162
-25
lines changed

packages/video_player/video_player_avfoundation/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.3.2
2+
3+
* Applies the standardized transform for videos with different orientations.
4+
15
## 2.3.1
26

37
* Renames internal method channels to avoid potential confusion with the

packages/video_player/video_player_avfoundation/example/ios/RunnerTests/VideoPlayerTests.m

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@import XCTest;
88

99
#import <OCMock/OCMock.h>
10+
#import <video_player_avfoundation/AVAssetTrackUtils.h>
1011

1112
@interface FLTVideoPlayer : NSObject <FlutterStreamHandler>
1213
@property(readonly, nonatomic) AVPlayer *player;
@@ -17,6 +18,44 @@ @interface FLTVideoPlayerPlugin (Test) <FLTAVFoundationVideoPlayerApi>
1718
NSMutableDictionary<NSNumber *, FLTVideoPlayer *> *playersByTextureId;
1819
@end
1920

21+
@interface FakeAVAssetTrack : AVAssetTrack
22+
@property(readonly, nonatomic) CGAffineTransform preferredTransform;
23+
@property(readonly, nonatomic) CGSize naturalSize;
24+
@property(readonly, nonatomic) UIImageOrientation orientation;
25+
- (instancetype)initWithOrientation:(UIImageOrientation)orientation;
26+
@end
27+
28+
@implementation FakeAVAssetTrack
29+
30+
- (instancetype)initWithOrientation:(UIImageOrientation)orientation {
31+
_orientation = orientation;
32+
_naturalSize = CGSizeMake(800, 600);
33+
return self;
34+
}
35+
36+
- (CGAffineTransform)preferredTransform {
37+
switch (_orientation) {
38+
case UIImageOrientationUp:
39+
return CGAffineTransformMake(1, 0, 0, 1, 0, 0);
40+
case UIImageOrientationDown:
41+
return CGAffineTransformMake(-1, 0, 0, -1, 0, 0);
42+
case UIImageOrientationLeft:
43+
return CGAffineTransformMake(0, -1, 1, 0, 0, 0);
44+
case UIImageOrientationRight:
45+
return CGAffineTransformMake(0, 1, -1, 0, 0, 0);
46+
case UIImageOrientationUpMirrored:
47+
return CGAffineTransformMake(-1, 0, 0, 1, 0, 0);
48+
case UIImageOrientationDownMirrored:
49+
return CGAffineTransformMake(1, 0, 0, -1, 0, 0);
50+
case UIImageOrientationLeftMirrored:
51+
return CGAffineTransformMake(0, -1, -1, 0, 0, 0);
52+
case UIImageOrientationRightMirrored:
53+
return CGAffineTransformMake(0, 1, 1, 0, 0, 0);
54+
}
55+
}
56+
57+
@end
58+
2059
@interface VideoPlayerTests : XCTestCase
2160
@end
2261

@@ -121,6 +160,17 @@ - (void)testHLSControls {
121160
XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200);
122161
}
123162

163+
- (void)testTransformFix {
164+
[self validateTransformFixForOrientation:UIImageOrientationUp];
165+
[self validateTransformFixForOrientation:UIImageOrientationDown];
166+
[self validateTransformFixForOrientation:UIImageOrientationLeft];
167+
[self validateTransformFixForOrientation:UIImageOrientationRight];
168+
[self validateTransformFixForOrientation:UIImageOrientationUpMirrored];
169+
[self validateTransformFixForOrientation:UIImageOrientationDownMirrored];
170+
[self validateTransformFixForOrientation:UIImageOrientationLeftMirrored];
171+
[self validateTransformFixForOrientation:UIImageOrientationRightMirrored];
172+
}
173+
124174
- (NSDictionary<NSString *, id> *)testPlugin:(FLTVideoPlayerPlugin *)videoPlayerPlugin
125175
uri:(NSString *)uri {
126176
FlutterError *error;
@@ -175,4 +225,47 @@ - (void)testHLSControls {
175225
return initializationEvent;
176226
}
177227

228+
- (void)validateTransformFixForOrientation:(UIImageOrientation)orientation {
229+
AVAssetTrack *track = [[FakeAVAssetTrack alloc] initWithOrientation:orientation];
230+
CGAffineTransform t = FLTGetStandardizedTransformForTrack(track);
231+
CGSize size = track.naturalSize;
232+
CGFloat expectX, expectY;
233+
switch (orientation) {
234+
case UIImageOrientationUp:
235+
expectX = 0;
236+
expectY = 0;
237+
break;
238+
case UIImageOrientationDown:
239+
expectX = size.width;
240+
expectY = size.height;
241+
break;
242+
case UIImageOrientationLeft:
243+
expectX = 0;
244+
expectY = size.width;
245+
break;
246+
case UIImageOrientationRight:
247+
expectX = size.height;
248+
expectY = 0;
249+
break;
250+
case UIImageOrientationUpMirrored:
251+
expectX = size.width;
252+
expectY = 0;
253+
break;
254+
case UIImageOrientationDownMirrored:
255+
expectX = 0;
256+
expectY = size.height;
257+
break;
258+
case UIImageOrientationLeftMirrored:
259+
expectX = size.height;
260+
expectY = size.width;
261+
break;
262+
case UIImageOrientationRightMirrored:
263+
expectX = 0;
264+
expectY = 0;
265+
break;
266+
}
267+
XCTAssertEqual(t.tx, expectX);
268+
XCTAssertEqual(t.ty, expectY);
269+
}
270+
178271
@end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <AVFoundation/AVFoundation.h>
6+
7+
/**
8+
* Returns a standardized transform
9+
* according to the orientation of the track.
10+
*
11+
* Note: https://stackoverflow.com/questions/64161544
12+
* `AVAssetTrack.preferredTransform` can have wrong `tx` and `ty`.
13+
*/
14+
CGAffineTransform FLTGetStandardizedTransformForTrack(AVAssetTrack* track);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <AVFoundation/AVFoundation.h>
6+
7+
CGAffineTransform FLTGetStandardizedTransformForTrack(AVAssetTrack *track) {
8+
CGAffineTransform t = track.preferredTransform;
9+
CGSize size = track.naturalSize;
10+
// Each case of control flows corresponds to a specific
11+
// `UIImageOrientation`, with 8 cases in total.
12+
if (t.a == 1 && t.b == 0 && t.c == 0 && t.d == 1) {
13+
// UIImageOrientationUp
14+
t.tx = 0;
15+
t.ty = 0;
16+
} else if (t.a == -1 && t.b == 0 && t.c == 0 && t.d == -1) {
17+
// UIImageOrientationDown
18+
t.tx = size.width;
19+
t.ty = size.height;
20+
} else if (t.a == 0 && t.b == -1 && t.c == 1 && t.d == 0) {
21+
// UIImageOrientationLeft
22+
t.tx = 0;
23+
t.ty = size.width;
24+
} else if (t.a == 0 && t.b == 1 && t.c == -1 && t.d == 0) {
25+
// UIImageOrientationRight
26+
t.tx = size.height;
27+
t.ty = 0;
28+
} else if (t.a == -1 && t.b == 0 && t.c == 0 && t.d == 1) {
29+
// UIImageOrientationUpMirrored
30+
t.tx = size.width;
31+
t.ty = 0;
32+
} else if (t.a == 1 && t.b == 0 && t.c == 0 && t.d == -1) {
33+
// UIImageOrientationDownMirrored
34+
t.tx = 0;
35+
t.ty = size.height;
36+
} else if (t.a == 0 && t.b == -1 && t.c == -1 && t.d == 0) {
37+
// UIImageOrientationLeftMirrored
38+
t.tx = size.height;
39+
t.ty = size.width;
40+
} else if (t.a == 0 && t.b == 1 && t.c == 1 && t.d == 0) {
41+
// UIImageOrientationRightMirrored
42+
t.tx = 0;
43+
t.ty = 0;
44+
}
45+
return t;
46+
}

packages/video_player/video_player_avfoundation/ios/Classes/FLTVideoPlayerPlugin.m

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// found in the LICENSE file.
44

55
#import "FLTVideoPlayerPlugin.h"
6+
67
#import <AVFoundation/AVFoundation.h>
78
#import <GLKit/GLKit.h>
9+
10+
#import "AVAssetTrackUtils.h"
811
#import "messages.g.h"
912

1013
#if !__has_feature(objc_arc)
@@ -187,29 +190,6 @@ - (instancetype)initWithURL:(NSURL *)url
187190
return [self initWithPlayerItem:item frameUpdater:frameUpdater];
188191
}
189192

190-
- (CGAffineTransform)fixTransform:(AVAssetTrack *)videoTrack {
191-
CGAffineTransform transform = videoTrack.preferredTransform;
192-
// TODO(@recastrodiaz): why do we need to do this? Why is the preferredTransform incorrect?
193-
// At least 2 user videos show a black screen when in portrait mode if we directly use the
194-
// videoTrack.preferredTransform Setting tx to the height of the video instead of 0, properly
195-
// displays the video https://github.com/flutter/flutter/issues/17606#issuecomment-413473181
196-
if (transform.tx == 0 && transform.ty == 0) {
197-
NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(transform.b, transform.a)));
198-
NSLog(@"TX and TY are 0. Rotation: %ld. Natural width,height: %f, %f", (long)rotationDegrees,
199-
videoTrack.naturalSize.width, videoTrack.naturalSize.height);
200-
if (rotationDegrees == 90) {
201-
NSLog(@"Setting transform tx");
202-
transform.tx = videoTrack.naturalSize.height;
203-
transform.ty = 0;
204-
} else if (rotationDegrees == 270) {
205-
NSLog(@"Setting transform ty");
206-
transform.tx = 0;
207-
transform.ty = videoTrack.naturalSize.width;
208-
}
209-
}
210-
return transform;
211-
}
212-
213193
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
214194
frameUpdater:(FLTFrameUpdater *)frameUpdater {
215195
self = [super init];
@@ -226,7 +206,7 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem *)item
226206
if ([videoTrack statusOfValueForKey:@"preferredTransform"
227207
error:nil] == AVKeyValueStatusLoaded) {
228208
// Rotate the video by using a videoComposition and the preferredTransform
229-
self->_preferredTransform = [self fixTransform:videoTrack];
209+
self->_preferredTransform = FLTGetStandardizedTransformForTrack(videoTrack);
230210
// Note:
231211
// https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition
232212
// Video composition can only be used with file-based media and is not supported for

packages/video_player/video_player_avfoundation/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: video_player_avfoundation
22
description: iOS implementation of the video_player plugin.
33
repository: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_avfoundation
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
5-
version: 2.3.1
5+
version: 2.3.2
66

77
environment:
88
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)