Skip to content
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

Feature/january changes 7 #259

Merged
merged 8 commits into from
Jan 31, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added retry feature
  • Loading branch information
jhomlala committed Jan 30, 2021
commit 3a4a3d36a04f4ce4150ddb979c38a3cd380c4c5b
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 0.0.49
* Fixed fullscreen dispose issue.
* Added videoFormat parameter in BetterPlayerDataSource.
* Added videoFormat parameter in BetterPlayerDataSource (should be used when data source url has no extension).
* Added retry feature after video failed to load.
* Added enableRetry in BetterPlayerControlsConfiguration.

## 0.0.48
* Fixed loading large videos in iOS.
Expand Down
4 changes: 1 addition & 3 deletions example/lib/pages/normal_player_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ class _NormalPlayerPageState extends State<NormalPlayerPage> {
);
BetterPlayerDataSource dataSource = BetterPlayerDataSource(
BetterPlayerDataSourceType.network,
"https://d6b09034.tekurarere.tk/mq/NNUVmAXzjPHmdFAxvlmOAXjNYAmNAgjVmZJiRmX_lp",
videoFormat: BetterPlayerVideoFormat.hls,
//overriddenDuration: Duration(seconds: 5),
Constants.bugBuckBunnyVideoUrl,
);
_betterPlayerController = BetterPlayerController(betterPlayerConfiguration);
_betterPlayerController.setupDataSource(dataSource);
Expand Down
64 changes: 49 additions & 15 deletions ios/Classes/FLTBetterPlayerPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ @interface FLTBetterPlayer : NSObject <FlutterTexture, FlutterStreamHandler, AVP
@property(nonatomic) AVPlayerLayer* _playerLayer;
@property(nonatomic) bool _pictureInPicture;
@property(nonatomic) bool _observersAdded;
@property(nonatomic) int stalledCount;
- (void)play;
- (void)pause;
- (void)setIsLooping:(bool)isLooping;
Expand All @@ -66,6 +67,7 @@ - (int64_t) duration;
- (int64_t) position;
@end


static void* timeRangeContext = &timeRangeContext;
static void* statusContext = &statusContext;
static void* playbackLikelyToKeepUpContext = &playbackLikelyToKeepUpContext;
Expand Down Expand Up @@ -106,6 +108,7 @@ - (void)addObservers:(AVPlayerItem*)item {
if (!self._observersAdded){
[item addObserver:self forKeyPath:@"loadedTimeRanges" options:0 context:timeRangeContext];
[item addObserver:self forKeyPath:@"status" options:0 context:statusContext];
[_player addObserver:self forKeyPath:@"rate" options:0 context:nil];
[item addObserver:self
forKeyPath:@"playbackLikelyToKeepUp"
options:0
Expand All @@ -122,8 +125,10 @@ - (void)addObservers:(AVPlayerItem*)item {
selector:@selector(itemDidPlayToEndTime:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item];
///Currently disabled, because it leads to unexpected problems
//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackStalled:) name:AVPlayerItemPlaybackStalledNotification object:item ];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(itemFailedToPlayToEndTime:)
name:AVPlayerItemFailedToPlayToEndTimeNotification
object:item];
self._observersAdded = true;
}
}
Expand Down Expand Up @@ -175,14 +180,13 @@ - (void) removeObservers{
[[_player currentItem] removeObserver:self
forKeyPath:@"playbackBufferFull"
context:playbackBufferFullContext];
///Currently disabled
///[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
self._observersAdded = false;
}
}

