-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[video_player] foundation - reduce seek accuracy to fix seek to end bug #3784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
45fb3a5
426c067
7d167e2
b78ae9b
cc5b173
aa9fbbd
041dcb3
c6861f4
747a421
2e12e1a
d130337
7e8fada
b61eace
6a586d1
c9bd628
ce41a1f
3ef8801
46f3c7d
fb9b9b8
7aeb3db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
// found in the LICENSE file. | ||
|
||
#import "FLTVideoPlayerPlugin.h" | ||
#import "FLTVideoPlayerPlugin_Test.h" | ||
|
||
#import <AVFoundation/AVFoundation.h> | ||
#import <GLKit/GLKit.h> | ||
|
@@ -33,6 +34,16 @@ - (void)onDisplayLink:(CADisplayLink *)link { | |
} | ||
@end | ||
|
||
@interface FVPDefaultPlayerFactory : NSObject <FVPPlayerFactory> | ||
@end | ||
|
||
@implementation FVPDefaultPlayerFactory | ||
- (AVPlayer *)playerWithPlayerItem:(AVPlayerItem *)playerItem { | ||
return [AVPlayer playerWithPlayerItem:playerItem]; | ||
} | ||
|
||
@end | ||
|
||
@interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler> | ||
@property(readonly, nonatomic) AVPlayer *player; | ||
@property(readonly, nonatomic) AVPlayerItemVideoOutput *videoOutput; | ||
|
@@ -52,7 +63,8 @@ @interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler> | |
@property(nonatomic, readonly) BOOL isInitialized; | ||
- (instancetype)initWithURL:(NSURL *)url | ||
frameUpdater:(FLTFrameUpdater *)frameUpdater | ||
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers; | ||
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers | ||
playerFactory:(id<FVPPlayerFactory>)playerFactory; | ||
@end | ||
|
||
static void *timeRangeContext = &timeRangeContext; | ||
|
@@ -65,9 +77,14 @@ - (instancetype)initWithURL:(NSURL *)url | |
static void *rateContext = &rateContext; | ||
|
||
@implementation FLTVideoPlayer | ||
- (instancetype)initWithAsset:(NSString *)asset frameUpdater:(FLTFrameUpdater *)frameUpdater { | ||
- (instancetype)initWithAsset:(NSString *)asset | ||
frameUpdater:(FLTFrameUpdater *)frameUpdater | ||
playerFactory:(id<FVPPlayerFactory>)playerFactory { | ||
NSString *path = [[NSBundle mainBundle] pathForResource:asset ofType:nil]; | ||
return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater httpHeaders:@{}]; | ||
return [self initWithURL:[NSURL fileURLWithPath:path] | ||
frameUpdater:frameUpdater | ||
httpHeaders:@{} | ||
playerFactory:playerFactory]; | ||
} | ||
|
||
- (void)addObserversForItem:(AVPlayerItem *)item player:(AVPlayer *)player { | ||
|
@@ -203,18 +220,20 @@ - (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater *)frameUpdater { | |
|
||
- (instancetype)initWithURL:(NSURL *)url | ||
frameUpdater:(FLTFrameUpdater *)frameUpdater | ||
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers { | ||
httpHeaders:(nonnull NSDictionary<NSString *, NSString *> *)headers | ||
playerFactory:(id<FVPPlayerFactory>)playerFactory { | ||
NSDictionary<NSString *, id> *options = nil; | ||
if ([headers count] != 0) { | ||
options = @{@"AVURLAssetHTTPHeaderFieldsKey" : headers}; | ||
} | ||
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:options]; | ||
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:urlAsset]; | ||
return [self initWithPlayerItem:item frameUpdater:frameUpdater]; | ||
return [self initWithPlayerItem:item frameUpdater:frameUpdater playerFactory:playerFactory]; | ||
} | ||
|
||
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item | ||
frameUpdater:(FLTFrameUpdater *)frameUpdater { | ||
frameUpdater:(FLTFrameUpdater *)frameUpdater | ||
playerFactory:(id<FVPPlayerFactory>)playerFactory { | ||
self = [super init]; | ||
NSAssert(self, @"super init cannot be nil"); | ||
|
||
|
@@ -247,7 +266,7 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem *)item | |
} | ||
}; | ||
|
||
_player = [AVPlayer playerWithPlayerItem:item]; | ||
_player = [playerFactory playerWithPlayerItem:item]; | ||
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone; | ||
|
||
// This is to fix 2 bugs: 1. blank video for encrypted video streams on iOS 16 | ||
|
@@ -420,9 +439,15 @@ - (int64_t)duration { | |
} | ||
|
||
- (void)seekTo:(int)location completionHandler:(void (^)(BOOL))completionHandler { | ||
[_player seekToTime:CMTimeMake(location, 1000) | ||
toleranceBefore:kCMTimeZero | ||
toleranceAfter:kCMTimeZero | ||
CMTime locationCMT = CMTimeMake(location, 1000); | ||
CMTimeValue duration = _player.currentItem.asset.duration.value; | ||
// Without adding tolerance when seeking to duration, | ||
// seekToTime will never complete, and this call will hang. | ||
// see issue https://github.com/flutter/flutter/issues/124475. | ||
CMTime tolerance = location == duration ? CMTimeMake(1, 1000) : kCMTimeZero; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you write a unit test (XCTest) for this behavior? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have no idea what I didn't think of that when @tarrinneal and I were talking about testing for this 🤦🏻 It looks like we don't currently have a way to mock out the underlying
The, similar to the other DI you did recently, you'd have a private implementation of that protocol that's the default version, just wrapping There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can also try stubbing the constructor directly if it's easier:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't. OCMock class mocking is giant foot-gun; it's incredibly easy to accidentally leak mocking across tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha. i did that a few times for camera plugin iirc. i should clean that up sometime.
Comment on lines
+442
to
+447
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Video https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4 from tests gives 2422 and 600 for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @misos1 This is a PR that landed a year and a half ago; comments here aren't actionable. If there's a bug, please file an issue with details. |
||
[_player seekToTime:locationCMT | ||
toleranceBefore:tolerance | ||
toleranceAfter:tolerance | ||
completionHandler:completionHandler]; | ||
} | ||
|
||
|
@@ -523,6 +548,7 @@ @interface FLTVideoPlayerPlugin () <FLTAVFoundationVideoPlayerApi> | |
@property(readonly, strong, nonatomic) | ||
NSMutableDictionary<NSNumber *, FLTVideoPlayer *> *playersByTextureId; | ||
@property(readonly, strong, nonatomic) NSObject<FlutterPluginRegistrar> *registrar; | ||
@property(nonatomic, strong) id<FVPPlayerFactory> playerFactory; | ||
@end | ||
|
||
@implementation FLTVideoPlayerPlugin | ||
|
@@ -533,11 +559,17 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { | |
} | ||
|
||
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { | ||
return [self initWithPlayerFactory:[[FVPDefaultPlayerFactory alloc] init] registrar:registrar]; | ||
} | ||
|
||
- (instancetype)initWithPlayerFactory:(id<FVPPlayerFactory>)playerFactory | ||
registrar:(NSObject<FlutterPluginRegistrar> *)registrar { | ||
self = [super init]; | ||
NSAssert(self, @"super init cannot be nil"); | ||
_registry = [registrar textures]; | ||
_messenger = [registrar messenger]; | ||
_registrar = registrar; | ||
_playerFactory = playerFactory; | ||
_playersByTextureId = [NSMutableDictionary dictionaryWithCapacity:1]; | ||
return self; | ||
} | ||
|
@@ -588,12 +620,15 @@ - (FLTTextureMessage *)create:(FLTCreateMessage *)input error:(FlutterError **)e | |
} else { | ||
assetPath = [_registrar lookupKeyForAsset:input.asset]; | ||
} | ||
player = [[FLTVideoPlayer alloc] initWithAsset:assetPath frameUpdater:frameUpdater]; | ||
player = [[FLTVideoPlayer alloc] initWithAsset:assetPath | ||
frameUpdater:frameUpdater | ||
playerFactory:_playerFactory]; | ||
return [self onPlayerSetup:player frameUpdater:frameUpdater]; | ||
} else if (input.uri) { | ||
player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:input.uri] | ||
frameUpdater:frameUpdater | ||
httpHeaders:input.httpHeaders]; | ||
httpHeaders:input.httpHeaders | ||
playerFactory:_playerFactory]; | ||
return [self onPlayerSetup:player frameUpdater:frameUpdater]; | ||
} else { | ||
*error = [FlutterError errorWithCode:@"video_player" message:@"not implemented" details:nil]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#import "FLTVideoPlayerPlugin.h" | ||
|
||
#import <AVFoundation/AVFoundation.h> | ||
|
||
// Protocol for an AVPlayer instance factory. Used for injecting players in tests. | ||
@protocol FVPPlayerFactory | ||
- (AVPlayer *)playerWithPlayerItem:(AVPlayerItem *)playerItem; | ||
@end | ||
|
||
@interface FLTVideoPlayerPlugin () | ||
|
||
- (instancetype)initWithPlayerFactory:(id<FVPPlayerFactory>)playerFactory | ||
registrar:(NSObject<FlutterPluginRegistrar> *)registrar; | ||
@end |
Uh oh!
There was an error while loading. Please reload this page.