- (void)itemDidPlayToEndTime:(NSNotification*)notification {

if (_isLooping) {
AVPlayerItem* p = [notification object];
[p seekToTime:kCMTimeZero completionHandler:nil];
Expand All @@ -195,6 +199,7 @@ - (void)itemDidPlayToEndTime:(NSNotification*)notification {
}
}


static inline CGFloat radiansToDegrees(CGFloat radians) {
// Input range [-pi, pi] or [-180, 180]
CGFloat degrees = GLKMathRadiansToDegrees((float)radians);
Expand Down Expand Up @@ -313,6 +318,7 @@ - (void)setDataSourceURL:(NSURL*)url withKey:(NSString*)key withHeaders:(NSDicti

- (void)setDataSourcePlayerItem:(AVPlayerItem*)item withKey:(NSString*)key{
_key = key;
_stalledCount =0;
[_player replaceCurrentItemWithPlayerItem:item];

AVAsset* asset = [item asset];
Expand Down Expand Up @@ -348,20 +354,47 @@ - (void)setDataSourcePlayerItem:(AVPlayerItem*)item withKey:(NSString*)key{
[self addObservers:item];
}

- (void)playbackStalled:(NSNotification *)notification {
if (_eventSink != nil) {
_eventSink([FlutterError
errorWithCode:@"VideoError"
message:@"Failed to load video: playback stalled"
details:nil]);
-(void)handleStalled {
if (_player.currentItem.playbackLikelyToKeepUp ||
[self availableDuration] - CMTimeGetSeconds(_player.currentItem.currentTime) > 10.0) {
[self play];
} else {
_stalledCount++;
if (_stalledCount > 5){
_eventSink([FlutterError
errorWithCode:@"VideoError"
message:@"Failed to load video: playback stalled"
details:nil]);
return;
}
[self performSelector:@selector(handleStalled) withObject:nil afterDelay:1];
}
}

- (NSTimeInterval) availableDuration
{
NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges];
CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue];
Float64 startSeconds = CMTimeGetSeconds(timeRange.start);
Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;
return result;
}

- (void)observeValueForKeyPath:(NSString*)path
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {

if ([path isEqualToString:@"rate"]) {
if (_player.rate == 0 && //if player rate dropped to 0
CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, >, kCMTimeZero) && //if video was started
CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, <, _player.currentItem.duration) && //but not yet finished
_isPlaying) { //instance variable to handle overall state (changed to YES when user triggers playback)
[self handleStalled];
}
}

if (context == timeRangeContext) {
if (_eventSink != nil) {
NSMutableArray<NSArray<NSNumber*>*>* values = [[NSMutableArray alloc] init];
Expand Down Expand Up @@ -475,6 +508,7 @@ - (void)onReadyToPlay {
}

- (void)play {
_stalledCount = 0;
_isPlaying = true;
[self updatePlayingState];
}
Expand All @@ -489,7 +523,7 @@ - (int64_t)position {
}

- (int64_t)absolutePosition {
return FLTNSTimeIntervalToMillis([[[_player currentItem] currentDate] timeIntervalSince1970]);
return FLTNSTimeIntervalToMillis([[[_player currentItem] currentDate] timeIntervalSince1970]);
}

- (int64_t)duration {
Expand All @@ -512,16 +546,16 @@ - (void)seekTo:(int)location {
if (wasPlaying){
[_player pause];
}

[_player seekToTime:CMTimeMake(location, 1000)
toleranceBefore:kCMTimeZero
toleranceAfter:kCMTimeZero
completionHandler:^(BOOL finished){
completionHandler:^(BOOL finished){
if (wasPlaying){
[self->_player play];
}
}];

}

- (void)setIsLooping:(bool)isLooping {
Expand Down Expand Up @@ -1093,7 +1127,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
} else if ([@"position" isEqualToString:call.method]) {
result(@([player position]));
} else if ([@"absolutePosition" isEqualToString:call.method]) {
result(@([player absolutePosition]));
result(@([player absolutePosition]));
} else if ([@"seekTo" isEqualToString:call.method]) {
[player seekTo:[argsMap[@"location"] intValue]];
result(nil);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ class BetterPlayerControlsConfiguration {
///Flag used to show/hide PiP mode
final bool enablePip;

///Flag used to enable/disable retry feature
final bool enableRetry;

///Custom items of overflow menu
final List<BetterPlayerOverflowMenuItem> overflowMenuCustomItems;

Expand Down Expand Up @@ -178,6 +181,7 @@ class BetterPlayerControlsConfiguration {
this.enableSubtitles = true,
this.enableQualities = true,
this.enablePip = true,
this.enableRetry = true,
this.overflowMenuCustomItems = const [],
this.overflowMenuIcon = Icons.more_vert,
this.pipMenuIcon = Icons.picture_in_picture,
Expand Down
5 changes: 5 additions & 0 deletions lib/src/configuration/better_player_translations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class BetterPlayerTranslations {
final String generalDefaultError;
final String generalNone;
final String generalDefault;
final String generalRetry;
final String playlistLoadingNextVideo;
final String controlsLive;
final String controlsNextVideoIn;
Expand All @@ -16,6 +17,7 @@ class BetterPlayerTranslations {
this.generalDefaultError = "Video can't be played",
this.generalNone = "None",
this.generalDefault = "Default",
this.generalRetry = "Retry",
this.playlistLoadingNextVideo = "Loading next video",
this.controlsLive = "LIVE",
this.controlsNextVideoIn = "Next video in",
Expand All @@ -29,6 +31,7 @@ class BetterPlayerTranslations {
generalDefaultError: "Video nie może zostać odtworzone",
generalNone: "Brak",
generalDefault: "Domyślne",
generalRetry: "Spróbuj ponownie",
playlistLoadingNextVideo: "Ładowanie następnego filmu",
controlsNextVideoIn: "Następne video za",
overflowMenuPlaybackSpeed: "Szybkość odtwarzania",
Expand All @@ -41,6 +44,7 @@ class BetterPlayerTranslations {
generalDefaultError: "无法播放视频",
generalNone: "没有",
generalDefault: "默认",
generalRetry: "重試",
playlistLoadingNextVideo: "正在加载下一个视频",
controlsLive: "直播",
controlsNextVideoIn: "下一部影片n",
Expand All @@ -54,6 +58,7 @@ class BetterPlayerTranslations {
generalDefaultError: "वीडियो नहीं चलाया जा सकता",
generalNone: "कोई नहीं",
generalDefault: "चूक",
generalRetry: "पुनः प्रयास करें",
playlistLoadingNextVideo: "अगला वीडियो लोड हो रहा है",
controlsLive: "लाइव",
controlsNextVideoIn: "में अगला वीडियो",
Expand Down
18 changes: 16 additions & 2 deletions lib/src/controls/better_player_cupertino_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ class _BetterPlayerCupertinoControlsState
_betterPlayerController = BetterPlayerController.of(context);

if (_latestValue?.hasError == true) {
return _buildErrorWidget();
return Container(
color: Colors.black,
child: _buildErrorWidget(),
);
}

final backgroundColor = _controlsConfiguration.controlBarColor;
Expand Down Expand Up @@ -687,6 +690,7 @@ class _BetterPlayerCupertinoControlsState
return _betterPlayerController.errorBuilder(context,
_betterPlayerController.videoPlayerController.value.errorDescription);
} else {
final textStyle = TextStyle(color: _controlsConfiguration.textColor);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
Expand All @@ -698,8 +702,18 @@ class _BetterPlayerCupertinoControlsState
),
Text(
_betterPlayerController.translations.generalDefaultError,
style: TextStyle(color: _controlsConfiguration.textColor),
style: textStyle,
),
if (_controlsConfiguration.enableRetry)
FlatButton(
child: Text(
_betterPlayerController.translations.generalRetry,
style: textStyle.copyWith(fontWeight: FontWeight.bold),
),
onPressed: () {
_betterPlayerController.retryDataSource();
},
)
],
),
);
Expand Down
18 changes: 16 additions & 2 deletions lib/src/controls/better_player_material_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ class _BetterPlayerMaterialControlsState
Widget build(BuildContext context) {
_wasLoading = isLoading(_latestValue);
if (_latestValue?.hasError == true) {
return _buildErrorWidget();
return Container(
color: Colors.black,
child: _buildErrorWidget(),
);
}
return MouseRegion(
onHover: (_) {
Expand Down Expand Up @@ -137,6 +140,7 @@ class _BetterPlayerMaterialControlsState
return _betterPlayerController.errorBuilder(context,
_betterPlayerController.videoPlayerController.value.errorDescription);
} else {
final textStyle = TextStyle(color: _controlsConfiguration.textColor);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
Expand All @@ -148,8 +152,18 @@ class _BetterPlayerMaterialControlsState
),
Text(
_betterPlayerController.translations.generalDefaultError,
style: TextStyle(color: _controlsConfiguration.textColor),
style: textStyle,
),
if (_controlsConfiguration.enableRetry)
FlatButton(
child: Text(
_betterPlayerController.translations.generalRetry,
style: textStyle.copyWith(fontWeight: FontWeight.bold),
),
onPressed: () {
_betterPlayerController.retryDataSource();
},
)
],
),
);
Expand Down
16 changes: 16 additions & 0 deletions lib/src/core/better_player_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,11 @@ class BetterPlayerController extends ChangeNotifier {
///Are controls always visible
bool _controlsAlwaysVisible = false;

///Are controls always visible
bool get controlsAlwaysVisible => _controlsAlwaysVisible;

VideoPlayerValue _videoPlayerValueOnError;

BetterPlayerController(
this.betterPlayerConfiguration, {
this.betterPlayerPlaylistConfiguration,
Expand Down Expand Up @@ -475,6 +478,9 @@ class BetterPlayerController extends ChangeNotifier {
void _onVideoPlayerChanged() async {
final currentVideoPlayerValue = videoPlayerController.value;
if (currentVideoPlayerValue.hasError) {
if (_videoPlayerValueOnError == null) {
_videoPlayerValueOnError = currentVideoPlayerValue;
}
_postEvent(
BetterPlayerEvent(
BetterPlayerEventType.exception,
Expand Down Expand Up @@ -788,6 +794,16 @@ class BetterPlayerController extends ChangeNotifier {
_controlsVisibilityStreamController.add(controlsAlwaysVisible);
}

Future retryDataSource() async {
await _setupDataSource(_betterPlayerDataSource);
if (_videoPlayerValueOnError != null) {
final position = _videoPlayerValueOnError.position;
await seekTo(position);
await play();
_videoPlayerValueOnError = null;
}
}

///Dispose BetterPlayerController. When [forceDispose] parameter is true, then
///autoDispose parameter will be overridden and controller will be disposed
///(if it wasn't disposed before).
Expand Down
10 changes: 7 additions & 3 deletions lib/src/core/better_player_with_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,13 @@ class _BetterPlayerWithControlsState extends State<BetterPlayerWithControls> {
) {
if (controlsConfiguration.showControls) {
BetterPlayerTheme playerTheme = controlsConfiguration.playerTheme;
playerTheme ??= Platform.isAndroid != null
? BetterPlayerTheme.material
: BetterPlayerTheme.cupertino;
if (playerTheme == null){
if (Platform.isAndroid){
playerTheme = BetterPlayerTheme.material;
} else {
playerTheme = BetterPlayerTheme.cupertino;
}
}

if (controlsConfiguration.customControlsBuilder != null &&
playerTheme == BetterPlayerTheme.custom) {
Expand Down
6 changes: 4 additions & 2 deletions lib/src/video_player/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,11 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
void errorListener(Object object) {
if (object is PlatformException) {
final PlatformException e = object;
value = VideoPlayerValue.erroneous(e.message);
value = value.copyWith(errorDescription: e.message);
//value = VideoPlayerValue.erroneous(e.message);
} else {
value = VideoPlayerValue.erroneous(object.toString());
//value = VideoPlayerValue.erroneous(object.toString());
value.copyWith(errorDescription: object.toString());
}
_timer?.cancel();
if (!_initializingCompleter.isCompleted) {
Expand Down