diff --git a/CHANGELOG.md b/CHANGELOG.md index 905176f1f..cc8b7088a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.0.62 +* Refactored internal event handling. +* [BREAKING_CHANGE] Migrated to null safety. +* [BREAKING_CHANGE] Updated dart min version to 2.12.0. +* Fixed issue where player controls were immediately hidden. +* Removed cancelFullScreenDismiss parameter. +* Added initialization check for VideoPlayerController. +* Changed default value of enableProgressText to true in BetterPlayerControlsConfiguration. +* Setup first selected HLS Audio as default one. +* General bug fixes. + ## 0.0.61 * Fixed fullscreenByDefault issue. * Updated documentation. diff --git a/README.md b/README.md index a4ed948fa..182eb7f97 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ This plugin is based on [Chewie](https://github.com/brianegan/chewie). Chewie is ```yaml dependencies: - better_player: ^0.0.61 + better_player: ^0.0.62 ``` 2. Install it diff --git a/example/lib/model/video_list_data.dart b/example/lib/model/video_list_data.dart index b02a92abc..f0e0ea2b1 100644 --- a/example/lib/model/video_list_data.dart +++ b/example/lib/model/video_list_data.dart @@ -1,8 +1,8 @@ class VideoListData { final String videoTitle; final String videoUrl; - Duration lastPosition; - bool wasPlaying = false; + Duration? lastPosition; + bool? wasPlaying = false; VideoListData(this.videoTitle, this.videoUrl); } diff --git a/example/lib/pages/auto_fullscreen_orientation_page.dart b/example/lib/pages/auto_fullscreen_orientation_page.dart index a528d1257..eed036887 100644 --- a/example/lib/pages/auto_fullscreen_orientation_page.dart +++ b/example/lib/pages/auto_fullscreen_orientation_page.dart @@ -10,7 +10,7 @@ class AutoFullscreenOrientationPage extends StatefulWidget { class _AutoFullscreenOrientationPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/basic_player_page.dart b/example/lib/pages/basic_player_page.dart index 44f8ab698..b7d96b17c 100644 --- a/example/lib/pages/basic_player_page.dart +++ b/example/lib/pages/basic_player_page.dart @@ -43,7 +43,7 @@ class _BasicPlayerPageState extends State { future: Utils.getFileUrl(Constants.fileTestVideoUrl), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.data != null) { - return BetterPlayer.file(snapshot.data); + return BetterPlayer.file(snapshot.data!); } else { return const SizedBox(); } diff --git a/example/lib/pages/cache_page.dart b/example/lib/pages/cache_page.dart index a9bbecae7..1245fbb63 100644 --- a/example/lib/pages/cache_page.dart +++ b/example/lib/pages/cache_page.dart @@ -8,7 +8,7 @@ class CachePage extends StatefulWidget { } class _CachePageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/change_player_theme_page.dart b/example/lib/pages/change_player_theme_page.dart index c567b911b..6eec22afe 100644 --- a/example/lib/pages/change_player_theme_page.dart +++ b/example/lib/pages/change_player_theme_page.dart @@ -9,8 +9,8 @@ class ChangePlayerThemePage extends StatefulWidget { } class _ChangePlayerThemePageState extends State { - BetterPlayerController _betterPlayerController; - BetterPlayerDataSource _dataSource; + late BetterPlayerController _betterPlayerController; + BetterPlayerDataSource? _dataSource; BetterPlayerTheme _playerTheme = BetterPlayerTheme.material; @override diff --git a/example/lib/pages/controller_controls_page.dart b/example/lib/pages/controller_controls_page.dart index 076173250..fe7e36c89 100644 --- a/example/lib/pages/controller_controls_page.dart +++ b/example/lib/pages/controller_controls_page.dart @@ -8,7 +8,7 @@ class ControllerControlsPage extends StatefulWidget { } class _ControllerControlsPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/controls_always_visible_page.dart b/example/lib/pages/controls_always_visible_page.dart index 41e9c9b05..b9aca8eff 100644 --- a/example/lib/pages/controls_always_visible_page.dart +++ b/example/lib/pages/controls_always_visible_page.dart @@ -9,7 +9,7 @@ class ControlsAlwaysVisiblePage extends StatefulWidget { } class _ControlsAlwaysVisiblePageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { @@ -56,7 +56,7 @@ class _ControlsAlwaysVisiblePageState extends State { ElevatedButton( onPressed: () { _betterPlayerController.setControlsAlwaysVisible( - !(_betterPlayerController.controlsAlwaysVisible ?? false)); + !(_betterPlayerController.controlsAlwaysVisible)); }, child: Text("Toggle always visible controls")) ], diff --git a/example/lib/pages/controls_configuration_page.dart b/example/lib/pages/controls_configuration_page.dart index c0d606755..a12c1815a 100644 --- a/example/lib/pages/controls_configuration_page.dart +++ b/example/lib/pages/controls_configuration_page.dart @@ -9,7 +9,7 @@ class ControlsConfigurationPage extends StatefulWidget { } class _ControlsConfigurationPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/custom_controls/custom_controls_widget.dart b/example/lib/pages/custom_controls/custom_controls_widget.dart index 156663fd3..4644109bf 100644 --- a/example/lib/pages/custom_controls/custom_controls_widget.dart +++ b/example/lib/pages/custom_controls/custom_controls_widget.dart @@ -2,9 +2,9 @@ import 'package:better_player/better_player.dart'; import 'package:flutter/material.dart'; class CustomControlsWidget extends StatefulWidget { - final BetterPlayerController controller; + final BetterPlayerController? controller; - const CustomControlsWidget({Key key, this.controller}) : super(key: key); + const CustomControlsWidget({Key? key, this.controller}) : super(key: key); @override _CustomControlsWidgetState createState() => _CustomControlsWidgetState(); @@ -30,7 +30,7 @@ class _CustomControlsWidgetState extends State { child: Padding( padding: const EdgeInsets.all(8.0), child: Icon( - widget.controller.isFullScreen + widget.controller!.isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, color: Colors.white, @@ -39,10 +39,10 @@ class _CustomControlsWidgetState extends State { ), ), onTap: () => setState(() { - if (widget.controller.isFullScreen) - widget.controller.exitFullScreen(); + if (widget.controller!.isFullScreen) + widget.controller!.exitFullScreen(); else - widget.controller.enterFullScreen(); + widget.controller!.enterFullScreen(); }), ), ), @@ -65,18 +65,18 @@ class _CustomControlsWidgetState extends State { children: [ InkWell( onTap: () async { - Duration videoDuration = await widget - .controller.videoPlayerController.position; + Duration? videoDuration = await widget + .controller!.videoPlayerController!.position; setState(() { - if (widget.controller.isPlaying()) { + if (widget.controller!.isPlaying()!) { Duration rewindDuration = Duration( - seconds: (videoDuration.inSeconds - 2)); + seconds: (videoDuration!.inSeconds - 2)); if (rewindDuration < - widget.controller.videoPlayerController.value - .duration) { - widget.controller.seekTo(Duration(seconds: 0)); + widget.controller!.videoPlayerController! + .value.duration!) { + widget.controller!.seekTo(Duration(seconds: 0)); } else { - widget.controller.seekTo(rewindDuration); + widget.controller!.seekTo(rewindDuration); } } }); @@ -89,14 +89,14 @@ class _CustomControlsWidgetState extends State { InkWell( onTap: () { setState(() { - if (widget.controller.isPlaying()) - widget.controller.pause(); + if (widget.controller!.isPlaying()!) + widget.controller!.pause(); else - widget.controller.play(); + widget.controller!.play(); }); }, child: Icon( - widget.controller.isPlaying() + widget.controller!.isPlaying()! ? Icons.pause : Icons.play_arrow, color: Colors.white, @@ -104,19 +104,19 @@ class _CustomControlsWidgetState extends State { ), InkWell( onTap: () async { - Duration videoDuration = await widget - .controller.videoPlayerController.position; + Duration? videoDuration = await widget + .controller!.videoPlayerController!.position; setState(() { - if (widget.controller.isPlaying()) { + if (widget.controller!.isPlaying()!) { Duration forwardDuration = Duration( - seconds: (videoDuration.inSeconds + 2)); + seconds: (videoDuration!.inSeconds + 2)); if (forwardDuration > - widget.controller.videoPlayerController.value - .duration) { - widget.controller.seekTo(Duration(seconds: 0)); - widget.controller.pause(); + widget.controller!.videoPlayerController! + .value.duration!) { + widget.controller!.seekTo(Duration(seconds: 0)); + widget.controller!.pause(); } else { - widget.controller.seekTo(forwardDuration); + widget.controller!.seekTo(forwardDuration); } } }); diff --git a/example/lib/pages/drm_page.dart b/example/lib/pages/drm_page.dart index 86f415b96..9226bae46 100644 --- a/example/lib/pages/drm_page.dart +++ b/example/lib/pages/drm_page.dart @@ -8,8 +8,8 @@ class DrmPage extends StatefulWidget { } class _DrmPageState extends State { - BetterPlayerController _tokenController; - BetterPlayerController _widevineController; + late BetterPlayerController _tokenController; + late BetterPlayerController _widevineController; @override void initState() { diff --git a/example/lib/pages/event_listener_page.dart b/example/lib/pages/event_listener_page.dart index 0cd9bcc8c..3043531c4 100644 --- a/example/lib/pages/event_listener_page.dart +++ b/example/lib/pages/event_listener_page.dart @@ -10,7 +10,7 @@ class EventListenerPage extends StatefulWidget { } class _EventListenerPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; List events = []; StreamController _eventStreamController = StreamController.broadcast(); diff --git a/example/lib/pages/fade_placeholder_page.dart b/example/lib/pages/fade_placeholder_page.dart index d21bbb899..43976c743 100644 --- a/example/lib/pages/fade_placeholder_page.dart +++ b/example/lib/pages/fade_placeholder_page.dart @@ -10,7 +10,7 @@ class FadePlaceholderPage extends StatefulWidget { } class _FadePlaceholderPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; StreamController _playController = StreamController.broadcast(); @override diff --git a/example/lib/pages/hls_audio_page.dart b/example/lib/pages/hls_audio_page.dart index d3a7ee9f6..116223d04 100644 --- a/example/lib/pages/hls_audio_page.dart +++ b/example/lib/pages/hls_audio_page.dart @@ -8,7 +8,7 @@ class HlsAudioPage extends StatefulWidget { } class _HlsAudioPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/hls_subtitles_page.dart b/example/lib/pages/hls_subtitles_page.dart index f49c9cf5f..d142cd37d 100644 --- a/example/lib/pages/hls_subtitles_page.dart +++ b/example/lib/pages/hls_subtitles_page.dart @@ -8,7 +8,7 @@ class HlsSubtitlesPage extends StatefulWidget { } class _HlsSubtitlesPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/hls_tracks_page.dart b/example/lib/pages/hls_tracks_page.dart index 4ebc7a6e3..852de2fc9 100644 --- a/example/lib/pages/hls_tracks_page.dart +++ b/example/lib/pages/hls_tracks_page.dart @@ -8,7 +8,7 @@ class HlsTracksPage extends StatefulWidget { } class _HlsTracksPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/memory_player_page.dart b/example/lib/pages/memory_player_page.dart index 7a9c30174..702f772de 100644 --- a/example/lib/pages/memory_player_page.dart +++ b/example/lib/pages/memory_player_page.dart @@ -11,7 +11,7 @@ class MemoryPlayerPage extends StatefulWidget { } class _MemoryPlayerPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/normal_player_page.dart b/example/lib/pages/normal_player_page.dart index b9e2af2e0..87e0e15bb 100644 --- a/example/lib/pages/normal_player_page.dart +++ b/example/lib/pages/normal_player_page.dart @@ -9,7 +9,7 @@ class NormalPlayerPage extends StatefulWidget { } class _NormalPlayerPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { @@ -17,13 +17,11 @@ class _NormalPlayerPageState extends State { BetterPlayerConfiguration( aspectRatio: 16 / 9, fit: BoxFit.contain, - ); - BetterPlayerDataSource dataSource = BetterPlayerDataSource( - BetterPlayerDataSourceType.network, - "http://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8", + autoPlay: true, ); _betterPlayerController = BetterPlayerController(betterPlayerConfiguration); - _betterPlayerController.setupDataSource(dataSource); + _betterPlayerController.setupDataSource( + BetterPlayerDataSource.network(Constants.forBiggerBlazesUrl)); super.initState(); } diff --git a/example/lib/pages/notification_player_page.dart b/example/lib/pages/notification_player_page.dart index be6358d35..e5cd3365a 100644 --- a/example/lib/pages/notification_player_page.dart +++ b/example/lib/pages/notification_player_page.dart @@ -8,7 +8,7 @@ class NotificationPlayerPage extends StatefulWidget { } class _NotificationPlayerPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/overridden_aspect_ratio_page.dart b/example/lib/pages/overridden_aspect_ratio_page.dart index fc87a1c80..52203a85b 100644 --- a/example/lib/pages/overridden_aspect_ratio_page.dart +++ b/example/lib/pages/overridden_aspect_ratio_page.dart @@ -9,7 +9,7 @@ class OverriddenAspectRatioPage extends StatefulWidget { } class _OverriddenAspectRatioPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/overriden_duration_page.dart b/example/lib/pages/overriden_duration_page.dart index c43f2c981..f4af01a47 100644 --- a/example/lib/pages/overriden_duration_page.dart +++ b/example/lib/pages/overriden_duration_page.dart @@ -8,7 +8,7 @@ class OverriddenDurationPage extends StatefulWidget { } class _OverriddenDurationPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/picture_in_picture_page.dart b/example/lib/pages/picture_in_picture_page.dart index a8362c377..6ff4c3308 100644 --- a/example/lib/pages/picture_in_picture_page.dart +++ b/example/lib/pages/picture_in_picture_page.dart @@ -8,7 +8,7 @@ class PictureInPicturePage extends StatefulWidget { } class _PictureInPicturePageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; GlobalKey _betterPlayerKey = GlobalKey(); @override diff --git a/example/lib/pages/placeholder_until_play_page.dart b/example/lib/pages/placeholder_until_play_page.dart index e4041b82a..4f857213c 100644 --- a/example/lib/pages/placeholder_until_play_page.dart +++ b/example/lib/pages/placeholder_until_play_page.dart @@ -11,7 +11,7 @@ class PlaceholderUntilPlayPage extends StatefulWidget { } class _PlaceholderUntilPlayPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; StreamController _placeholderStreamController = StreamController.broadcast(); bool _showPlaceholder = true; diff --git a/example/lib/pages/playlist_page.dart b/example/lib/pages/playlist_page.dart index 385ad4fd6..d45428c76 100644 --- a/example/lib/pages/playlist_page.dart +++ b/example/lib/pages/playlist_page.dart @@ -13,8 +13,8 @@ class _PlaylistPageState extends State { final GlobalKey _betterPlayerPlaylistStateKey = GlobalKey(); List _dataSourceList = []; - BetterPlayerConfiguration _betterPlayerConfiguration; - BetterPlayerPlaylistConfiguration _betterPlayerPlaylistConfiguration; + late BetterPlayerConfiguration _betterPlayerConfiguration; + late BetterPlayerPlaylistConfiguration _betterPlayerPlaylistConfiguration; _PlaylistPageState() { _betterPlayerConfiguration = BetterPlayerConfiguration( @@ -93,33 +93,33 @@ class _PlaylistPageState extends State { betterPlayerConfiguration: _betterPlayerConfiguration, betterPlayerPlaylistConfiguration: _betterPlayerPlaylistConfiguration, - betterPlayerDataSourceList: snapshot.data, + betterPlayerDataSourceList: snapshot.data!, ), aspectRatio: 1, ), ElevatedButton( onPressed: () { - _betterPlayerPlaylistController.setupDataSource(0); + _betterPlayerPlaylistController!.setupDataSource(0); }, child: Text("Change to first data source"), ), ElevatedButton( onPressed: () { - _betterPlayerPlaylistController.setupDataSource(2); + _betterPlayerPlaylistController!.setupDataSource(2); }, child: Text("Change to last source"), ), ElevatedButton( onPressed: () { print("Currently playing video: " + - _betterPlayerPlaylistController.currentDataSourceIndex + _betterPlayerPlaylistController!.currentDataSourceIndex .toString()); }, child: Text("Check currently playing video index"), ), ElevatedButton( onPressed: () { - _betterPlayerPlaylistController.betterPlayerController + _betterPlayerPlaylistController!.betterPlayerController! .pause(); }, child: Text("Pause current video with BetterPlayerController"), @@ -131,6 +131,7 @@ class _PlaylistPageState extends State { ); } - BetterPlayerPlaylistController get _betterPlayerPlaylistController => - _betterPlayerPlaylistStateKey.currentState.betterPlayerPlaylistController; + BetterPlayerPlaylistController? get _betterPlayerPlaylistController => + _betterPlayerPlaylistStateKey + .currentState!.betterPlayerPlaylistController; } diff --git a/example/lib/pages/resolutions_page.dart b/example/lib/pages/resolutions_page.dart index 5e299a774..f89dcf7a6 100644 --- a/example/lib/pages/resolutions_page.dart +++ b/example/lib/pages/resolutions_page.dart @@ -8,7 +8,7 @@ class ResolutionsPage extends StatefulWidget { } class _ResolutionsPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/reusable_video_list/reusable_video_list_controller.dart b/example/lib/pages/reusable_video_list/reusable_video_list_controller.dart index 6b1582c11..aac6ff523 100644 --- a/example/lib/pages/reusable_video_list/reusable_video_list_controller.dart +++ b/example/lib/pages/reusable_video_list/reusable_video_list_controller.dart @@ -1,4 +1,5 @@ import 'package:better_player/better_player.dart'; +import 'package:collection/collection.dart' show IterableExtension; class ReusableVideoListController { final List _betterPlayerControllerRegistry = []; @@ -14,11 +15,10 @@ class ReusableVideoListController { } } - BetterPlayerController getBetterPlayerController() { - final freeController = _betterPlayerControllerRegistry.firstWhere( + BetterPlayerController? getBetterPlayerController() { + final freeController = _betterPlayerControllerRegistry.firstWhereOrNull( (controller) => - !_usedBetterPlayerControllerRegistry.contains(controller), - orElse: () => null); + !_usedBetterPlayerControllerRegistry.contains(controller)); if (freeController != null) { _usedBetterPlayerControllerRegistry.add(freeController); @@ -28,7 +28,7 @@ class ReusableVideoListController { } void freeBetterPlayerController( - BetterPlayerController betterPlayerController) { + BetterPlayerController? betterPlayerController) { _usedBetterPlayerControllerRegistry.remove(betterPlayerController); } diff --git a/example/lib/pages/reusable_video_list/reusable_video_list_page.dart b/example/lib/pages/reusable_video_list/reusable_video_list_page.dart index 4a6c8c19a..6e826bfe0 100644 --- a/example/lib/pages/reusable_video_list/reusable_video_list_page.dart +++ b/example/lib/pages/reusable_video_list/reusable_video_list_page.dart @@ -57,7 +57,7 @@ class _ReusableVideoListPageState extends State { final now = DateTime.now(); final timeDiff = now.millisecondsSinceEpoch - lastMilli; if (notification is ScrollUpdateNotification) { - final pixelsPerMilli = notification.scrollDelta / timeDiff; + final pixelsPerMilli = notification.scrollDelta! / timeDiff; if (pixelsPerMilli.abs() > 1) { _canBuildVideo = false; } else { diff --git a/example/lib/pages/reusable_video_list/reusable_video_list_widget.dart b/example/lib/pages/reusable_video_list/reusable_video_list_widget.dart index afe58443c..6090b155e 100644 --- a/example/lib/pages/reusable_video_list/reusable_video_list_widget.dart +++ b/example/lib/pages/reusable_video_list/reusable_video_list_widget.dart @@ -4,15 +4,15 @@ import 'package:better_player/better_player.dart'; import 'package:better_player_example/model/video_list_data.dart'; import 'package:better_player_example/pages/reusable_video_list/reusable_video_list_controller.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_widgets/flutter_widgets.dart'; +import 'package:visibility_detector/visibility_detector.dart'; class ReusableVideoListWidget extends StatefulWidget { - final VideoListData videoListData; - final ReusableVideoListController videoListController; - final Function canBuildVideo; + final VideoListData? videoListData; + final ReusableVideoListController? videoListController; + final Function? canBuildVideo; const ReusableVideoListWidget({ - Key key, + Key? key, this.videoListData, this.videoListController, this.canBuildVideo, @@ -24,12 +24,12 @@ class ReusableVideoListWidget extends StatefulWidget { } class _ReusableVideoListWidgetState extends State { - VideoListData get videoListData => widget.videoListData; - BetterPlayerController controller; - StreamController + VideoListData? get videoListData => widget.videoListData; + BetterPlayerController? controller; + StreamController betterPlayerControllerStreamController = StreamController.broadcast(); bool _initialized = false; - Timer _timer; + Timer? _timer; @override void initState() { @@ -44,12 +44,12 @@ class _ReusableVideoListWidgetState extends State { void _setupController() { if (controller == null) { - controller = widget.videoListController.getBetterPlayerController(); - controller.setupDataSource(BetterPlayerDataSource.network( - videoListData.videoUrl, + controller = widget.videoListController!.getBetterPlayerController(); + controller!.setupDataSource(BetterPlayerDataSource.network( + videoListData!.videoUrl, cacheConfiguration: BetterPlayerCacheConfiguration(useCache: true))); betterPlayerControllerStreamController.add(controller); - controller.addEventsListener(onPlayerEvent); + controller!.addEventsListener(onPlayerEvent); } } @@ -59,9 +59,9 @@ class _ReusableVideoListWidgetState extends State { return; } if (controller != null && _initialized) { - controller.removeEventsListener(onPlayerEvent); - widget.videoListController.freeBetterPlayerController(controller); - controller.pause(); + controller!.removeEventsListener(onPlayerEvent); + widget.videoListController!.freeBetterPlayerController(controller); + controller!.pause(); controller = null; betterPlayerControllerStreamController.add(null); _initialized = false; @@ -70,14 +70,14 @@ class _ReusableVideoListWidgetState extends State { void onPlayerEvent(BetterPlayerEvent event) { if (event.betterPlayerEventType == BetterPlayerEventType.progress) { - videoListData.lastPosition = event.parameters["progress"] as Duration; + videoListData!.lastPosition = event.parameters!["progress"] as Duration?; } if (event.betterPlayerEventType == BetterPlayerEventType.initialized) { - if (videoListData.lastPosition != null) { - controller.seekTo(videoListData.lastPosition); + if (videoListData!.lastPosition != null) { + controller!.seekTo(videoListData!.lastPosition!); } - if (videoListData.wasPlaying) { - controller.play(); + if (videoListData!.wasPlaying!) { + controller!.play(); } } } @@ -94,14 +94,14 @@ class _ReusableVideoListWidgetState extends State { Padding( padding: EdgeInsets.all(8), child: Text( - videoListData.videoTitle, + videoListData!.videoTitle, style: TextStyle(fontSize: 50), ), ), VisibilityDetector( key: Key(hashCode.toString() + DateTime.now().toString()), onVisibilityChanged: (info) { - if (!widget.canBuildVideo()) { + if (!widget.canBuildVideo!()) { _timer?.cancel(); _timer = null; _timer = Timer(Duration(milliseconds: 500), () { @@ -119,14 +119,14 @@ class _ReusableVideoListWidgetState extends State { _freeController(); } }, - child: StreamBuilder( + child: StreamBuilder( stream: betterPlayerControllerStreamController.stream, builder: (context, snapshot) { return AspectRatio( aspectRatio: 16 / 9, child: controller != null ? BetterPlayer( - controller: controller, + controller: controller!, ) : Container( color: Colors.black, @@ -156,21 +156,21 @@ class _ReusableVideoListWidgetState extends State { ElevatedButton( child: Text("Play"), onPressed: () { - controller.play(); + controller!.play(); }, ), const SizedBox(width: 8), ElevatedButton( child: Text("Pause"), onPressed: () { - controller.pause(); + controller!.pause(); }, ), const SizedBox(width: 8), ElevatedButton( child: Text("Set max volume"), onPressed: () { - controller.setVolume(1.0); + controller!.setVolume(1.0); }, ), ]), @@ -183,7 +183,7 @@ class _ReusableVideoListWidgetState extends State { @override void deactivate() { if (controller != null) { - videoListData.wasPlaying = controller.isPlaying(); + videoListData!.wasPlaying = controller!.isPlaying(); } _initialized = true; _freeController(); diff --git a/example/lib/pages/rotation_and_fit_page.dart b/example/lib/pages/rotation_and_fit_page.dart index 35fde4501..c90d7bb59 100644 --- a/example/lib/pages/rotation_and_fit_page.dart +++ b/example/lib/pages/rotation_and_fit_page.dart @@ -8,7 +8,7 @@ class RotationAndFitPage extends StatefulWidget { } class _RotationAndFitPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/subtitles_page.dart b/example/lib/pages/subtitles_page.dart index 6626e1e44..54a03e9c2 100644 --- a/example/lib/pages/subtitles_page.dart +++ b/example/lib/pages/subtitles_page.dart @@ -9,7 +9,7 @@ class SubtitlesPage extends StatefulWidget { } class _SubtitlesPageState extends State { - BetterPlayerController _betterPlayerController; + late BetterPlayerController _betterPlayerController; @override void initState() { diff --git a/example/lib/pages/video_list/video_list_widget.dart b/example/lib/pages/video_list/video_list_widget.dart index cc9223261..0957d29f2 100644 --- a/example/lib/pages/video_list/video_list_widget.dart +++ b/example/lib/pages/video_list/video_list_widget.dart @@ -3,18 +3,18 @@ import 'package:better_player_example/model/video_list_data.dart'; import 'package:flutter/material.dart'; class VideoListWidget extends StatefulWidget { - final VideoListData videoListData; + final VideoListData? videoListData; - const VideoListWidget({Key key, this.videoListData}) : super(key: key); + const VideoListWidget({Key? key, this.videoListData}) : super(key: key); @override _VideoListWidgetState createState() => _VideoListWidgetState(); } class _VideoListWidgetState extends State { - VideoListData get videoListData => widget.videoListData; - BetterPlayerConfiguration betterPlayerConfiguration; - BetterPlayerListVideoPlayerController controller; + VideoListData? get videoListData => widget.videoListData; + BetterPlayerConfiguration? betterPlayerConfiguration; + BetterPlayerListVideoPlayerController? controller; @override void initState() { @@ -38,7 +38,7 @@ class _VideoListWidgetState extends State { Padding( padding: EdgeInsets.all(8), child: Text( - videoListData.videoTitle, + videoListData!.videoTitle, style: TextStyle(fontSize: 50), ), ), @@ -46,17 +46,16 @@ class _VideoListWidgetState extends State { child: BetterPlayerListVideoPlayer( BetterPlayerDataSource( BetterPlayerDataSourceType.network, - videoListData.videoUrl, + videoListData!.videoUrl, notificationConfiguration: BetterPlayerNotificationConfiguration( showNotification: true, - title: videoListData.videoTitle, + title: videoListData!.videoTitle, author: "Test"), ), configuration: BetterPlayerConfiguration( autoPlay: false, aspectRatio: 1, - fit: BoxFit.cover, ), //key: Key(videoListData.hashCode.toString()), playFraction: 0.8, @@ -78,21 +77,21 @@ class _VideoListWidgetState extends State { ElevatedButton( child: Text("Play"), onPressed: () { - controller.play(); + controller!.play(); }, ), const SizedBox(width: 8), ElevatedButton( child: Text("Pause"), onPressed: () { - controller.pause(); + controller!.pause(); }, ), const SizedBox(width: 8), ElevatedButton( child: Text("Set max volume"), onPressed: () { - controller.setVolume(100); + controller!.setVolume(100); }, ), ]), diff --git a/example/lib/pages/welcome_page.dart b/example/lib/pages/welcome_page.dart index e0ea0eea2..4e7512563 100644 --- a/example/lib/pages/welcome_page.dart +++ b/example/lib/pages/welcome_page.dart @@ -162,7 +162,7 @@ class _WelcomePageState extends State { Widget _buildExampleElementWidget(String name, Function onClicked) { return Material( child: InkWell( - onTap: onClicked, + onTap: onClicked as void Function()?, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5b65170cd..5a57d05df 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.0+1 publish_to: none environment: - sdk: ">=2.2.2 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: #better_player: any @@ -14,8 +14,9 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - path_provider: ^1.6.5 - flutter_widgets: ^0.1.12 + path_provider: ^2.0.1 + visibility_detector: ^0.2.0-nullsafety.1 + collection: ^1.15.0-nullsafety.4 dev_dependencies: flutter_test: diff --git a/lib/src/configuration/better_player_configuration.dart b/lib/src/configuration/better_player_configuration.dart index d945a683e..45a23db4d 100644 --- a/lib/src/configuration/better_player_configuration.dart +++ b/lib/src/configuration/better_player_configuration.dart @@ -16,24 +16,25 @@ class BetterPlayerConfiguration { final bool autoPlay; /// Start video at a certain position - final Duration startAt; + final Duration? startAt; /// Whether or not the video should loop final bool looping; /// When the video playback runs into an error, you can build a custom /// error message. - final Widget Function(BuildContext context, String errorMessage) errorBuilder; + final Widget Function(BuildContext context, String? errorMessage)? + errorBuilder; /// The Aspect Ratio of the Video. Important to get the correct size of the /// video! /// /// Will fallback to fitting within the space allowed. - final double aspectRatio; + final double? aspectRatio; /// The placeholder is displayed underneath the Video before it is initialized /// or played. - final Widget placeholder; + final Widget? placeholder; /// Should the placeholder be shown until play is pressed final bool showPlaceholderUntilPlay; @@ -44,7 +45,7 @@ class BetterPlayerConfiguration { final bool placeholderOnTop; /// A widget which is placed between the video and the controls - final Widget overlay; + final Widget? overlay; /// Defines if the player will start in fullscreen when play is pressed final bool fullScreenByDefault; @@ -53,7 +54,7 @@ class BetterPlayerConfiguration { final bool allowedScreenSleep; /// Defines aspect ratio which will be used in fullscreen - final double fullScreenAspectRatio; + final double? fullScreenAspectRatio; /// Defines the set of allowed device orientations on entering fullscreen final List deviceOrientationsOnFullScreen; @@ -65,10 +66,10 @@ class BetterPlayerConfiguration { final List deviceOrientationsAfterFullScreen; /// Defines a custom RoutePageBuilder for the fullscreen - final BetterPlayerRoutePageBuilder routePageBuilder; + final BetterPlayerRoutePageBuilder? routePageBuilder; /// Defines a event listener where video player events will be send - final Function(BetterPlayerEvent) eventListener; + final Function(BetterPlayerEvent)? eventListener; ///Defines subtitles configuration final BetterPlayerSubtitlesConfiguration subtitlesConfiguration; @@ -85,11 +86,11 @@ class BetterPlayerConfiguration { final double rotation; ///Defines function which will react on player visibility changed - final Function(double visibilityFraction) playerVisibilityChangedBehavior; + final Function(double visibilityFraction)? playerVisibilityChangedBehavior; ///Defines translations used in player. If null, then default english translations ///will be used. - final List translations; + final List? translations; ///Defines if player should auto detect full screen device orientation based ///on aspect ratio of the video. If aspect ratio of the video is < 1 then @@ -149,31 +150,31 @@ class BetterPlayerConfiguration { }); BetterPlayerConfiguration copyWith({ - double aspectRatio, - bool autoPlay, - Duration startAt, - bool looping, - bool fullScreenByDefault, - Widget placeholder, - bool showPlaceholderUntilPlay, - bool placeholderOnTop, - Widget overlay, - bool showControlsOnInitialize, - Widget Function(BuildContext context, String errorMessage) errorBuilder, - bool allowedScreenSleep, - double fullScreenAspectRatio, - List deviceOrientationsOnFullScreen, - List systemOverlaysAfterFullScreen, - List deviceOrientationsAfterFullScreen, - BetterPlayerRoutePageBuilder routePageBuilder, - Function(BetterPlayerEvent) eventListener, - BetterPlayerSubtitlesConfiguration subtitlesConfiguration, - BetterPlayerControlsConfiguration controlsConfiguration, - BoxFit fit, - double rotation, - Function(double visibilityFraction) playerVisibilityChangedBehavior, - List translations, - bool autoDetectFullscreenDeviceOrientation, + double? aspectRatio, + bool? autoPlay, + Duration? startAt, + bool? looping, + bool? fullScreenByDefault, + Widget? placeholder, + bool? showPlaceholderUntilPlay, + bool? placeholderOnTop, + Widget? overlay, + bool? showControlsOnInitialize, + Widget Function(BuildContext context, String? errorMessage)? errorBuilder, + bool? allowedScreenSleep, + double? fullScreenAspectRatio, + List? deviceOrientationsOnFullScreen, + List? systemOverlaysAfterFullScreen, + List? deviceOrientationsAfterFullScreen, + BetterPlayerRoutePageBuilder? routePageBuilder, + Function(BetterPlayerEvent)? eventListener, + BetterPlayerSubtitlesConfiguration? subtitlesConfiguration, + BetterPlayerControlsConfiguration? controlsConfiguration, + BoxFit? fit, + double? rotation, + Function(double visibilityFraction)? playerVisibilityChangedBehavior, + List? translations, + bool? autoDetectFullscreenDeviceOrientation, }) { return BetterPlayerConfiguration( aspectRatio: aspectRatio ?? this.aspectRatio, diff --git a/lib/src/configuration/better_player_controller_event.dart b/lib/src/configuration/better_player_controller_event.dart new file mode 100644 index 000000000..7e337ac46 --- /dev/null +++ b/lib/src/configuration/better_player_controller_event.dart @@ -0,0 +1,17 @@ +///Internal events of BetterPlayerController, used in widgets to update state. +enum BetterPlayerControllerEvent { + ///Fullscreen mode has started. + openFullscreen, + + ///Fullscreen mode has ended. + hideFullscreen, + + ///Subtitles changed. + changeSubtitles, + + ///New data source has been set. + setupDataSource, + + //Video has started. + play +} diff --git a/lib/src/configuration/better_player_controls_configuration.dart b/lib/src/configuration/better_player_controls_configuration.dart index 9847afd3f..a5543cab4 100644 --- a/lib/src/configuration/better_player_controls_configuration.dart +++ b/lib/src/configuration/better_player_controls_configuration.dart @@ -83,11 +83,11 @@ class BetterPlayerControlsConfiguration { final Duration controlsHideTime; ///Parameter used to build custom controls - final Widget Function(BetterPlayerController controller) + final Widget Function(BetterPlayerController controller)? customControlsBuilder; ///Parameter used to change theme of the player - final BetterPlayerTheme playerTheme; + final BetterPlayerTheme? playerTheme; ///Flag used to show/hide controls final bool showControls; @@ -157,7 +157,7 @@ class BetterPlayerControlsConfiguration { final Color loadingColor; ///Widget which can be used instead of default progress - final Widget loadingWidget; + final Widget? loadingWidget; ///Color of the background, when no frame is displayed. final Color backgroundColor; @@ -182,7 +182,7 @@ class BetterPlayerControlsConfiguration { this.skipForwardIcon = Icons.fast_forward, this.enableFullscreen = true, this.enableMute = true, - this.enableProgressText = false, + this.enableProgressText = true, this.enableProgressBar = true, this.enableProgressBarDrag = true, this.enablePlayPause = true, @@ -237,7 +237,6 @@ class BetterPlayerControlsConfiguration { fullscreenEnableIcon: CupertinoIcons.fullscreen, fullscreenDisableIcon: CupertinoIcons.fullscreen_exit, playIcon: CupertinoIcons.play_arrow_solid, - pauseIcon: CupertinoIcons.pause_solid, - enableProgressText: true); + pauseIcon: CupertinoIcons.pause_solid); } } diff --git a/lib/src/configuration/better_player_data_source.dart b/lib/src/configuration/better_player_data_source.dart index 2dd15bc6a..25e9a8a7e 100644 --- a/lib/src/configuration/better_player_data_source.dart +++ b/lib/src/configuration/better_player_data_source.dart @@ -19,59 +19,59 @@ class BetterPlayerDataSource { final String url; ///Subtitles configuration - final List subtitles; + final List? subtitles; ///Flag to determine if current data source is live stream - final bool liveStream; + final bool? liveStream; /// Custom headers for player - final Map headers; + final Map? headers; ///Should player use hls subtitles - final bool useHlsSubtitles; + final bool? useHlsSubtitles; ///Should player use hls tracks - final bool useHlsTracks; + final bool? useHlsTracks; ///Should player use hls audio tracks - final bool useHlsAudioTracks; + final bool? useHlsAudioTracks; ///List of strings that represents tracks names. ///If empty, then better player will choose name based on track parameters - final List hlsTrackNames; + final List? hlsTrackNames; ///Optional, alternative resolutions for non-hls video. Used to setup ///different qualities for video. ///Data should be in given format: ///{"360p": "url", "540p": "url2" } - final Map resolutions; + final Map? resolutions; ///Optional cache configuration, used only for network data sources - final BetterPlayerCacheConfiguration cacheConfiguration; + final BetterPlayerCacheConfiguration? cacheConfiguration; ///List of bytes, used only in memory player - final List bytes; + final List? bytes; ///Configuration of remote controls notification - final BetterPlayerNotificationConfiguration notificationConfiguration; + final BetterPlayerNotificationConfiguration? notificationConfiguration; ///Duration which will be returned instead of original duration - final Duration overriddenDuration; + final Duration? overriddenDuration; ///Video format hint when data source url has not valid extension. - final BetterPlayerVideoFormat videoFormat; + final BetterPlayerVideoFormat? videoFormat; ///Extension of video without dot. Used only in memory data source. - final String videoExtension; + final String? videoExtension; ///Configuration of content protection - final BetterPlayerDrmConfiguration drmConfiguration; + final BetterPlayerDrmConfiguration? drmConfiguration; ///Placeholder widget which will be shown until video load or play. This ///placeholder may be useful if you want to show placeholder before each video ///in playlist. Otherwise, you should use placeholder from /// BetterPlayerConfiguration. - final Widget placeholder; + final Widget? placeholder; BetterPlayerDataSource( this.type, @@ -94,9 +94,8 @@ class BetterPlayerDataSource { this.drmConfiguration, this.placeholder, }) : assert( - ((type == BetterPlayerDataSourceType.network || - type == BetterPlayerDataSourceType.file) && - url != null) || + (type == BetterPlayerDataSourceType.network || + type == BetterPlayerDataSourceType.file) || (type == BetterPlayerDataSourceType.memory && bytes?.isNotEmpty == true), "Url can't be null in network or file data source | bytes can't be null when using memory data source"); @@ -105,20 +104,20 @@ class BetterPlayerDataSource { ///Bytes parameter is not used in this data source. factory BetterPlayerDataSource.network( String url, { - List subtitles, - bool liveStream, - Map headers, - bool useHlsSubtitles, - bool useHlsTracks, - bool useHlsAudioTracks, - Map qualities, - BetterPlayerCacheConfiguration cacheConfiguration, + List? subtitles, + bool? liveStream, + Map? headers, + bool? useHlsSubtitles, + bool? useHlsTracks, + bool? useHlsAudioTracks, + Map? qualities, + BetterPlayerCacheConfiguration? cacheConfiguration, BetterPlayerNotificationConfiguration notificationConfiguration = const BetterPlayerNotificationConfiguration(showNotification: false), - Duration overriddenDuration, - BetterPlayerVideoFormat videoFormat, - BetterPlayerDrmConfiguration drmConfiguration, - Widget placeholder, + Duration? overriddenDuration, + BetterPlayerVideoFormat? videoFormat, + BetterPlayerDrmConfiguration? drmConfiguration, + Widget? placeholder, }) { return BetterPlayerDataSource( BetterPlayerDataSourceType.network, @@ -143,14 +142,14 @@ class BetterPlayerDataSource { ///Bytes parameter is not used in this data source. factory BetterPlayerDataSource.file( String url, { - List subtitles, - bool useHlsSubtitles, - bool useHlsTracks, - Map qualities, - BetterPlayerCacheConfiguration cacheConfiguration, - BetterPlayerNotificationConfiguration notificationConfiguration, - Duration overriddenDuration, - Widget placeholder, + List? subtitles, + bool? useHlsSubtitles, + bool? useHlsTracks, + Map? qualities, + BetterPlayerCacheConfiguration? cacheConfiguration, + BetterPlayerNotificationConfiguration? notificationConfiguration, + Duration? overriddenDuration, + Widget? placeholder, }) { return BetterPlayerDataSource( BetterPlayerDataSourceType.file, @@ -171,15 +170,15 @@ class BetterPlayerDataSource { ///Url parameter is not used in this data source. factory BetterPlayerDataSource.memory( List bytes, { - String videoExtension, - List subtitles, - bool useHlsSubtitles, - bool useHlsTracks, - Map qualities, - BetterPlayerCacheConfiguration cacheConfiguration, - BetterPlayerNotificationConfiguration notificationConfiguration, - Duration overriddenDuration, - Widget placeholder, + String? videoExtension, + List? subtitles, + bool? useHlsSubtitles, + bool? useHlsTracks, + Map? qualities, + BetterPlayerCacheConfiguration? cacheConfiguration, + BetterPlayerNotificationConfiguration? notificationConfiguration, + Duration? overriddenDuration, + Widget? placeholder, }) { return BetterPlayerDataSource( BetterPlayerDataSourceType.memory, @@ -199,24 +198,24 @@ class BetterPlayerDataSource { } BetterPlayerDataSource copyWith({ - BetterPlayerDataSourceType type, - String url, - List bytes, - List subtitles, - bool liveStream, - Map headers, - bool useHlsSubtitles, - bool useHlsTracks, - bool useHlsAudioTracks, - Map resolutions, - BetterPlayerCacheConfiguration cacheConfiguration, - BetterPlayerNotificationConfiguration notificationConfiguration = + BetterPlayerDataSourceType? type, + String? url, + List? bytes, + List? subtitles, + bool? liveStream, + Map? headers, + bool? useHlsSubtitles, + bool? useHlsTracks, + bool? useHlsAudioTracks, + Map? resolutions, + BetterPlayerCacheConfiguration? cacheConfiguration, + BetterPlayerNotificationConfiguration? notificationConfiguration = const BetterPlayerNotificationConfiguration(showNotification: false), - Duration overriddenDuration, - BetterPlayerVideoFormat videoFormat, - String videoExtension, - BetterPlayerDrmConfiguration drmConfiguration, - Widget placeholder, + Duration? overriddenDuration, + BetterPlayerVideoFormat? videoFormat, + String? videoExtension, + BetterPlayerDrmConfiguration? drmConfiguration, + Widget? placeholder, }) { return BetterPlayerDataSource( type ?? this.type, diff --git a/lib/src/configuration/better_player_drm_configuration.dart b/lib/src/configuration/better_player_drm_configuration.dart index f0654db11..43d79ef55 100644 --- a/lib/src/configuration/better_player_drm_configuration.dart +++ b/lib/src/configuration/better_player_drm_configuration.dart @@ -3,16 +3,16 @@ import 'package:better_player/src/configuration/better_player_drm_type.dart'; ///Configuration of DRM used to protect data source class BetterPlayerDrmConfiguration { ///Type of DRM - final BetterPlayerDrmType drmType; + final BetterPlayerDrmType? drmType; ///Parameter used only for token encrypted DRMs - final String token; + final String? token; ///Url of license server, used only for WIDEVINE/PLAYREADY DRM - final String licenseUrl; + final String? licenseUrl; ///Additional headers send with auth request, used only for WIDEVINE DRM - final Map headers; + final Map? headers; BetterPlayerDrmConfiguration({ this.drmType, diff --git a/lib/src/configuration/better_player_event.dart b/lib/src/configuration/better_player_event.dart index 6a94fe9f2..9d22a82ae 100644 --- a/lib/src/configuration/better_player_event.dart +++ b/lib/src/configuration/better_player_event.dart @@ -5,8 +5,7 @@ import 'package:better_player/src/configuration/better_player_event_type.dart'; ///on higher layer. class BetterPlayerEvent { final BetterPlayerEventType betterPlayerEventType; - final Map parameters; + final Map? parameters; - BetterPlayerEvent(this.betterPlayerEventType, {this.parameters}) - : assert(betterPlayerEventType != null); + BetterPlayerEvent(this.betterPlayerEventType, {this.parameters}); } diff --git a/lib/src/configuration/better_player_notification_configuration.dart b/lib/src/configuration/better_player_notification_configuration.dart index 0dff03f12..a9e1c52c0 100644 --- a/lib/src/configuration/better_player_notification_configuration.dart +++ b/lib/src/configuration/better_player_notification_configuration.dart @@ -2,19 +2,19 @@ ///background. class BetterPlayerNotificationConfiguration { ///Is player controls notification enabled - final bool showNotification; + final bool? showNotification; ///Title of the given data source, used in controls notification - final String title; + final String? title; ///Author of the given data source, used in controls notification - final String author; + final String? author; ///Image of the video, used in controls notification - final String imageUrl; + final String? imageUrl; ///Name of the notification channel. Used only in Android. - final String notificationChannelName; + final String? notificationChannelName; const BetterPlayerNotificationConfiguration({ this.showNotification, diff --git a/lib/src/controls/better_player_clickable_widget.dart b/lib/src/controls/better_player_clickable_widget.dart index 6c26d215d..da21124cf 100644 --- a/lib/src/controls/better_player_clickable_widget.dart +++ b/lib/src/controls/better_player_clickable_widget.dart @@ -6,12 +6,10 @@ class BetterPlayerMaterialClickableWidget extends StatelessWidget { final void Function() onTap; const BetterPlayerMaterialClickableWidget({ - Key key, - @required this.onTap, - @required this.child, - }) : assert(onTap != null), - assert(child != null), - super(key: key); + Key? key, + required this.onTap, + required this.child, + }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/src/controls/better_player_controls_state.dart b/lib/src/controls/better_player_controls_state.dart index de34dff53..4f9a616ce 100644 --- a/lib/src/controls/better_player_controls_state.dart +++ b/lib/src/controls/better_player_controls_state.dart @@ -10,6 +10,7 @@ import 'package:better_player/src/hls/better_player_hls_track.dart'; import 'package:better_player/src/video_player/video_player.dart'; // Flutter imports: +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; ///Base class for both material and cupertino controls @@ -18,42 +19,43 @@ abstract class BetterPlayerControlsState ///Min. time of buffered video to hide loading timer (in milliseconds) static const int _bufferingInterval = 20000; - BetterPlayerController get betterPlayerController; + BetterPlayerController? get betterPlayerController; BetterPlayerControlsConfiguration get betterPlayerControlsConfiguration; - VideoPlayerValue get latestValue; + VideoPlayerValue? get latestValue; void cancelAndRestartTimer(); - bool isVideoFinished(VideoPlayerValue videoPlayerValue) { + bool isVideoFinished(VideoPlayerValue? videoPlayerValue) { return videoPlayerValue?.position != null && videoPlayerValue?.duration != null && - videoPlayerValue.position.inMilliseconds != 0 && - videoPlayerValue.duration.inMilliseconds != 0 && - videoPlayerValue.position >= videoPlayerValue.duration; + videoPlayerValue!.position.inMilliseconds != 0 && + videoPlayerValue.duration!.inMilliseconds != 0 && + videoPlayerValue.position >= videoPlayerValue.duration!; } void skipBack() { cancelAndRestartTimer(); final beginning = const Duration().inMilliseconds; - final skip = (latestValue.position - + final skip = (latestValue!.position - Duration( milliseconds: betterPlayerControlsConfiguration .backwardSkipTimeInMilliseconds)) .inMilliseconds; - betterPlayerController.seekTo(Duration(milliseconds: max(skip, beginning))); + betterPlayerController! + .seekTo(Duration(milliseconds: max(skip, beginning))); } void skipForward() { cancelAndRestartTimer(); - final end = latestValue.duration.inMilliseconds; - final skip = (latestValue.position + + final end = latestValue!.duration!.inMilliseconds; + final skip = (latestValue!.position + Duration( milliseconds: betterPlayerControlsConfiguration .forwardSkipTimeInMilliseconds)) .inMilliseconds; - betterPlayerController.seekTo(Duration(milliseconds: min(skip, end))); + betterPlayerController!.seekTo(Duration(milliseconds: min(skip, end))); } void onShowMoreClicked() { @@ -61,7 +63,7 @@ abstract class BetterPlayerControlsState } Widget _buildMoreOptionsList() { - final translations = betterPlayerController.translations; + final translations = betterPlayerController!.translations; return SingleChildScrollView( // ignore: avoid_unnecessary_containers child: Container( @@ -96,14 +98,14 @@ abstract class BetterPlayerControlsState _showAudioTracksSelectionWidget(); }), if (betterPlayerControlsConfiguration - .overflowMenuCustomItems?.isNotEmpty) + .overflowMenuCustomItems.isNotEmpty) ...betterPlayerControlsConfiguration.overflowMenuCustomItems.map( (customItem) => _buildMoreOptionsListRow( customItem.icon, customItem.title, () { Navigator.of(context).pop(); - customItem.onClicked?.call(); + customItem.onClicked.call(); }, ), ) @@ -115,9 +117,6 @@ abstract class BetterPlayerControlsState Widget _buildMoreOptionsListRow( IconData icon, String name, void Function() onTap) { - assert(icon != null, "Icon can't be null"); - assert(name != null, "Name can't be null"); - assert(onTap != null, "OnTap can't be null"); return BetterPlayerMaterialClickableWidget( onTap: onTap, child: Padding( @@ -153,14 +152,13 @@ abstract class BetterPlayerControlsState } Widget _buildSpeedRow(double value) { - assert(value != null, "Value can't be null"); final bool isSelected = - betterPlayerController.videoPlayerController.value.speed == value; + betterPlayerController!.videoPlayerController!.value.speed == value; return BetterPlayerMaterialClickableWidget( onTap: () { Navigator.of(context).pop(); - betterPlayerController.setSpeed(value); + betterPlayerController!.setSpeed(value); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), @@ -178,7 +176,7 @@ abstract class BetterPlayerControlsState } ///Latest value can be null - bool isLoading(VideoPlayerValue latestValue) { + bool isLoading(VideoPlayerValue? latestValue) { if (latestValue != null) { if (!latestValue.isPlaying && latestValue.duration == null) { return true; @@ -186,12 +184,12 @@ abstract class BetterPlayerControlsState final Duration position = latestValue.position; - Duration bufferedEndPosition; - if (latestValue.buffered?.isNotEmpty == true) { + Duration? bufferedEndPosition; + if (latestValue.buffered.isNotEmpty == true) { bufferedEndPosition = latestValue.buffered.last.end; } - if (position != null && bufferedEndPosition != null) { + if (bufferedEndPosition != null) { final difference = bufferedEndPosition - position; if (latestValue.isPlaying && @@ -206,13 +204,12 @@ abstract class BetterPlayerControlsState void _showSubtitlesSelectionWidget() { final subtitles = - List.of(betterPlayerController.betterPlayerSubtitlesSourceList); - final noneSubtitlesElementExists = subtitles?.firstWhere( - (source) => source.type == BetterPlayerSubtitlesSourceType.none, - orElse: () => null) != + List.of(betterPlayerController!.betterPlayerSubtitlesSourceList); + final noneSubtitlesElementExists = subtitles.firstWhereOrNull( + (source) => source.type == BetterPlayerSubtitlesSourceType.none) != null; if (!noneSubtitlesElementExists) { - subtitles?.add(BetterPlayerSubtitlesSource( + subtitles.add(BetterPlayerSubtitlesSource( type: BetterPlayerSubtitlesSourceType.none)); } @@ -221,18 +218,16 @@ abstract class BetterPlayerControlsState } Widget _buildSubtitlesSourceRow(BetterPlayerSubtitlesSource subtitlesSource) { - assert(subtitlesSource != null, "SubtitleSource can't be null"); - final selectedSourceType = - betterPlayerController.betterPlayerSubtitlesSource; + betterPlayerController!.betterPlayerSubtitlesSource; final bool isSelected = (subtitlesSource == selectedSourceType) || (subtitlesSource.type == BetterPlayerSubtitlesSourceType.none && - subtitlesSource?.type == selectedSourceType.type); + subtitlesSource.type == selectedSourceType!.type); return BetterPlayerMaterialClickableWidget( onTap: () { Navigator.of(context).pop(); - betterPlayerController.setupSubtitleSource(subtitlesSource); + betterPlayerController!.setupSubtitleSource(subtitlesSource); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), @@ -241,9 +236,9 @@ abstract class BetterPlayerControlsState const SizedBox(width: 16), Text( subtitlesSource.type == BetterPlayerSubtitlesSourceType.none - ? betterPlayerController.translations.generalNone + ? betterPlayerController!.translations.generalNone : subtitlesSource.name ?? - betterPlayerController.translations.generalDefault, + betterPlayerController!.translations.generalDefault, style: _getOverflowMenuElementTextStyle(isSelected), ), ], @@ -257,16 +252,16 @@ abstract class BetterPlayerControlsState ///Resolution selection is used for normal videos void _showQualitiesSelectionWidget() { final List trackNames = - betterPlayerController.betterPlayerDataSource.hlsTrackNames ?? []; + betterPlayerController!.betterPlayerDataSource!.hlsTrackNames ?? []; final List tracks = - betterPlayerController.betterPlayerTracks; + betterPlayerController!.betterPlayerTracks; final List children = []; for (var index = 0; index < tracks.length; index++) { final track = tracks[index]; - String preferredName; + String? preferredName; if (track.height == 0 && track.width == 0 && track.bitrate == 0) { - preferredName = betterPlayerController.translations.qualityAuto; + preferredName = betterPlayerController!.translations.qualityAuto; } else { preferredName = trackNames.length > index ? trackNames[index] : null; } @@ -274,7 +269,7 @@ abstract class BetterPlayerControlsState } final resolutions = - betterPlayerController.betterPlayerDataSource.resolutions; + betterPlayerController!.betterPlayerDataSource!.resolutions; resolutions?.forEach((key, value) { children.add(_buildResolutionSelectionRow(key, value)); }); @@ -282,26 +277,24 @@ abstract class BetterPlayerControlsState if (children.isEmpty) { children.add( _buildTrackRow(BetterPlayerHlsTrack.defaultTrack(), - betterPlayerController.translations.qualityAuto), + betterPlayerController!.translations.qualityAuto), ); } _showModalBottomSheet(children); } - Widget _buildTrackRow(BetterPlayerHlsTrack track, String preferredName) { - assert(track != null, "Track can't be null"); - + Widget _buildTrackRow(BetterPlayerHlsTrack track, String? preferredName) { final String trackName = preferredName ?? - "${track.width}x${track.height} ${BetterPlayerUtils.formatBitrate(track.bitrate)}"; + "${track.width}x${track.height} ${BetterPlayerUtils.formatBitrate(track.bitrate!)}"; - final selectedTrack = betterPlayerController.betterPlayerTrack; + final selectedTrack = betterPlayerController!.betterPlayerTrack; final bool isSelected = selectedTrack != null && selectedTrack == track; return BetterPlayerMaterialClickableWidget( onTap: () { Navigator.of(context).pop(); - betterPlayerController.setTrack(track); + betterPlayerController!.setTrack(track); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), @@ -320,11 +313,11 @@ abstract class BetterPlayerControlsState Widget _buildResolutionSelectionRow(String name, String url) { final bool isSelected = - url == betterPlayerController.betterPlayerDataSource.url; + url == betterPlayerController!.betterPlayerDataSource!.url; return BetterPlayerMaterialClickableWidget( onTap: () { Navigator.of(context).pop(); - betterPlayerController.setResolution(url); + betterPlayerController!.setResolution(url); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), @@ -342,40 +335,39 @@ abstract class BetterPlayerControlsState } void _showAudioTracksSelectionWidget() { - final List tracks = - betterPlayerController.betterPlayerAudioTracks; + final List? tracks = + betterPlayerController!.betterPlayerAudioTracks; final List children = []; - final BetterPlayerHlsAudioTrack selectedAudioTrack = - betterPlayerController.betterPlayerAudioTrack; + final BetterPlayerHlsAudioTrack? selectedAudioTrack = + betterPlayerController!.betterPlayerAudioTrack; if (tracks != null) { for (var index = 0; index < tracks.length; index++) { - children.add(_buildAudioTrackRow(tracks[index], selectedAudioTrack)); + final bool isSelected = + selectedAudioTrack != null && selectedAudioTrack == tracks[index]; + children.add(_buildAudioTrackRow(tracks[index], isSelected)); } } if (children.isEmpty) { children.add( _buildAudioTrackRow( - BetterPlayerHlsAudioTrack( - label: betterPlayerController.translations.generalDefault, - ), - selectedAudioTrack), + BetterPlayerHlsAudioTrack( + label: betterPlayerController!.translations.generalDefault, + ), + true, + ), ); } _showModalBottomSheet(children); } - Widget _buildAudioTrackRow(BetterPlayerHlsAudioTrack audioTrack, - BetterPlayerHlsAudioTrack selectedAudioTrack) { - assert(audioTrack != null, "Track can't be null"); - - final bool isSelected = - selectedAudioTrack != null && selectedAudioTrack == audioTrack; + Widget _buildAudioTrackRow( + BetterPlayerHlsAudioTrack audioTrack, bool isSelected) { return BetterPlayerMaterialClickableWidget( onTap: () { Navigator.of(context).pop(); - betterPlayerController.setAudioTrack(audioTrack); + betterPlayerController!.setAudioTrack(audioTrack); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), @@ -383,7 +375,7 @@ abstract class BetterPlayerControlsState children: [ const SizedBox(width: 16), Text( - audioTrack.label, + audioTrack.label!, style: _getOverflowMenuElementTextStyle(isSelected), ), ], diff --git a/lib/src/controls/better_player_cupertino_controls.dart b/lib/src/controls/better_player_cupertino_controls.dart index aefe6b243..dac518a99 100644 --- a/lib/src/controls/better_player_cupertino_controls.dart +++ b/lib/src/controls/better_player_cupertino_controls.dart @@ -22,12 +22,10 @@ class BetterPlayerCupertinoControls extends StatefulWidget { final BetterPlayerControlsConfiguration controlsConfiguration; const BetterPlayerCupertinoControls({ - @required this.onControlsVisibilityChanged, - @required this.controlsConfiguration, - Key key, - }) : assert(onControlsVisibilityChanged != null), - assert(controlsConfiguration != null), - super(key: key); + required this.onControlsVisibilityChanged, + required this.controlsConfiguration, + Key? key, + }) : super(key: key); @override State createState() { @@ -38,26 +36,26 @@ class BetterPlayerCupertinoControls extends StatefulWidget { class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState { final marginSize = 5.0; - VideoPlayerValue _latestValue; - double _latestVolume; + VideoPlayerValue? _latestValue; + double? _latestVolume; bool _hideStuff = true; - Timer _hideTimer; - Timer _expandCollapseTimer; - Timer _initTimer; + Timer? _hideTimer; + Timer? _expandCollapseTimer; + Timer? _initTimer; bool _wasLoading = false; - VideoPlayerController _controller; - BetterPlayerController _betterPlayerController; - StreamSubscription _controlsVisibilityStreamSubscription; + VideoPlayerController? _controller; + BetterPlayerController? _betterPlayerController; + StreamSubscription? _controlsVisibilityStreamSubscription; BetterPlayerControlsConfiguration get _controlsConfiguration => widget.controlsConfiguration; @override - VideoPlayerValue get latestValue => _latestValue; + VideoPlayerValue? get latestValue => _latestValue; @override - BetterPlayerController get betterPlayerController => _betterPlayerController; + BetterPlayerController? get betterPlayerController => _betterPlayerController; @override BetterPlayerControlsConfiguration get betterPlayerControlsConfiguration => @@ -77,43 +75,37 @@ class _BetterPlayerCupertinoControlsState final backgroundColor = _controlsConfiguration.controlBarColor; final iconColor = _controlsConfiguration.iconsColor; _betterPlayerController = BetterPlayerController.of(context); - _controller = _betterPlayerController.videoPlayerController; + _controller = _betterPlayerController!.videoPlayerController; final orientation = MediaQuery.of(context).orientation; final barHeight = orientation == Orientation.portrait ? _controlsConfiguration.controlBarHeight : _controlsConfiguration.controlBarHeight + 17; final buttonPadding = orientation == Orientation.portrait ? 16.0 : 24.0; _wasLoading = isLoading(_latestValue); - return MouseRegion( - onHover: (_) { + return GestureDetector( + onTap: () { + _hideStuff + ? cancelAndRestartTimer() + : setState(() { + _hideStuff = true; + }); + }, + onDoubleTap: () { cancelAndRestartTimer(); + _onPlayPause(); }, - child: GestureDetector( - onTap: () { - _hideStuff - ? cancelAndRestartTimer() - : setState(() { - _hideStuff = true; - }); - }, - onDoubleTap: () { - cancelAndRestartTimer(); - _onPlayPause(); - }, - child: AbsorbPointer( - absorbing: _hideStuff, - child: Column( - children: [ - _buildTopBar( - backgroundColor, iconColor, barHeight, buttonPadding), - if (_wasLoading) - Expanded(child: Center(child: _buildLoadingWidget())) - else - _buildHitArea(), - _buildNextVideoWidget(), - _buildBottomBar(backgroundColor, iconColor, barHeight), - ], - ), + child: AbsorbPointer( + absorbing: _hideStuff, + child: Column( + children: [ + _buildTopBar(backgroundColor, iconColor, barHeight, buttonPadding), + if (_wasLoading) + Expanded(child: Center(child: _buildLoadingWidget())) + else + _buildHitArea(), + _buildNextVideoWidget(), + _buildBottomBar(backgroundColor, iconColor, barHeight), + ], ), ), ); @@ -126,7 +118,7 @@ class _BetterPlayerCupertinoControlsState } void _dispose() { - _controller.removeListener(_updateState); + _controller!.removeListener(_updateState); _hideTimer?.cancel(); _expandCollapseTimer?.cancel(); _initTimer?.cancel(); @@ -137,7 +129,7 @@ class _BetterPlayerCupertinoControlsState void didChangeDependencies() { final _oldController = _betterPlayerController; _betterPlayerController = BetterPlayerController.of(context); - _controller = _betterPlayerController.videoPlayerController; + _controller = _betterPlayerController!.videoPlayerController; if (_oldController != _betterPlayerController) { _dispose(); @@ -152,7 +144,7 @@ class _BetterPlayerCupertinoControlsState Color iconColor, double barHeight, ) { - if (!betterPlayerController.controlsEnabled) { + if (!betterPlayerController!.controlsEnabled) { return const SizedBox(); } return AnimatedOpacity( @@ -173,13 +165,13 @@ class _BetterPlayerCupertinoControlsState child: Container( height: barHeight, color: backgroundColor, - child: _betterPlayerController.isLiveStream() + child: _betterPlayerController!.isLiveStream() ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox(width: 8), if (_controlsConfiguration.enablePlayPause) - _buildPlayPause(_controller, iconColor, barHeight) + _buildPlayPause(_controller!, iconColor, barHeight) else const SizedBox(), const SizedBox(width: 8), @@ -193,7 +185,7 @@ class _BetterPlayerCupertinoControlsState else const SizedBox(), if (_controlsConfiguration.enablePlayPause) - _buildPlayPause(_controller, iconColor, barHeight) + _buildPlayPause(_controller!, iconColor, barHeight) else const SizedBox(), if (_controlsConfiguration.enableSkips) @@ -224,7 +216,7 @@ class _BetterPlayerCupertinoControlsState Widget _buildLiveWidget() { return Expanded( child: Text( - _betterPlayerController.translations.controlsLive, + _betterPlayerController!.translations.controlsLive, style: TextStyle( color: _controlsConfiguration.liveTextColor, fontWeight: FontWeight.bold), @@ -256,7 +248,7 @@ class _BetterPlayerCupertinoControlsState color: backgroundColor, child: Center( child: Icon( - _betterPlayerController.isFullScreen + _betterPlayerController!.isFullScreen ? _controlsConfiguration.fullscreenDisableIcon : _controlsConfiguration.fullscreenEnableIcon, color: iconColor, @@ -272,7 +264,7 @@ class _BetterPlayerCupertinoControlsState Expanded _buildHitArea() { return Expanded( child: GestureDetector( - onTap: _latestValue != null && _latestValue.isPlaying + onTap: _latestValue != null && _latestValue!.isPlaying ? () { if (_hideStuff == true) { cancelAndRestartTimer(); @@ -299,7 +291,7 @@ class _BetterPlayerCupertinoControlsState } GestureDetector _buildMoreButton( - VideoPlayerController controller, + VideoPlayerController? controller, Color backgroundColor, Color iconColor, double barHeight, @@ -336,7 +328,7 @@ class _BetterPlayerCupertinoControlsState } GestureDetector _buildMuteButton( - VideoPlayerController controller, + VideoPlayerController? controller, Color backgroundColor, Color iconColor, double barHeight, @@ -346,10 +338,10 @@ class _BetterPlayerCupertinoControlsState onTap: () { cancelAndRestartTimer(); - if (_latestValue.volume == 0) { - controller.setVolume(_latestVolume ?? 0.5); + if (_latestValue!.volume == 0) { + controller!.setVolume(_latestVolume ?? 0.5); } else { - _latestVolume = controller.value.volume; + _latestVolume = controller!.value.volume; controller.setVolume(0.0); } }, @@ -368,7 +360,7 @@ class _BetterPlayerCupertinoControlsState horizontal: buttonPadding, ), child: Icon( - (_latestValue != null && _latestValue.volume > 0) + (_latestValue != null && _latestValue!.volume > 0) ? _controlsConfiguration.muteIcon : _controlsConfiguration.unMuteIcon, color: iconColor, @@ -404,7 +396,7 @@ class _BetterPlayerCupertinoControlsState Widget _buildPosition() { final position = - _latestValue != null ? _latestValue.position : const Duration(); + _latestValue != null ? _latestValue!.position : const Duration(); return Padding( padding: const EdgeInsets.only(right: 12.0), @@ -419,8 +411,8 @@ class _BetterPlayerCupertinoControlsState } Widget _buildRemaining() { - final position = _latestValue != null && _latestValue.duration != null - ? _latestValue.duration - _latestValue.position + final position = _latestValue != null && _latestValue!.duration != null + ? _latestValue!.duration! - _latestValue!.position : const Duration(); return Padding( @@ -471,7 +463,7 @@ class _BetterPlayerCupertinoControlsState double barHeight, double buttonPadding, ) { - if (!betterPlayerController.controlsEnabled) { + if (!betterPlayerController!.controlsEnabled) { return const SizedBox(); } @@ -516,14 +508,14 @@ class _BetterPlayerCupertinoControlsState } Widget _buildNextVideoWidget() { - return StreamBuilder( - stream: _betterPlayerController.nextVideoTimeStreamController.stream, + return StreamBuilder( + stream: _betterPlayerController!.nextVideoTimeStreamController.stream, builder: (context, snapshot) { final time = snapshot.data; if (time != null && time > 0) { return InkWell( onTap: () { - _betterPlayerController.playNextVideo(); + _betterPlayerController!.playNextVideo(); }, child: Align( alignment: Alignment.bottomRight, @@ -536,7 +528,7 @@ class _BetterPlayerCupertinoControlsState child: Padding( padding: const EdgeInsets.all(12), child: Text( - "${_betterPlayerController.translations.controlsNextVideoIn} $time ...", + "${_betterPlayerController!.translations.controlsNextVideoIn} $time ...", style: const TextStyle(color: Colors.white), ), ), @@ -560,12 +552,12 @@ class _BetterPlayerCupertinoControlsState } Future _initialize() async { - _controller.addListener(_updateState); + _controller!.addListener(_updateState); _updateState(); - if ((_controller.value != null && _controller.value.isPlaying) || - _betterPlayerController.betterPlayerConfiguration.autoPlay) { + if ((_controller!.value.isPlaying) || + _betterPlayerController!.betterPlayerConfiguration.autoPlay) { _startHideTimer(); } @@ -577,7 +569,7 @@ class _BetterPlayerCupertinoControlsState }); } _controlsVisibilityStreamSubscription = - _betterPlayerController.controlsVisibilityStream.listen((state) { + _betterPlayerController!.controlsVisibilityStream.listen((state) { setState(() { _hideStuff = !state; }); @@ -591,7 +583,7 @@ class _BetterPlayerCupertinoControlsState setState(() { _hideStuff = true; - _betterPlayerController.toggleFullScreen(); + _betterPlayerController!.toggleFullScreen(); _expandCollapseTimer = Timer(_controlsConfiguration.controlsHideTime, () { setState(() { cancelAndRestartTimer(); @@ -628,35 +620,35 @@ class _BetterPlayerCupertinoControlsState bool isFinished = false; if (_latestValue?.position != null && _latestValue?.duration != null) { - isFinished = _latestValue.position >= _latestValue.duration; + isFinished = _latestValue!.position >= _latestValue!.duration!; } setState(() { - if (_controller.value.isPlaying) { + if (_controller!.value.isPlaying) { _hideStuff = false; _hideTimer?.cancel(); - _betterPlayerController.pause(); + _betterPlayerController!.pause(); } else { cancelAndRestartTimer(); - if (!_controller.value.initialized) { - if (_betterPlayerController.betterPlayerDataSource?.liveStream == + if (!_controller!.value.initialized) { + if (_betterPlayerController!.betterPlayerDataSource?.liveStream == true) { - _betterPlayerController.play(); - _betterPlayerController.cancelNextVideoTimer(); + _betterPlayerController!.play(); + _betterPlayerController!.cancelNextVideoTimer(); } } else { if (isFinished) { - _betterPlayerController.seekTo(const Duration()); + _betterPlayerController!.seekTo(const Duration()); } - _betterPlayerController.play(); - _betterPlayerController.cancelNextVideoTimer(); + _betterPlayerController!.play(); + _betterPlayerController!.cancelNextVideoTimer(); } } }); } void _startHideTimer() { - if (_betterPlayerController.controlsAlwaysVisible) { + if (_betterPlayerController!.controlsAlwaysVisible) { return; } _hideTimer = Timer(const Duration(seconds: 3), () { @@ -669,11 +661,11 @@ class _BetterPlayerCupertinoControlsState void _updateState() { if (mounted) { if (!_hideStuff || - isVideoFinished(_controller.value) || + isVideoFinished(_controller!.value) || _wasLoading || - isLoading(_controller.value)) { + isLoading(_controller!.value)) { setState(() { - _latestValue = _controller.value; + _latestValue = _controller!.value; if (isVideoFinished(_latestValue)) { _hideStuff = false; } @@ -683,16 +675,18 @@ class _BetterPlayerCupertinoControlsState } void _onPlayerHide() { - _betterPlayerController.toggleControlsVisibility(!_hideStuff); + _betterPlayerController!.toggleControlsVisibility(!_hideStuff); widget.onControlsVisibilityChanged(!_hideStuff); } Widget _buildErrorWidget() { final errorBuilder = - _betterPlayerController.betterPlayerConfiguration.errorBuilder; + _betterPlayerController!.betterPlayerConfiguration.errorBuilder; if (errorBuilder != null) { - return errorBuilder(context, - _betterPlayerController.videoPlayerController.value.errorDescription); + return errorBuilder( + context, + _betterPlayerController! + .videoPlayerController!.value.errorDescription); } else { final textStyle = TextStyle(color: _controlsConfiguration.textColor); return Center( @@ -705,16 +699,16 @@ class _BetterPlayerCupertinoControlsState size: 42, ), Text( - _betterPlayerController.translations.generalDefaultError, + _betterPlayerController!.translations.generalDefaultError, style: textStyle, ), if (_controlsConfiguration.enableRetry) TextButton( onPressed: () { - _betterPlayerController.retryDataSource(); + _betterPlayerController!.retryDataSource(); }, child: Text( - _betterPlayerController.translations.generalRetry, + _betterPlayerController!.translations.generalRetry, style: textStyle.copyWith(fontWeight: FontWeight.bold), ), ) @@ -724,30 +718,29 @@ class _BetterPlayerCupertinoControlsState } } - Widget _buildLoadingWidget() { + Widget? _buildLoadingWidget() { if (_controlsConfiguration.loadingWidget != null) { return _controlsConfiguration.loadingWidget; } return CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation( - _controlsConfiguration.loadingColor ?? - _controlsConfiguration.controlBarColor), + valueColor: + AlwaysStoppedAnimation(_controlsConfiguration.loadingColor), ); } Widget _buildPipButton(Color backgroundColor, Color iconColor, double barHeight, double buttonPadding) { return FutureBuilder( - future: _betterPlayerController.isPictureInPictureSupported(), + future: _betterPlayerController!.isPictureInPictureSupported(), builder: (context, snapshot) { final isPipSupported = snapshot.data ?? false; if (isPipSupported && - _betterPlayerController.betterPlayerGlobalKey != null) { + _betterPlayerController!.betterPlayerGlobalKey != null) { return GestureDetector( onTap: () { - betterPlayerController.enablePictureInPicture( - betterPlayerController.betterPlayerGlobalKey); + betterPlayerController!.enablePictureInPicture( + betterPlayerController!.betterPlayerGlobalKey!); }, child: AnimatedOpacity( opacity: _hideStuff ? 0.0 : 1.0, diff --git a/lib/src/controls/better_player_cupertino_progress_bar.dart b/lib/src/controls/better_player_cupertino_progress_bar.dart index 06fc065b2..41c72e190 100644 --- a/lib/src/controls/better_player_cupertino_progress_bar.dart +++ b/lib/src/controls/better_player_cupertino_progress_bar.dart @@ -11,20 +11,20 @@ class BetterPlayerCupertinoVideoProgressBar extends StatefulWidget { BetterPlayerCupertinoVideoProgressBar( this.controller, this.betterPlayerController, { - BetterPlayerProgressColors colors, + BetterPlayerProgressColors? colors, this.onDragEnd, this.onDragStart, this.onDragUpdate, - Key key, + Key? key, }) : colors = colors ?? BetterPlayerProgressColors(), super(key: key); - final VideoPlayerController controller; - final BetterPlayerController betterPlayerController; + final VideoPlayerController? controller; + final BetterPlayerController? betterPlayerController; final BetterPlayerProgressColors colors; - final Function() onDragStart; - final Function() onDragEnd; - final Function() onDragUpdate; + final Function()? onDragStart; + final Function()? onDragEnd; + final Function()? onDragUpdate; @override _VideoProgressBarState createState() { @@ -40,63 +40,66 @@ class _VideoProgressBarState }; } - VoidCallback listener; + late VoidCallback listener; bool _controllerWasPlaying = false; - VideoPlayerController get controller => widget.controller; + VideoPlayerController? get controller => widget.controller; - BetterPlayerController get betterPlayerController => + BetterPlayerController? get betterPlayerController => widget.betterPlayerController; @override void initState() { super.initState(); - controller.addListener(listener); + controller!.addListener(listener); } @override void deactivate() { - controller.removeListener(listener); + controller!.removeListener(listener); super.deactivate(); } @override Widget build(BuildContext context) { void seekToRelativePosition(Offset globalPosition) { - final box = context.findRenderObject() as RenderBox; - final Offset tapPos = box.globalToLocal(globalPosition); - final double relative = tapPos.dx / box.size.width; - if (relative > 0) { - final Duration position = controller.value.duration * relative; - controller.seekTo(position); + final RenderObject? renderObject = context.findRenderObject(); + if (renderObject != null) { + final box = renderObject as RenderBox; + final Offset tapPos = box.globalToLocal(globalPosition); + final double relative = tapPos.dx / box.size.width; + if (relative > 0) { + final Duration position = controller!.value.duration! * relative; + controller!.seekTo(position); + } } } - final bool enableProgressBarDrag = betterPlayerController + final bool enableProgressBarDrag = betterPlayerController! .betterPlayerConfiguration.controlsConfiguration.enableProgressBarDrag; return GestureDetector( onHorizontalDragStart: (DragStartDetails details) { - if (!controller.value.initialized || !enableProgressBarDrag) { + if (!controller!.value.initialized || !enableProgressBarDrag) { return; } - _controllerWasPlaying = controller.value.isPlaying; + _controllerWasPlaying = controller!.value.isPlaying; if (_controllerWasPlaying) { - controller.pause(); + controller!.pause(); } if (widget.onDragStart != null) { - widget.onDragStart(); + widget.onDragStart!(); } }, onHorizontalDragUpdate: (DragUpdateDetails details) { - if (!controller.value.initialized || !enableProgressBarDrag) { + if (!controller!.value.initialized || !enableProgressBarDrag) { return; } seekToRelativePosition(details.globalPosition); if (widget.onDragUpdate != null) { - widget.onDragUpdate(); + widget.onDragUpdate!(); } }, onHorizontalDragEnd: (DragEndDetails details) { @@ -105,14 +108,14 @@ class _VideoProgressBarState } if (_controllerWasPlaying) { - controller.play(); + controller!.play(); } if (widget.onDragEnd != null) { - widget.onDragEnd(); + widget.onDragEnd!(); } }, onTapDown: (TapDownDetails details) { - if (!controller.value.initialized || !enableProgressBarDrag) { + if (!controller!.value.initialized || !enableProgressBarDrag) { return; } @@ -125,7 +128,7 @@ class _VideoProgressBarState color: Colors.transparent, child: CustomPaint( painter: _ProgressBarPainter( - controller.value, + controller!.value, widget.colors, ), ), @@ -166,12 +169,12 @@ class _ProgressBarPainter extends CustomPainter { return; } final double playedPartPercent = - value.position.inMilliseconds / value.duration.inMilliseconds; + value.position.inMilliseconds / value.duration!.inMilliseconds; final double playedPart = playedPartPercent > 1 ? size.width : playedPartPercent * size.width; for (final DurationRange range in value.buffered) { - final double start = range.startFraction(value.duration) * size.width; - final double end = range.endFraction(value.duration) * size.width; + final double start = range.startFraction(value.duration!) * size.width; + final double end = range.endFraction(value.duration!) * size.width; canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromPoints( diff --git a/lib/src/controls/better_player_material_controls.dart b/lib/src/controls/better_player_material_controls.dart index 8beef3e1b..1ed1144c8 100644 --- a/lib/src/controls/better_player_material_controls.dart +++ b/lib/src/controls/better_player_material_controls.dart @@ -24,12 +24,10 @@ class BetterPlayerMaterialControls extends StatefulWidget { final BetterPlayerControlsConfiguration controlsConfiguration; const BetterPlayerMaterialControls({ - Key key, - @required this.onControlsVisibilityChanged, - @required this.controlsConfiguration, - }) : assert(onControlsVisibilityChanged != null), - assert(controlsConfiguration != null), - super(key: key); + Key? key, + required this.onControlsVisibilityChanged, + required this.controlsConfiguration, + }) : super(key: key); @override State createState() { @@ -39,26 +37,26 @@ class BetterPlayerMaterialControls extends StatefulWidget { class _BetterPlayerMaterialControlsState extends BetterPlayerControlsState { - VideoPlayerValue _latestValue; - double _latestVolume; + VideoPlayerValue? _latestValue; + double? _latestVolume; bool _hideStuff = true; - Timer _hideTimer; - Timer _initTimer; - Timer _showAfterExpandCollapseTimer; + Timer? _hideTimer; + Timer? _initTimer; + Timer? _showAfterExpandCollapseTimer; bool _displayTapped = false; bool _wasLoading = false; - VideoPlayerController _controller; - BetterPlayerController _betterPlayerController; - StreamSubscription _controlsVisibilityStreamSubscription; + VideoPlayerController? _controller; + BetterPlayerController? _betterPlayerController; + StreamSubscription? _controlsVisibilityStreamSubscription; BetterPlayerControlsConfiguration get _controlsConfiguration => widget.controlsConfiguration; @override - VideoPlayerValue get latestValue => _latestValue; + VideoPlayerValue? get latestValue => _latestValue; @override - BetterPlayerController get betterPlayerController => _betterPlayerController; + BetterPlayerController? get betterPlayerController => _betterPlayerController; @override BetterPlayerControlsConfiguration get betterPlayerControlsConfiguration => @@ -73,34 +71,29 @@ class _BetterPlayerMaterialControlsState child: _buildErrorWidget(), ); } - return MouseRegion( - onHover: (_) { + return GestureDetector( + onTap: () { + _hideStuff + ? cancelAndRestartTimer() + : setState(() { + _hideStuff = true; + }); + }, + onDoubleTap: () { cancelAndRestartTimer(); + _onPlayPause(); }, - child: GestureDetector( - onTap: () { - _hideStuff - ? cancelAndRestartTimer() - : setState(() { - _hideStuff = true; - }); - }, - onDoubleTap: () { - cancelAndRestartTimer(); - _onPlayPause(); - }, - child: AbsorbPointer( - absorbing: _hideStuff, - child: Column( - children: [ - _buildTopBar(), - if (_wasLoading) - Expanded(child: Center(child: _buildLoadingWidget())) - else - _buildHitArea(), - _buildBottomBar(), - ], - ), + child: AbsorbPointer( + absorbing: _hideStuff, + child: Column( + children: [ + _buildTopBar(), + if (_wasLoading) + Expanded(child: Center(child: _buildLoadingWidget())) + else + _buildHitArea(), + _buildBottomBar(), + ], ), ), ); @@ -124,8 +117,8 @@ class _BetterPlayerMaterialControlsState void didChangeDependencies() { final _oldController = _betterPlayerController; _betterPlayerController = BetterPlayerController.of(context); - _controller = _betterPlayerController.videoPlayerController; - _latestValue = _controller.value; + _controller = _betterPlayerController!.videoPlayerController; + _latestValue = _controller!.value; if (_oldController != _betterPlayerController) { _dispose(); @@ -137,10 +130,12 @@ class _BetterPlayerMaterialControlsState Widget _buildErrorWidget() { final errorBuilder = - _betterPlayerController.betterPlayerConfiguration.errorBuilder; + _betterPlayerController!.betterPlayerConfiguration.errorBuilder; if (errorBuilder != null) { - return errorBuilder(context, - _betterPlayerController.videoPlayerController.value.errorDescription); + return errorBuilder( + context, + _betterPlayerController! + .videoPlayerController!.value.errorDescription); } else { final textStyle = TextStyle(color: _controlsConfiguration.textColor); return Center( @@ -153,16 +148,16 @@ class _BetterPlayerMaterialControlsState size: 42, ), Text( - _betterPlayerController.translations.generalDefaultError, + _betterPlayerController!.translations.generalDefaultError, style: textStyle, ), if (_controlsConfiguration.enableRetry) TextButton( onPressed: () { - _betterPlayerController.retryDataSource(); + _betterPlayerController!.retryDataSource(); }, child: Text( - _betterPlayerController.translations.generalRetry, + _betterPlayerController!.translations.generalRetry, style: textStyle.copyWith(fontWeight: FontWeight.bold), ), ) @@ -173,7 +168,7 @@ class _BetterPlayerMaterialControlsState } Widget _buildTopBar() { - if (!betterPlayerController.controlsEnabled) { + if (!betterPlayerController!.controlsEnabled) { return const SizedBox(); } @@ -205,8 +200,8 @@ class _BetterPlayerMaterialControlsState Widget _buildPipButton() { return BetterPlayerMaterialClickableWidget( onTap: () { - betterPlayerController.enablePictureInPicture( - betterPlayerController.betterPlayerGlobalKey); + betterPlayerController!.enablePictureInPicture( + betterPlayerController!.betterPlayerGlobalKey!); }, child: Padding( padding: const EdgeInsets.all(8), @@ -221,11 +216,11 @@ class _BetterPlayerMaterialControlsState Widget _buildPipButtonWrapperWidget( bool hideStuff, void Function() onPlayerHide) { return FutureBuilder( - future: betterPlayerController.isPictureInPictureSupported(), + future: betterPlayerController!.isPictureInPictureSupported(), builder: (context, snapshot) { final bool isPipSupported = snapshot.data ?? false; if (isPipSupported && - _betterPlayerController.betterPlayerGlobalKey != null) { + _betterPlayerController!.betterPlayerGlobalKey != null) { return AnimatedOpacity( opacity: hideStuff ? 0.0 : 1.0, duration: betterPlayerControlsConfiguration.controlsHideTime, @@ -263,7 +258,7 @@ class _BetterPlayerMaterialControlsState } Widget _buildBottomBar() { - if (!betterPlayerController.controlsEnabled) { + if (!betterPlayerController!.controlsEnabled) { return const SizedBox(); } return AnimatedOpacity( @@ -276,16 +271,16 @@ class _BetterPlayerMaterialControlsState child: Row( children: [ if (_controlsConfiguration.enablePlayPause) - _buildPlayPause(_controller) + _buildPlayPause(_controller!) else const SizedBox(), - if (_betterPlayerController.isLiveStream()) + if (_betterPlayerController!.isLiveStream()) _buildLiveWidget() else _controlsConfiguration.enableProgressText ? _buildPosition() : const SizedBox(), - if (_betterPlayerController.isLiveStream()) + if (_betterPlayerController!.isLiveStream()) const SizedBox() else _controlsConfiguration.enableProgressBar @@ -308,7 +303,7 @@ class _BetterPlayerMaterialControlsState Widget _buildLiveWidget() { return Expanded( child: Text( - _betterPlayerController.translations.controlsLive, + _betterPlayerController!.translations.controlsLive, style: TextStyle( color: _controlsConfiguration.liveTextColor, fontWeight: FontWeight.bold), @@ -328,7 +323,7 @@ class _BetterPlayerMaterialControlsState padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Center( child: Icon( - _betterPlayerController.isFullScreen + _betterPlayerController!.isFullScreen ? _controlsConfiguration.fullscreenDisableIcon : _controlsConfiguration.fullscreenEnableIcon, color: _controlsConfiguration.iconsColor, @@ -340,7 +335,7 @@ class _BetterPlayerMaterialControlsState } Widget _buildHitArea() { - if (!betterPlayerController.controlsEnabled) { + if (!betterPlayerController!.controlsEnabled) { return const SizedBox(); } return Expanded( @@ -383,7 +378,7 @@ class _BetterPlayerMaterialControlsState } Widget _buildHitAreaClickableButton( - {Widget icon, void Function() onClicked}) { + {Widget? icon, required void Function() onClicked}) { return BetterPlayerMaterialClickableWidget( onTap: onClicked, child: Align( @@ -395,7 +390,7 @@ class _BetterPlayerMaterialControlsState child: Padding( padding: const EdgeInsets.all(12), child: Stack( - children: [icon], + children: [icon!], ), ), ), @@ -438,7 +433,7 @@ class _BetterPlayerMaterialControlsState color: _controlsConfiguration.iconsColor, ), onClicked: () { - if (_latestValue != null && _latestValue.isPlaying) { + if (_latestValue != null && _latestValue!.isPlaying) { if (_displayTapped) { setState(() { _hideStuff = true; @@ -458,14 +453,14 @@ class _BetterPlayerMaterialControlsState } Widget _buildNextVideoWidget() { - return StreamBuilder( - stream: _betterPlayerController.nextVideoTimeStreamController.stream, + return StreamBuilder( + stream: _betterPlayerController!.nextVideoTimeStreamController.stream, builder: (context, snapshot) { final time = snapshot.data; if (time != null && time > 0) { return BetterPlayerMaterialClickableWidget( onTap: () { - _betterPlayerController.playNextVideo(); + _betterPlayerController!.playNextVideo(); }, child: Align( alignment: Alignment.bottomRight, @@ -478,7 +473,7 @@ class _BetterPlayerMaterialControlsState child: Padding( padding: const EdgeInsets.all(12), child: Text( - "${_betterPlayerController.translations.controlsNextVideoIn} $time ...", + "${_betterPlayerController!.translations.controlsNextVideoIn} $time ...", style: const TextStyle(color: Colors.white), ), ), @@ -493,16 +488,16 @@ class _BetterPlayerMaterialControlsState } Widget _buildMuteButton( - VideoPlayerController controller, + VideoPlayerController? controller, ) { return BetterPlayerMaterialClickableWidget( onTap: () { cancelAndRestartTimer(); - if (_latestValue.volume == 0) { - _betterPlayerController.setVolume(_latestVolume ?? 0.5); + if (_latestValue!.volume == 0) { + _betterPlayerController!.setVolume(_latestVolume ?? 0.5); } else { - _latestVolume = controller.value.volume; - _betterPlayerController.setVolume(0.0); + _latestVolume = controller!.value.volume; + _betterPlayerController!.setVolume(0.0); } }, child: AnimatedOpacity( @@ -513,7 +508,7 @@ class _BetterPlayerMaterialControlsState height: _controlsConfiguration.controlBarHeight, padding: const EdgeInsets.symmetric(horizontal: 8), child: Icon( - (_latestValue != null && _latestValue.volume > 0) + (_latestValue != null && _latestValue!.volume > 0) ? _controlsConfiguration.muteIcon : _controlsConfiguration.unMuteIcon, color: _controlsConfiguration.iconsColor, @@ -542,11 +537,10 @@ class _BetterPlayerMaterialControlsState } Widget _buildPosition() { - final position = _latestValue != null && _latestValue.position != null - ? _latestValue.position - : Duration.zero; - final duration = _latestValue != null && _latestValue.duration != null - ? _latestValue.duration + final position = + _latestValue != null ? _latestValue!.position : Duration.zero; + final duration = _latestValue != null && _latestValue!.duration != null + ? _latestValue!.duration! : Duration.zero; return Padding( @@ -574,12 +568,12 @@ class _BetterPlayerMaterialControlsState } Future _initialize() async { - _controller.addListener(_updateState); + _controller!.addListener(_updateState); _updateState(); - if ((_controller.value != null && _controller.value.isPlaying) || - _betterPlayerController.betterPlayerConfiguration.autoPlay) { + if ((_controller!.value.isPlaying) || + _betterPlayerController!.betterPlayerConfiguration.autoPlay) { _startHideTimer(); } @@ -592,7 +586,7 @@ class _BetterPlayerMaterialControlsState } _controlsVisibilityStreamSubscription = - _betterPlayerController.controlsVisibilityStream.listen((state) { + _betterPlayerController!.controlsVisibilityStream.listen((state) { setState(() { _hideStuff = !state; }); @@ -606,7 +600,7 @@ class _BetterPlayerMaterialControlsState setState(() { _hideStuff = true; - _betterPlayerController.toggleFullScreen(); + _betterPlayerController!.toggleFullScreen(); _showAfterExpandCollapseTimer = Timer(_controlsConfiguration.controlsHideTime, () { setState(() { @@ -620,31 +614,31 @@ class _BetterPlayerMaterialControlsState bool isFinished = false; if (_latestValue?.position != null && _latestValue?.duration != null) { - isFinished = _latestValue.position >= _latestValue.duration; + isFinished = _latestValue!.position >= _latestValue!.duration!; } setState(() { - if (_controller.value.isPlaying) { + if (_controller!.value.isPlaying) { _hideStuff = false; _hideTimer?.cancel(); - _betterPlayerController.pause(); + _betterPlayerController!.pause(); } else { cancelAndRestartTimer(); - if (!_controller.value.initialized) { + if (!_controller!.value.initialized) { } else { if (isFinished) { - _betterPlayerController.seekTo(const Duration()); + _betterPlayerController!.seekTo(const Duration()); } - _betterPlayerController.play(); - _betterPlayerController.cancelNextVideoTimer(); + _betterPlayerController!.play(); + _betterPlayerController!.cancelNextVideoTimer(); } } }); } void _startHideTimer() { - if (_betterPlayerController.controlsAlwaysVisible) { + if (_betterPlayerController!.controlsAlwaysVisible) { return; } _hideTimer = Timer(const Duration(seconds: 3), () { @@ -657,11 +651,11 @@ class _BetterPlayerMaterialControlsState void _updateState() { if (mounted) { if (!_hideStuff || - isVideoFinished(_controller.value) || + isVideoFinished(_controller!.value) || _wasLoading || - isLoading(_controller.value)) { + isLoading(_controller!.value)) { setState(() { - _latestValue = _controller.value; + _latestValue = _controller!.value; if (isVideoFinished(_latestValue)) { _hideStuff = false; } @@ -695,19 +689,18 @@ class _BetterPlayerMaterialControlsState } void _onPlayerHide() { - _betterPlayerController.toggleControlsVisibility(!_hideStuff); + _betterPlayerController!.toggleControlsVisibility(!_hideStuff); widget.onControlsVisibilityChanged(!_hideStuff); } - Widget _buildLoadingWidget() { + Widget? _buildLoadingWidget() { if (_controlsConfiguration.loadingWidget != null) { return _controlsConfiguration.loadingWidget; } return CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation( - _controlsConfiguration.loadingColor ?? - _controlsConfiguration.controlBarColor), + valueColor: + AlwaysStoppedAnimation(_controlsConfiguration.loadingColor), ); } } diff --git a/lib/src/controls/better_player_material_progress_bar.dart b/lib/src/controls/better_player_material_progress_bar.dart index c45206354..f035fd1d9 100644 --- a/lib/src/controls/better_player_material_progress_bar.dart +++ b/lib/src/controls/better_player_material_progress_bar.dart @@ -11,20 +11,20 @@ class BetterPlayerMaterialVideoProgressBar extends StatefulWidget { BetterPlayerMaterialVideoProgressBar( this.controller, this.betterPlayerController, { - BetterPlayerProgressColors colors, + BetterPlayerProgressColors? colors, this.onDragEnd, this.onDragStart, this.onDragUpdate, - Key key, + Key? key, }) : colors = colors ?? BetterPlayerProgressColors(), super(key: key); - final VideoPlayerController controller; - final BetterPlayerController betterPlayerController; + final VideoPlayerController? controller; + final BetterPlayerController? betterPlayerController; final BetterPlayerProgressColors colors; - final Function() onDragStart; - final Function() onDragEnd; - final Function() onDragUpdate; + final Function()? onDragStart; + final Function()? onDragEnd; + final Function()? onDragUpdate; @override _VideoProgressBarState createState() { @@ -40,65 +40,68 @@ class _VideoProgressBarState }; } - VoidCallback listener; + late VoidCallback listener; bool _controllerWasPlaying = false; - VideoPlayerController get controller => widget.controller; + VideoPlayerController? get controller => widget.controller; - BetterPlayerController get betterPlayerController => + BetterPlayerController? get betterPlayerController => widget.betterPlayerController; @override void initState() { super.initState(); - controller.addListener(listener); + controller!.addListener(listener); } @override void deactivate() { - controller.removeListener(listener); + controller!.removeListener(listener); super.deactivate(); } @override Widget build(BuildContext context) { void seekToRelativePosition(Offset globalPosition) { - final box = context.findRenderObject() as RenderBox; - final Offset tapPos = box.globalToLocal(globalPosition); - final double relative = tapPos.dx / box.size.width; - if (relative > 0) { - final Duration position = controller.value.duration * relative; - betterPlayerController.seekTo(position); + final RenderObject? renderObject = context.findRenderObject(); + if (renderObject != null) { + final box = renderObject as RenderBox; + final Offset tapPos = box.globalToLocal(globalPosition); + final double relative = tapPos.dx / box.size.width; + if (relative > 0) { + final Duration position = controller!.value.duration! * relative; + betterPlayerController!.seekTo(position); + } } } - final bool enableProgressBarDrag = betterPlayerController + final bool enableProgressBarDrag = betterPlayerController! .betterPlayerConfiguration.controlsConfiguration.enableProgressBarDrag; return GestureDetector( onHorizontalDragStart: (DragStartDetails details) { - if (!controller.value.initialized || !enableProgressBarDrag) { + if (!controller!.value.initialized || !enableProgressBarDrag) { return; } - _controllerWasPlaying = controller.value.isPlaying; + _controllerWasPlaying = controller!.value.isPlaying; if (_controllerWasPlaying) { - controller.pause(); + controller!.pause(); } if (widget.onDragStart != null) { - widget.onDragStart(); + widget.onDragStart!(); } }, onHorizontalDragUpdate: (DragUpdateDetails details) { - if (!controller.value.initialized || !enableProgressBarDrag) { + if (!controller!.value.initialized || !enableProgressBarDrag) { return; } seekToRelativePosition(details.globalPosition); if (widget.onDragUpdate != null) { - widget.onDragUpdate(); + widget.onDragUpdate!(); } }, onHorizontalDragEnd: (DragEndDetails details) { @@ -107,15 +110,15 @@ class _VideoProgressBarState } if (_controllerWasPlaying) { - controller.play(); + controller!.play(); } if (widget.onDragEnd != null) { - widget.onDragEnd(); + widget.onDragEnd!(); } }, onTapDown: (TapDownDetails details) { - if (!controller.value.initialized || !enableProgressBarDrag) { + if (!controller!.value.initialized || !enableProgressBarDrag) { return; } seekToRelativePosition(details.globalPosition); @@ -127,7 +130,7 @@ class _VideoProgressBarState color: Colors.transparent, child: CustomPaint( painter: _ProgressBarPainter( - controller.value, + controller!.value, widget.colors, ), ), @@ -166,12 +169,12 @@ class _ProgressBarPainter extends CustomPainter { return; } final double playedPartPercent = - value.position.inMilliseconds / value.duration.inMilliseconds; + value.position.inMilliseconds / value.duration!.inMilliseconds; final double playedPart = playedPartPercent > 1 ? size.width : playedPartPercent * size.width; for (final DurationRange range in value.buffered) { - final double start = range.startFraction(value.duration) * size.width; - final double end = range.endFraction(value.duration) * size.width; + final double start = range.startFraction(value.duration!) * size.width; + final double end = range.endFraction(value.duration!) * size.width; canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromPoints( diff --git a/lib/src/controls/better_player_overflow_menu_item.dart b/lib/src/controls/better_player_overflow_menu_item.dart index e91c94f3f..9148dbfe2 100644 --- a/lib/src/controls/better_player_overflow_menu_item.dart +++ b/lib/src/controls/better_player_overflow_menu_item.dart @@ -10,9 +10,7 @@ class BetterPlayerOverflowMenuItem { final String title; ///Callback when item is clicked - final Function onClicked; + final Function() onClicked; - BetterPlayerOverflowMenuItem(this.icon, this.title, this.onClicked) - : assert(icon != null, "Icon can't be null"), - assert(title != null, "Title can't be null"); + BetterPlayerOverflowMenuItem(this.icon, this.title, this.onClicked); } diff --git a/lib/src/core/better_player.dart b/lib/src/core/better_player.dart index 58085db18..c3feaaf17 100644 --- a/lib/src/core/better_player.dart +++ b/lib/src/core/better_player.dart @@ -3,6 +3,8 @@ import 'dart:async'; // Project imports: import 'package:better_player/better_player.dart'; +import 'package:better_player/src/configuration/better_player_controller_event.dart'; +import 'package:better_player/src/core/better_player_utils.dart'; import 'package:better_player/src/core/better_player_with_controls.dart'; // Flutter imports: @@ -18,14 +20,11 @@ import 'better_player_controller_provider.dart'; ///Widget which uses provided controller to render video player. class BetterPlayer extends StatefulWidget { - const BetterPlayer({Key key, @required this.controller}) - : assert( - controller != null, 'You must provide a better player controller'), - super(key: key); + const BetterPlayer({Key? key, required this.controller}) : super(key: key); factory BetterPlayer.network( String url, { - BetterPlayerConfiguration betterPlayerConfiguration, + BetterPlayerConfiguration? betterPlayerConfiguration, }) => BetterPlayer( controller: BetterPlayerController( @@ -37,7 +36,7 @@ class BetterPlayer extends StatefulWidget { factory BetterPlayer.file( String url, { - BetterPlayerConfiguration betterPlayerConfiguration, + BetterPlayerConfiguration? betterPlayerConfiguration, }) => BetterPlayer( controller: BetterPlayerController( @@ -63,18 +62,18 @@ class _BetterPlayerState extends State bool _isFullScreen = false; ///State of navigator on widget created - NavigatorState _navigatorState; + late NavigatorState _navigatorState; ///Flag which determines if widget has initialized bool _initialized = false; + ///Subscription for controller events + StreamSubscription? _controllerEventSubscription; + @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); - Future.delayed(Duration.zero, () { - _setup(); - }); + WidgetsBinding.instance!.addObserver(this); } @override @@ -84,19 +83,25 @@ class _BetterPlayerState extends State setState(() { _navigatorState = navigator; }); + _setup(); _initialized = true; } super.didChangeDependencies(); } Future _setup() async { - widget.controller.addListener(onFullScreenChanged); + _controllerEventSubscription = + widget.controller.controllerEventStream.listen(onControllerEvent); + + //Default locale var locale = const Locale("en", "US"); - if (mounted) { - final contextLocale = Localizations.localeOf(context); - if (contextLocale != null) { + try { + if (mounted) { + final contextLocale = Localizations.localeOf(context); locale = contextLocale; } + } catch (exception) { + BetterPlayerUtils.log(exception.toString()); } widget.controller.setupTranslations(locale); } @@ -115,8 +120,8 @@ class _BetterPlayerState extends State _betterPlayerConfiguration.deviceOrientationsAfterFullScreen); } - WidgetsBinding.instance.removeObserver(this); - widget.controller.removeListener(onFullScreenChanged); + WidgetsBinding.instance!.removeObserver(this); + _controllerEventSubscription?.cancel(); widget.controller.dispose(); super.dispose(); } @@ -124,11 +129,27 @@ class _BetterPlayerState extends State @override void didUpdateWidget(BetterPlayer oldWidget) { if (oldWidget.controller != widget.controller) { - widget.controller.addListener(onFullScreenChanged); + _controllerEventSubscription?.cancel(); + _controllerEventSubscription = + widget.controller.controllerEventStream.listen(onControllerEvent); } super.didUpdateWidget(oldWidget); } + void onControllerEvent(BetterPlayerControllerEvent event) { + switch (event) { + case BetterPlayerControllerEvent.openFullscreen: + onFullScreenChanged(); + break; + case BetterPlayerControllerEvent.hideFullscreen: + onFullScreenChanged(); + break; + default: + setState(() {}); + break; + } + } + // ignore: avoid_void_async Future onFullScreenChanged() async { final controller = widget.controller; @@ -137,16 +158,12 @@ class _BetterPlayerState extends State controller .postEvent(BetterPlayerEvent(BetterPlayerEventType.openFullscreen)); await _pushFullScreenWidget(context); - } else if (_isFullScreen && !controller.cancelFullScreenDismiss) { + } else if (_isFullScreen) { Navigator.of(context, rootNavigator: true).pop(); _isFullScreen = false; controller .postEvent(BetterPlayerEvent(BetterPlayerEventType.hideFullscreen)); } - - if (controller.cancelFullScreenDismiss) { - controller.cancelFullScreenDismiss = false; - } } @override @@ -178,7 +195,7 @@ class _BetterPlayerState extends State BetterPlayerControllerProvider controllerProvider) { return AnimatedBuilder( animation: animation, - builder: (BuildContext context, Widget child) { + builder: (BuildContext context, Widget? child) { return _buildFullScreenVideo(context, animation, controllerProvider); }, ); @@ -215,8 +232,7 @@ class _BetterPlayerState extends State if (_betterPlayerConfiguration.autoDetectFullscreenDeviceOrientation == true) { final aspectRatio = - widget?.controller?.videoPlayerController?.value?.aspectRatio ?? - 1.0; + widget.controller.videoPlayerController?.value.aspectRatio ?? 1.0; List deviceOrientations; if (aspectRatio < 1.0) { deviceOrientations = [ diff --git a/lib/src/core/better_player_controller.dart b/lib/src/core/better_player_controller.dart index 4f2b2ce59..c3f4aeae6 100644 --- a/lib/src/core/better_player_controller.dart +++ b/lib/src/core/better_player_controller.dart @@ -5,6 +5,7 @@ import 'dart:io'; // Project imports: import 'package:better_player/better_player.dart'; import 'package:better_player/src/configuration/better_player_configuration.dart'; +import 'package:better_player/src/configuration/better_player_controller_event.dart'; import 'package:better_player/src/configuration/better_player_drm_type.dart'; import 'package:better_player/src/configuration/better_player_event.dart'; import 'package:better_player/src/configuration/better_player_event_type.dart'; @@ -21,6 +22,7 @@ import 'package:better_player/src/subtitles/better_player_subtitle.dart'; import 'package:better_player/src/subtitles/better_player_subtitles_factory.dart'; import 'package:better_player/src/video_player/video_player.dart'; import 'package:better_player/src/video_player/video_player_platform_interface.dart'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; // Package imports: @@ -28,7 +30,7 @@ import 'package:path_provider/path_provider.dart'; ///Class used to control overall Better Player behavior. Main class to change ///state of Better Player. -class BetterPlayerController extends ChangeNotifier { +class BetterPlayerController { static const String _durationParameter = "duration"; static const String _progressParameter = "progress"; static const String _volumeParameter = "volume"; @@ -41,10 +43,10 @@ class BetterPlayerController extends ChangeNotifier { final BetterPlayerConfiguration betterPlayerConfiguration; ///Playlist configuration used in controller instance. - final BetterPlayerPlaylistConfiguration betterPlayerPlaylistConfiguration; + final BetterPlayerPlaylistConfiguration? betterPlayerPlaylistConfiguration; ///List of event listeners, which listen to events. - final List _eventListeners = []; + final List _eventListeners = []; ///List of files to delete once player disposes. final List _tempFiles = []; @@ -55,10 +57,10 @@ class BetterPlayerController extends ChangeNotifier { ///Instance of video player controller which is adapter used to communicate ///between flutter high level code and lower level native code. - VideoPlayerController videoPlayerController; + VideoPlayerController? videoPlayerController; /// Defines a event listener where video player events will be send. - Function(BetterPlayerEvent) get eventListener => + Function(BetterPlayerEvent)? get eventListener => betterPlayerConfiguration.eventListener; ///Flag used to store full screen mode state. @@ -71,10 +73,10 @@ class BetterPlayerController extends ChangeNotifier { int _lastPositionSelection = 0; ///Currently used data source in player. - BetterPlayerDataSource _betterPlayerDataSource; + BetterPlayerDataSource? _betterPlayerDataSource; ///Currently used data source in player. - BetterPlayerDataSource get betterPlayerDataSource => _betterPlayerDataSource; + BetterPlayerDataSource? get betterPlayerDataSource => _betterPlayerDataSource; ///List of BetterPlayerSubtitlesSources. final List _betterPlayerSubtitlesSourceList = []; @@ -82,10 +84,10 @@ class BetterPlayerController extends ChangeNotifier { ///List of BetterPlayerSubtitlesSources. List get betterPlayerSubtitlesSourceList => _betterPlayerSubtitlesSourceList; - BetterPlayerSubtitlesSource _betterPlayerSubtitlesSource; + BetterPlayerSubtitlesSource? _betterPlayerSubtitlesSource; ///Currently used subtitles source. - BetterPlayerSubtitlesSource get betterPlayerSubtitlesSource => + BetterPlayerSubtitlesSource? get betterPlayerSubtitlesSource => _betterPlayerSubtitlesSource; ///Subtitles lines for current data source. @@ -98,30 +100,26 @@ class BetterPlayerController extends ChangeNotifier { List get betterPlayerTracks => _betterPlayerTracks; ///Currently selected player track. Used only for HLS. - BetterPlayerHlsTrack _betterPlayerTrack; + BetterPlayerHlsTrack? _betterPlayerTrack; ///Currently selected player track. Used only for HLS. - BetterPlayerHlsTrack get betterPlayerTrack => _betterPlayerTrack; + BetterPlayerHlsTrack? get betterPlayerTrack => _betterPlayerTrack; ///Timer for next video. Used in playlist. - Timer _nextVideoTimer; + Timer? _nextVideoTimer; ///Time for next video. - int _nextVideoTime; + int? _nextVideoTime; ///Stream controller which emits next video time. - StreamController nextVideoTimeStreamController = + StreamController nextVideoTimeStreamController = StreamController.broadcast(); ///Has player been disposed. bool _disposed = false; ///Was player playing before automatic pause. - bool _wasPlayingBeforePause; - - ///Internal flag used to cancel dismiss of the full screen. Used when user - ///switches quality (track or resolution) of the video. You should ignore it. - bool cancelFullScreenDismiss = false; + bool? _wasPlayingBeforePause; ///Currently used translations BetterPlayerTranslations translations = BetterPlayerTranslations(); @@ -149,7 +147,7 @@ class BetterPlayerController extends ChangeNotifier { ///Overridden aspect ratio which will be used instead of aspect ratio passed ///in configuration. - double _overriddenAspectRatio; + double? _overriddenAspectRatio; ///Was Picture in Picture opened. bool _wasInPipMode = false; @@ -161,13 +159,13 @@ class BetterPlayerController extends ChangeNotifier { bool _wasControlsEnabledBeforePiP = false; ///GlobalKey of the BetterPlayer widget - GlobalKey _betterPlayerGlobalKey; + GlobalKey? _betterPlayerGlobalKey; ///Getter of the GlobalKey - GlobalKey get betterPlayerGlobalKey => _betterPlayerGlobalKey; + GlobalKey? get betterPlayerGlobalKey => _betterPlayerGlobalKey; ///StreamSubscription for VideoEvent listener - StreamSubscription _videoEventStreamSubscription; + StreamSubscription? _videoEventStreamSubscription; ///Are controls always visible bool _controlsAlwaysVisible = false; @@ -176,31 +174,38 @@ class BetterPlayerController extends ChangeNotifier { bool get controlsAlwaysVisible => _controlsAlwaysVisible; ///List of all possible audio tracks returned from HLS stream - List _betterPlayerAudioTracks; + List? _betterPlayerAudioTracks; ///List of all possible audio tracks returned from HLS stream - List get betterPlayerAudioTracks => + List? get betterPlayerAudioTracks => _betterPlayerAudioTracks; ///Selected HLS audio track - BetterPlayerHlsAudioTrack _betterPlayerHlsAudioTrack; + BetterPlayerHlsAudioTrack? _betterPlayerHlsAudioTrack; ///Selected HLS audio track - BetterPlayerHlsAudioTrack get betterPlayerAudioTrack => + BetterPlayerHlsAudioTrack? get betterPlayerAudioTrack => _betterPlayerHlsAudioTrack; ///Selected videoPlayerValue when error occurred. - VideoPlayerValue _videoPlayerValueOnError; + VideoPlayerValue? _videoPlayerValueOnError; ///Flag which holds information about player visibility bool _isPlayerVisible = true; + final StreamController + _controllerEventStreamController = StreamController.broadcast(); + + ///Stream of internal controller events. Shouldn't be used inside app. For + ///normal events, use eventListener. + Stream get controllerEventStream => + _controllerEventStreamController.stream; + BetterPlayerController( this.betterPlayerConfiguration, { this.betterPlayerPlaylistConfiguration, - BetterPlayerDataSource betterPlayerDataSource, - }) : assert(betterPlayerConfiguration != null, - "BetterPlayerConfiguration can't be null") { + BetterPlayerDataSource? betterPlayerDataSource, + }) { _eventListeners.add(eventListener); if (betterPlayerDataSource != null) { setupDataSource(betterPlayerDataSource); @@ -210,19 +215,18 @@ class BetterPlayerController extends ChangeNotifier { ///Get BetterPlayerController from context. Used in InheritedWidget. static BetterPlayerController of(BuildContext context) { final betterPLayerControllerProvider = context - .dependOnInheritedWidgetOfExactType(); + .dependOnInheritedWidgetOfExactType()!; return betterPLayerControllerProvider.controller; } ///Setup new data source in Better Player. Future setupDataSource(BetterPlayerDataSource betterPlayerDataSource) async { - assert( - betterPlayerDataSource != null, "BetterPlayerDataSource can't be null"); postEvent(BetterPlayerEvent(BetterPlayerEventType.setupDataSource, parameters: { _dataSourceParameter: betterPlayerDataSource, })); + _postControllerEvent(BetterPlayerControllerEvent.setupDataSource); _hasCurrentDataSourceStarted = false; _hasCurrentDataSourceInitialized = false; _betterPlayerDataSource = betterPlayerDataSource; @@ -237,10 +241,11 @@ class BetterPlayerController extends ChangeNotifier { betterPlayerTracks.clear(); ///Setup subtitles - final List betterPlayerSubtitlesSourceList = + final List? betterPlayerSubtitlesSourceList = betterPlayerDataSource.subtitles; if (betterPlayerSubtitlesSourceList != null) { - _betterPlayerSubtitlesSourceList.addAll(betterPlayerDataSource.subtitles); + _betterPlayerSubtitlesSourceList + .addAll(betterPlayerDataSource.subtitles!); } if (_isDataSourceHls(betterPlayerDataSource)) { @@ -261,9 +266,8 @@ class BetterPlayerController extends ChangeNotifier { _betterPlayerSubtitlesSourceList.add( BetterPlayerSubtitlesSource(type: BetterPlayerSubtitlesSourceType.none), ); - final defaultSubtitle = _betterPlayerSubtitlesSourceList.firstWhere( - (element) => element.selectedByDefault == true, - orElse: () => null); + final defaultSubtitle = _betterPlayerSubtitlesSourceList + .firstWhereOrNull((element) => element.selectedByDefault == true); ///Setup subtitles (none is default) setupSubtitleSource( @@ -280,22 +284,22 @@ class BetterPlayerController extends ChangeNotifier { ///This method configures tracks, subtitles and audio tracks from given ///master playlist. Future _setupHlsDataSource() async { - final String hlsData = await BetterPlayerHlsUtils.getDataFromUrl( - betterPlayerDataSource.url, + final String? hlsData = await BetterPlayerHlsUtils.getDataFromUrl( + betterPlayerDataSource!.url, _getHeaders(), ); if (hlsData != null) { /// Load hls tracks if (_betterPlayerDataSource?.useHlsTracks == true) { _betterPlayerTracks = await BetterPlayerHlsUtils.parseTracks( - hlsData, betterPlayerDataSource.url); + hlsData, betterPlayerDataSource!.url); } /// Load hls subtitles if (betterPlayerDataSource?.useHlsSubtitles == true) { final hlsSubtitles = await BetterPlayerHlsUtils.parseSubtitles( - hlsData, betterPlayerDataSource.url); - hlsSubtitles?.forEach((hlsSubtitle) { + hlsData, betterPlayerDataSource!.url); + hlsSubtitles.forEach((hlsSubtitle) { _betterPlayerSubtitlesSourceList.add( BetterPlayerSubtitlesSource( type: BetterPlayerSubtitlesSourceType.network, @@ -307,9 +311,12 @@ class BetterPlayerController extends ChangeNotifier { ///Load audio tracks if (betterPlayerDataSource?.useHlsAudioTracks == true && - _isDataSourceHls(betterPlayerDataSource)) { + _isDataSourceHls(betterPlayerDataSource!)) { _betterPlayerAudioTracks = await BetterPlayerHlsUtils.parseLanguages( - hlsData, betterPlayerDataSource.url); + hlsData, betterPlayerDataSource!.url); + if (_betterPlayerAudioTracks?.isNotEmpty == true) { + setAudioTrack(_betterPlayerAudioTracks!.first); + } } } } @@ -317,7 +324,6 @@ class BetterPlayerController extends ChangeNotifier { ///Setup subtitles to be displayed from given subtitle source Future setupSubtitleSource(BetterPlayerSubtitlesSource subtitlesSource, {bool sourceInitialize = false}) async { - assert(subtitlesSource != null, "SubtitlesSource can't be null"); _betterPlayerSubtitlesSource = subtitlesSource; subtitlesLines.clear(); if (subtitlesSource.type != BetterPlayerSubtitlesSourceType.none) { @@ -328,14 +334,14 @@ class BetterPlayerController extends ChangeNotifier { _postEvent(BetterPlayerEvent(BetterPlayerEventType.changedSubtitles)); if (!_disposed && !sourceInitialize) { - cancelFullScreenDismiss = true; - notifyListeners(); + _postControllerEvent(BetterPlayerControllerEvent.changeSubtitles); } } ///Get VideoFormat from BetterPlayerVideoFormat (adapter method which translates ///to video_player supported format). - VideoFormat _getVideoFormat(BetterPlayerVideoFormat betterPlayerVideoFormat) { + VideoFormat? _getVideoFormat( + BetterPlayerVideoFormat? betterPlayerVideoFormat) { if (betterPlayerVideoFormat == null) { return null; } @@ -349,24 +355,22 @@ class BetterPlayerController extends ChangeNotifier { case BetterPlayerVideoFormat.other: return VideoFormat.other; } - return null; } ///Internal method which invokes videoPlayerController source setup. Future _setupDataSource(BetterPlayerDataSource betterPlayerDataSource) async { - assert( - betterPlayerDataSource != null, "BetterPlayerDataSource can't be null"); switch (betterPlayerDataSource.type) { case BetterPlayerDataSourceType.network: await videoPlayerController?.setNetworkDataSource( betterPlayerDataSource.url, headers: _getHeaders(), useCache: - _betterPlayerDataSource.cacheConfiguration?.useCache ?? false, + _betterPlayerDataSource!.cacheConfiguration?.useCache ?? false, maxCacheSize: - _betterPlayerDataSource.cacheConfiguration?.maxCacheSize ?? 0, + _betterPlayerDataSource!.cacheConfiguration?.maxCacheSize ?? 0, maxCacheFileSize: - _betterPlayerDataSource.cacheConfiguration?.maxCacheFileSize ?? 0, + _betterPlayerDataSource!.cacheConfiguration?.maxCacheFileSize ?? + 0, showNotification: _betterPlayerDataSource ?.notificationConfiguration?.showNotification, title: _betterPlayerDataSource?.notificationConfiguration?.title, @@ -375,8 +379,8 @@ class BetterPlayerController extends ChangeNotifier { _betterPlayerDataSource?.notificationConfiguration?.imageUrl, notificationChannelName: _betterPlayerDataSource ?.notificationConfiguration?.notificationChannelName, - overriddenDuration: _betterPlayerDataSource.overriddenDuration, - formatHint: _getVideoFormat(_betterPlayerDataSource.videoFormat), + overriddenDuration: _betterPlayerDataSource!.overriddenDuration, + formatHint: _getVideoFormat(_betterPlayerDataSource!.videoFormat), licenseUrl: _betterPlayerDataSource?.drmConfiguration?.licenseUrl, drmHeaders: _betterPlayerDataSource?.drmConfiguration?.headers, ); @@ -393,14 +397,14 @@ class BetterPlayerController extends ChangeNotifier { _betterPlayerDataSource?.notificationConfiguration?.imageUrl, notificationChannelName: _betterPlayerDataSource ?.notificationConfiguration?.notificationChannelName, - overriddenDuration: _betterPlayerDataSource.overriddenDuration, + overriddenDuration: _betterPlayerDataSource!.overriddenDuration, ); break; case BetterPlayerDataSourceType.memory: - final file = await _createFile(_betterPlayerDataSource.bytes, - extension: _betterPlayerDataSource.videoExtension); + final file = await _createFile(_betterPlayerDataSource!.bytes!, + extension: _betterPlayerDataSource!.videoExtension); - if (file != null) { + if (file.existsSync()) { await videoPlayerController?.setFileDataSource( file, showNotification: _betterPlayerDataSource @@ -411,7 +415,7 @@ class BetterPlayerController extends ChangeNotifier { _betterPlayerDataSource?.notificationConfiguration?.imageUrl, notificationChannelName: _betterPlayerDataSource ?.notificationConfiguration?.notificationChannelName, - overriddenDuration: _betterPlayerDataSource.overriddenDuration, + overriddenDuration: _betterPlayerDataSource!.overriddenDuration, ); _tempFiles.add(file); } else { @@ -428,7 +432,8 @@ class BetterPlayerController extends ChangeNotifier { ///Create file from provided list of bytes. File will be created in temporary ///directory. - Future _createFile(List bytes, {String extension = "temp"}) async { + Future _createFile(List bytes, + {String? extension = "temp"}) async { final String dir = (await getTemporaryDirectory()).path; final File temp = File( '$dir/better_player_${DateTime.now().millisecondsSinceEpoch}.$extension'); @@ -439,11 +444,10 @@ class BetterPlayerController extends ChangeNotifier { ///Initializes video based on configuration. Invoke actions which need to be ///run on player start. Future _initializeVideo() async { - await videoPlayerController?.setLooping(betterPlayerConfiguration.looping); - + setLooping(betterPlayerConfiguration.looping); _videoEventStreamSubscription = videoPlayerController - ?.videoEventStreamController?.stream - ?.listen(_handleVideoEvent); + ?.videoEventStreamController.stream + .listen(_handleVideoEvent); final fullScreenByDefault = betterPlayerConfiguration.fullScreenByDefault; if (betterPlayerConfiguration.autoPlay) { @@ -468,13 +472,13 @@ class BetterPlayerController extends ChangeNotifier { final startAt = betterPlayerConfiguration.startAt; if (startAt != null) { - await videoPlayerController?.seekTo(startAt); + seekTo(startAt); } } ///Method which is invoked when full screen changes. Future _onFullScreenStateChanged() async { - if (videoPlayerController?.value?.isPlaying == true && !_isFullScreen) { + if (videoPlayerController?.value.isPlaying == true && !_isFullScreen) { enterFullScreen(); videoPlayerController?.removeListener(_onFullScreenStateChanged); } @@ -483,50 +487,75 @@ class BetterPlayerController extends ChangeNotifier { ///Enables full screen mode in player. This will trigger route change. void enterFullScreen() { _isFullScreen = true; - notifyListeners(); + _postControllerEvent(BetterPlayerControllerEvent.openFullscreen); } ///Disables full screen mode in player. This will trigger route change. void exitFullScreen() { _isFullScreen = false; - notifyListeners(); + _postControllerEvent(BetterPlayerControllerEvent.hideFullscreen); } ///Enables/disables full screen mode based on current fullscreen state. void toggleFullScreen() { _isFullScreen = !_isFullScreen; - notifyListeners(); + if (_isFullScreen) { + _postControllerEvent(BetterPlayerControllerEvent.openFullscreen); + } else { + _postControllerEvent(BetterPlayerControllerEvent.hideFullscreen); + } } ///Start video playback. Play will be triggered only if current lifecycle state ///is resumed. Future play() async { + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + if (_appLifecycleState == AppLifecycleState.resumed) { - await videoPlayerController?.play(); + await videoPlayerController!.play(); _hasCurrentDataSourceStarted = true; _wasPlayingBeforePause = null; _postEvent(BetterPlayerEvent(BetterPlayerEventType.play)); + _postControllerEvent(BetterPlayerControllerEvent.play); } } ///Enables/disables looping (infinity playback) mode. Future setLooping(bool looping) async { - await videoPlayerController?.setLooping(looping); + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + + await videoPlayerController!.setLooping(looping); } ///Stop video playback. Future pause() async { - await videoPlayerController?.pause(); + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + + await videoPlayerController!.pause(); _postEvent(BetterPlayerEvent(BetterPlayerEventType.pause)); } ///Move player to specific position/moment of the video. Future seekTo(Duration moment) async { - await videoPlayerController?.seekTo(moment); + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + await videoPlayerController!.seekTo(moment); _postEvent(BetterPlayerEvent(BetterPlayerEventType.seekTo, parameters: {_durationParameter: moment})); - if (moment > videoPlayerController?.value?.duration) { + + final Duration? currentDuration = videoPlayerController!.value.duration; + if (currentDuration == null) { + return; + } + if (moment > currentDuration) { _postEvent(BetterPlayerEvent(BetterPlayerEventType.finished)); } else { cancelNextVideoTimer(); @@ -538,7 +567,11 @@ class BetterPlayerController extends ChangeNotifier { if (volume < 0.0 || volume > 1.0) { throw ArgumentError("Volume must be between 0.0 and 1.0"); } - await videoPlayerController?.setVolume(volume); + if (videoPlayerController == null) { + BetterPlayerUtils.log("The data source has not been initialized"); + return; + } + await videoPlayerController!.setVolume(volume); _postEvent(BetterPlayerEvent(BetterPlayerEventType.setVolume, parameters: {_volumeParameter: volume})); } @@ -548,30 +581,43 @@ class BetterPlayerController extends ChangeNotifier { if (speed < 0 || speed > 2) { throw ArgumentError("Speed must be between 0 and 2"); } + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } await videoPlayerController?.setSpeed(speed); - _postEvent(BetterPlayerEvent(BetterPlayerEventType.setSpeed, - parameters: {_speedParameter: speed})); + _postEvent( + BetterPlayerEvent( + BetterPlayerEventType.setSpeed, + parameters: { + _speedParameter: speed, + }, + ), + ); } ///Flag which determines whenever player is playing or not. - bool isPlaying() { - return videoPlayerController?.value?.isPlaying; + bool? isPlaying() { + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + return videoPlayerController!.value.isPlaying; } ///Flag which determines whenever player is loading video data or not. - bool isBuffering() { - return videoPlayerController?.value?.isBuffering; + bool? isBuffering() { + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + return videoPlayerController!.value.isBuffering; } ///Show or hide controls manually void setControlsVisibility(bool isVisible) { - assert(isVisible != null, "IsVisible can't be null"); _controlsVisibilityStreamController.add(isVisible); } ///Enable/disable controls (when enabled = false, controls will be always hidden) void setControlsEnabled(bool enabled) { - assert(enabled != null, "Enabled can't be null"); if (!enabled) { _controlsVisibilityStreamController.add(false); } @@ -581,7 +627,6 @@ class BetterPlayerController extends ChangeNotifier { ///Internal method, used to trigger CONTROLS_VISIBLE or CONTROLS_HIDDEN event ///once controls state changed. void toggleControlsVisibility(bool isVisible) { - assert(isVisible != null, "IsVisible can't be null"); _postEvent(isVisible ? BetterPlayerEvent(BetterPlayerEventType.controlsVisible) : BetterPlayerEvent(BetterPlayerEventType.controlsHidden)); @@ -594,7 +639,7 @@ class BetterPlayerController extends ChangeNotifier { ///Send player event to all listeners. void _postEvent(BetterPlayerEvent betterPlayerEvent) { - for (final Function eventListener in _eventListeners) { + for (final Function(BetterPlayerEvent)? eventListener in _eventListeners) { if (eventListener != null) { eventListener(betterPlayerEvent); } @@ -603,7 +648,10 @@ class BetterPlayerController extends ChangeNotifier { ///Listener used to handle video player changes. void _onVideoPlayerChanged() async { - final currentVideoPlayerValue = videoPlayerController?.value; + final VideoPlayerValue currentVideoPlayerValue = + videoPlayerController?.value ?? + VideoPlayerValue(duration: const Duration()); + if (currentVideoPlayerValue.hasError) { _videoPlayerValueOnError ??= currentVideoPlayerValue; _postEvent( @@ -662,12 +710,18 @@ class BetterPlayerController extends ChangeNotifier { ///Flag which determines whenever player is playing live data source. bool isLiveStream() { - return _betterPlayerDataSource?.liveStream == true; + if (_betterPlayerDataSource == null) { + throw StateError("The data source has not been initialized"); + } + return _betterPlayerDataSource!.liveStream == true; } ///Flag which determines whenever player data source has been initialized. - bool isVideoInitialized() { - return videoPlayerController?.value?.initialized; + bool? isVideoInitialized() { + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + return videoPlayerController?.value.initialized; } ///Start timer which will trigger next video. Used in playlist. Do not use @@ -675,7 +729,7 @@ class BetterPlayerController extends ChangeNotifier { void startNextVideoTimer() { if (_nextVideoTimer == null) { _nextVideoTime = - betterPlayerPlaylistConfiguration.nextVideoDelay.inSeconds; + betterPlayerPlaylistConfiguration!.nextVideoDelay.inSeconds; nextVideoTimeStreamController.add(_nextVideoTime); _nextVideoTimer = Timer.periodic(const Duration(milliseconds: 1000), (_timer) async { @@ -683,7 +737,9 @@ class BetterPlayerController extends ChangeNotifier { _timer.cancel(); _nextVideoTimer = null; } - _nextVideoTime -= 1; + if (_nextVideoTime != null) { + _nextVideoTime = _nextVideoTime! - 1; + } nextVideoTimeStreamController.add(_nextVideoTime); }); } @@ -707,10 +763,13 @@ class BetterPlayerController extends ChangeNotifier { ///Setup track parameters for currently played video. Can be used only for HLS ///data source. void setTrack(BetterPlayerHlsTrack track) { + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } _postEvent(BetterPlayerEvent(BetterPlayerEventType.changedTrack)); - videoPlayerController?.setTrackParameters( - track.width, track.height, track.bitrate); + videoPlayerController! + .setTrackParameters(track.width, track.height, track.bitrate); _betterPlayerTrack = track; } @@ -738,13 +797,13 @@ class BetterPlayerController extends ChangeNotifier { if (_isAutomaticPlayPauseHandled()) { if (betterPlayerConfiguration.playerVisibilityChangedBehavior != null) { betterPlayerConfiguration - .playerVisibilityChangedBehavior(visibilityFraction); + .playerVisibilityChangedBehavior!(visibilityFraction); } else { if (visibilityFraction == 0) { _wasPlayingBeforePause ??= isPlaying(); pause(); } else { - if (_wasPlayingBeforePause == true && !isPlaying()) { + if (_wasPlayingBeforePause == true && !isPlaying()!) { play(); } } @@ -754,15 +813,16 @@ class BetterPlayerController extends ChangeNotifier { ///Set different resolution (quality) for video void setResolution(String url) async { - assert(url != null, "Url can't be null"); - final position = await videoPlayerController?.position; - final wasPlayingBeforeChange = isPlaying(); - cancelFullScreenDismiss = true; - videoPlayerController?.pause(); - await setupDataSource(betterPlayerDataSource.copyWith(url: url)); - videoPlayerController?.seekTo(position); + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + final position = await videoPlayerController!.position; + final wasPlayingBeforeChange = isPlaying()!; + pause(); + await setupDataSource(betterPlayerDataSource!.copyWith(url: url)); + seekTo(position!); if (wasPlayingBeforeChange) { - videoPlayerController?.play(); + play(); } _postEvent(BetterPlayerEvent(BetterPlayerEventType.changedResolution)); } @@ -770,11 +830,11 @@ class BetterPlayerController extends ChangeNotifier { ///Setup translations for given locale. In normal use cases it shouldn't be ///called manually. void setupTranslations(Locale locale) { + // ignore: unnecessary_null_comparison if (locale != null) { final String languageCode = locale.languageCode; - translations = betterPlayerConfiguration.translations?.firstWhere( - (translations) => translations.languageCode == languageCode, - orElse: () => null) ?? + translations = betterPlayerConfiguration.translations?.firstWhereOrNull( + (translations) => translations.languageCode == languageCode) ?? _getDefaultTranslations(locale); } else { BetterPlayerUtils.log("Locale is null. Couldn't setup translations."); @@ -784,20 +844,17 @@ class BetterPlayerController extends ChangeNotifier { ///Setup default translations for selected user locale. These translations ///are pre-build in. BetterPlayerTranslations _getDefaultTranslations(Locale locale) { - if (locale != null) { - final String languageCode = locale.languageCode; - switch (languageCode) { - case "pl": - return BetterPlayerTranslations.polish(); - case "zh": - return BetterPlayerTranslations.chinese(); - case "hi": - return BetterPlayerTranslations.hindi(); - default: - return BetterPlayerTranslations(); - } + final String languageCode = locale.languageCode; + switch (languageCode) { + case "pl": + return BetterPlayerTranslations.polish(); + case "zh": + return BetterPlayerTranslations.chinese(); + case "hi": + return BetterPlayerTranslations.hindi(); + default: + return BetterPlayerTranslations(); } - return BetterPlayerTranslations(); } ///Flag which determines whenever current data source has started. @@ -831,17 +888,22 @@ class BetterPlayerController extends ChangeNotifier { ///Get aspect ratio used in current video. If aspect ratio is null, then ///aspect ratio from BetterPlayerConfiguration will be used. Otherwise ///[_overriddenAspectRatio] will be used. - double getAspectRatio() { + double? getAspectRatio() { return _overriddenAspectRatio ?? betterPlayerConfiguration.aspectRatio; } ///Enable Picture in Picture (PiP) mode. [betterPlayerGlobalKey] is required ///to open PiP mode in iOS. When device is not supported, PiP mode won't be ///open. - Future enablePictureInPicture(GlobalKey betterPlayerGlobalKey) async { - assert( - betterPlayerGlobalKey != null, "BetterPlayerGlobalKey can't be null"); - if (await videoPlayerController?.isPictureInPictureSupported()) { + Future? enablePictureInPicture(GlobalKey betterPlayerGlobalKey) async { + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + + final bool isPipSupported = + (await videoPlayerController!.isPictureInPictureSupported()) ?? false; + + if (isPipSupported) { _wasInFullScreenBeforePiP = _isFullScreen; _wasControlsEnabledBeforePiP = _controlsEnabled; setControlsEnabled(false); @@ -854,8 +916,8 @@ class BetterPlayerController extends ChangeNotifier { return; } if (Platform.isIOS) { - final RenderBox renderBox = betterPlayerGlobalKey.currentContext - .findRenderObject() as RenderBox; + final RenderBox? renderBox = betterPlayerGlobalKey.currentContext! + .findRenderObject() as RenderBox?; if (renderBox == null) { BetterPlayerUtils.log( "Can't show PiP. RenderBox is null. Did you provide valid global" @@ -881,21 +943,29 @@ class BetterPlayerController extends ChangeNotifier { } ///Disable Picture in Picture mode if it's enabled. - Future disablePictureInPicture() { - return videoPlayerController?.disablePictureInPicture(); + Future? disablePictureInPicture() { + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + return videoPlayerController!.disablePictureInPicture(); } + // ignore: use_setters_to_change_properties ///Set GlobalKey of BetterPlayer. Used in PiP methods called from controls. void setBetterPlayerGlobalKey(GlobalKey betterPlayerGlobalKey) { - assert( - betterPlayerGlobalKey != null, "BetterPlayerGlobalKey can't be null"); _betterPlayerGlobalKey = betterPlayerGlobalKey; } ///Check if picture in picture mode is supported in this device. Future isPictureInPictureSupported() async { - return await videoPlayerController?.isPictureInPictureSupported() && - !_isFullScreen; + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + + final bool isPipSupported = + (await videoPlayerController!.isPictureInPictureSupported()) ?? false; + + return isPipSupported && !_isFullScreen; } ///Handle VideoEvent when remote controls notification / PiP is shown @@ -911,13 +981,13 @@ class BetterPlayerController extends ChangeNotifier { _postEvent(BetterPlayerEvent(BetterPlayerEventType.seekTo)); break; case VideoEventType.completed: - final videoValue = videoPlayerController?.value; + final VideoPlayerValue? videoValue = videoPlayerController?.value; _postEvent( BetterPlayerEvent( BetterPlayerEventType.finished, parameters: { - _progressParameter: videoValue.position, - _durationParameter: videoValue.duration + _progressParameter: videoValue?.position, + _durationParameter: videoValue?.duration }, ), ); @@ -931,17 +1001,15 @@ class BetterPlayerController extends ChangeNotifier { ///Setup controls always visible mode void setControlsAlwaysVisible(bool controlsAlwaysVisible) { - assert( - controlsAlwaysVisible != null, "ControlsAlwaysVisible can't be null"); _controlsAlwaysVisible = controlsAlwaysVisible; _controlsVisibilityStreamController.add(controlsAlwaysVisible); } ///Retry data source if playback failed. Future retryDataSource() async { - await _setupDataSource(_betterPlayerDataSource); + await _setupDataSource(_betterPlayerDataSource!); if (_videoPlayerValueOnError != null) { - final position = _videoPlayerValueOnError.position; + final position = _videoPlayerValueOnError!.position; await seekTo(position); await play(); _videoPlayerValueOnError = null; @@ -950,7 +1018,9 @@ class BetterPlayerController extends ChangeNotifier { ///Set [audioTrack] in player. Works only for HLS streams. void setAudioTrack(BetterPlayerHlsAudioTrack audioTrack) { - assert(audioTrack != null, "AudioTrack can't be null"); + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } if (audioTrack.language == null) { _betterPlayerHlsAudioTrack = null; @@ -958,31 +1028,39 @@ class BetterPlayerController extends ChangeNotifier { } _betterPlayerHlsAudioTrack = audioTrack; - videoPlayerController?.setAudioTrack(audioTrack.label, audioTrack.id); + videoPlayerController!.setAudioTrack(audioTrack.label, audioTrack.id); } ///Enable or disable audio mixing with other sound within device. void setMixWithOthers(bool mixWithOthers) { - videoPlayerController.setMixWithOthers(mixWithOthers); + if (videoPlayerController == null) { + throw StateError("The data source has not been initialized"); + } + + videoPlayerController!.setMixWithOthers(mixWithOthers); } ///Build headers map that will be used to setup video player controller. Apply ///DRM headers if available. - Map _getHeaders() { - final headers = betterPlayerDataSource.headers ?? {}; - if (betterPlayerDataSource.drmConfiguration != null && - betterPlayerDataSource.drmConfiguration?.drmType == - BetterPlayerDrmType.token) { + Map _getHeaders() { + final headers = betterPlayerDataSource!.headers ?? {}; + if (betterPlayerDataSource?.drmConfiguration?.drmType == + BetterPlayerDrmType.token && + betterPlayerDataSource?.drmConfiguration?.token != null) { headers[_authorizationHeader] = - betterPlayerDataSource.drmConfiguration.token; + betterPlayerDataSource!.drmConfiguration!.token!; } return headers; } + /// Add controller internal event. + void _postControllerEvent(BetterPlayerControllerEvent event) { + _controllerEventStreamController.add(event); + } + ///Dispose BetterPlayerController. When [forceDispose] parameter is true, then ///autoDispose parameter will be overridden and controller will be disposed ///(if it wasn't disposed before). - @override void dispose({bool forceDispose = false}) { if (!betterPlayerConfiguration.autoDispose && !forceDispose) { return; @@ -998,10 +1076,10 @@ class BetterPlayerController extends ChangeNotifier { _controlsVisibilityStreamController.close(); _videoEventStreamSubscription?.cancel(); _disposed = true; + _controllerEventStreamController.close(); ///Delete files async - _tempFiles?.forEach((file) => file.delete()); - super.dispose(); + _tempFiles.forEach((file) => file.delete()); } } } diff --git a/lib/src/core/better_player_controller_provider.dart b/lib/src/core/better_player_controller_provider.dart index f3a8d4c6a..31b3a9d6a 100644 --- a/lib/src/core/better_player_controller_provider.dart +++ b/lib/src/core/better_player_controller_provider.dart @@ -6,12 +6,10 @@ import 'package:flutter/material.dart'; ///Widget which is used to inherit BetterPlayerController through widget tree. class BetterPlayerControllerProvider extends InheritedWidget { const BetterPlayerControllerProvider({ - Key key, - @required this.controller, - @required Widget child, - }) : assert(controller != null), - assert(child != null), - super(key: key, child: child); + Key? key, + required this.controller, + required Widget child, + }) : super(key: key, child: child); final BetterPlayerController controller; diff --git a/lib/src/core/better_player_utils.dart b/lib/src/core/better_player_utils.dart index e079d25e4..610f04082 100644 --- a/lib/src/core/better_player_utils.dart +++ b/lib/src/core/better_player_utils.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; class BetterPlayerUtils { static String formatBitrate(int bitrate) { - assert(bitrate != null, "Bitrate can't be null"); if (bitrate < 1000) { return "$bitrate bit/s"; } @@ -17,7 +16,6 @@ class BetterPlayerUtils { } static String formatDuration(Duration position) { - assert(position != null, "Position can't be null!"); final ms = position.inMilliseconds; int seconds = ms ~/ 1000; @@ -51,7 +49,6 @@ class BetterPlayerUtils { } static double calculateAspectRatio(BuildContext context) { - assert(context != null, "Context can't be null!"); final size = MediaQuery.of(context).size; final width = size.width; final height = size.height; diff --git a/lib/src/core/better_player_with_controls.dart b/lib/src/core/better_player_with_controls.dart index 7070f3ecf..e1a9334fb 100644 --- a/lib/src/core/better_player_with_controls.dart +++ b/lib/src/core/better_player_with_controls.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:math'; // Flutter imports: +import 'package:better_player/src/configuration/better_player_controller_event.dart'; import 'package:flutter/material.dart'; // Project imports: @@ -18,9 +19,9 @@ import 'package:better_player/src/subtitles/better_player_subtitles_drawer.dart' import 'package:better_player/src/video_player/video_player.dart'; class BetterPlayerWithControls extends StatefulWidget { - final BetterPlayerController controller; + final BetterPlayerController? controller; - const BetterPlayerWithControls({Key key, this.controller}) : super(key: key); + const BetterPlayerWithControls({Key? key, this.controller}) : super(key: key); @override _BetterPlayerWithControlsState createState() => @@ -29,27 +30,32 @@ class BetterPlayerWithControls extends StatefulWidget { class _BetterPlayerWithControlsState extends State { BetterPlayerSubtitlesConfiguration get subtitlesConfiguration => - widget.controller.betterPlayerConfiguration.subtitlesConfiguration; + widget.controller!.betterPlayerConfiguration.subtitlesConfiguration; BetterPlayerControlsConfiguration get controlsConfiguration => - widget.controller.betterPlayerConfiguration.controlsConfiguration; + widget.controller!.betterPlayerConfiguration.controlsConfiguration; final StreamController playerVisibilityStreamController = StreamController(); - bool _initalized = false; + bool _initialized = false; + + StreamSubscription? _controllerEventSubscription; @override void initState() { playerVisibilityStreamController.add(true); - widget.controller.addListener(_onControllerChanged); + _controllerEventSubscription = + widget.controller!.controllerEventStream.listen(_onControllerChanged); super.initState(); } @override void didUpdateWidget(BetterPlayerWithControls oldWidget) { if (oldWidget.controller != widget.controller) { - widget.controller.addListener(_onControllerChanged); + _controllerEventSubscription?.cancel(); + _controllerEventSubscription = + widget.controller!.controllerEventStream.listen(_onControllerChanged); } super.didUpdateWidget(oldWidget); } @@ -57,14 +63,14 @@ class _BetterPlayerWithControlsState extends State { @override void dispose() { playerVisibilityStreamController.close(); - widget.controller.removeListener(_onControllerChanged); + _controllerEventSubscription?.cancel(); super.dispose(); } - void _onControllerChanged() { + void _onControllerChanged(BetterPlayerControllerEvent event) { setState(() { - if (!_initalized) { - _initalized = true; + if (!_initialized) { + _initialized = true; } }); } @@ -74,12 +80,12 @@ class _BetterPlayerWithControlsState extends State { final BetterPlayerController betterPlayerController = BetterPlayerController.of(context); - double aspectRatio; + double? aspectRatio; if (betterPlayerController.isFullScreen) { if (betterPlayerController .betterPlayerConfiguration.autoDetectFullscreenDeviceOrientation) { aspectRatio = - betterPlayerController?.videoPlayerController?.value?.aspectRatio ?? + betterPlayerController.videoPlayerController?.value.aspectRatio ?? 1.0; } else { aspectRatio = betterPlayerController @@ -117,11 +123,10 @@ class _BetterPlayerWithControlsState extends State { if (betterPlayerController.betterPlayerDataSource == null) { return Container(); } - _initalized = true; + _initialized = true; final bool placeholderOnTop = - betterPlayerController.betterPlayerConfiguration.placeholderOnTop ?? - false; + betterPlayerController.betterPlayerConfiguration.placeholderOnTop; // ignore: avoid_unnecessary_containers return Container( child: Stack( @@ -151,7 +156,7 @@ class _BetterPlayerWithControlsState extends State { } Widget _buildPlaceholder(BetterPlayerController betterPlayerController) { - return betterPlayerController.betterPlayerDataSource.placeholder ?? + return betterPlayerController.betterPlayerDataSource!.placeholder ?? betterPlayerController.betterPlayerConfiguration.placeholder ?? Container(); } @@ -161,7 +166,7 @@ class _BetterPlayerWithControlsState extends State { BetterPlayerController betterPlayerController, ) { if (controlsConfiguration.showControls) { - BetterPlayerTheme playerTheme = controlsConfiguration.playerTheme; + BetterPlayerTheme? playerTheme = controlsConfiguration.playerTheme; if (playerTheme == null) { if (Platform.isAndroid) { playerTheme = BetterPlayerTheme.material; @@ -173,7 +178,7 @@ class _BetterPlayerWithControlsState extends State { if (controlsConfiguration.customControlsBuilder != null && playerTheme == BetterPlayerTheme.custom) { return controlsConfiguration - .customControlsBuilder(betterPlayerController); + .customControlsBuilder!(betterPlayerController); } else if (playerTheme == BetterPlayerTheme.material) { return _buildMaterialControl(); } else if (playerTheme == BetterPlayerTheme.cupertino) { @@ -208,11 +213,8 @@ class _BetterPlayerVideoFitWidget extends StatefulWidget { const _BetterPlayerVideoFitWidget( this.betterPlayerController, this.boxFit, { - Key key, - }) : assert(betterPlayerController != null, - "BetterPlayerController can't be null"), - assert(boxFit != null, "BoxFit can't be null"), - super(key: key); + Key? key, + }) : super(key: key); final BetterPlayerController betterPlayerController; final BoxFit boxFit; @@ -224,15 +226,17 @@ class _BetterPlayerVideoFitWidget extends StatefulWidget { class _BetterPlayerVideoFitWidgetState extends State<_BetterPlayerVideoFitWidget> { - VideoPlayerController get controller => + VideoPlayerController? get controller => widget.betterPlayerController.videoPlayerController; bool _initialized = false; - VoidCallback _initializedListener; + VoidCallback? _initializedListener; bool _started = false; + StreamSubscription? _controllerEventSubscription; + @override void initState() { super.initState(); @@ -250,43 +254,43 @@ class _BetterPlayerVideoFitWidgetState void didUpdateWidget(_BetterPlayerVideoFitWidget oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.betterPlayerController.videoPlayerController != controller) { - oldWidget.betterPlayerController.videoPlayerController - .removeListener(_initializedListener); + if (_initializedListener != null) { + oldWidget.betterPlayerController.videoPlayerController! + .removeListener(_initializedListener!); + } _initialized = false; _initialize(); } } void _initialize() { - if (controller?.value?.initialized == false) { + if (controller?.value.initialized == false) { _initializedListener = () { if (!mounted) { return; } - if (_initialized != controller.value.initialized) { - _initialized = controller.value.initialized; + if (_initialized != controller!.value.initialized) { + _initialized = controller!.value.initialized; setState(() {}); } }; - controller.addListener(_initializedListener); + controller!.addListener(_initializedListener!); } else { _initialized = true; } - widget.betterPlayerController.addEventsListener((event) { - if (event.betterPlayerEventType == BetterPlayerEventType.play) { - if (widget.betterPlayerController.betterPlayerConfiguration - .showPlaceholderUntilPlay && - !_started && - mounted) { + + _controllerEventSubscription = + widget.betterPlayerController.controllerEventStream.listen((event) { + if (event == BetterPlayerControllerEvent.play) { + if (!_started) { setState(() { _started = widget.betterPlayerController.hasCurrentDataSourceStarted; }); } } - if (event.betterPlayerEventType == - BetterPlayerEventType.setupDataSource) { + if (event == BetterPlayerControllerEvent.setupDataSource) { setState(() { _started = false; }); @@ -302,10 +306,10 @@ class _BetterPlayerVideoFitWidgetState width: double.infinity, height: double.infinity, child: FittedBox( - fit: widget.boxFit ?? BoxFit.fill, + fit: widget.boxFit, child: SizedBox( - width: controller.value.size?.width ?? 0, - height: controller.value.size?.height ?? 0, + width: controller!.value.size?.width ?? 0, + height: controller!.value.size?.height ?? 0, child: VideoPlayer(controller), // ), @@ -319,8 +323,11 @@ class _BetterPlayerVideoFitWidgetState @override void dispose() { - widget.betterPlayerController.videoPlayerController - .removeListener(_initializedListener); + if (_initializedListener != null) { + widget.betterPlayerController.videoPlayerController! + .removeListener(_initializedListener!); + } + _controllerEventSubscription?.cancel(); super.dispose(); } } diff --git a/lib/src/hls/better_player_hls_audio_track.dart b/lib/src/hls/better_player_hls_audio_track.dart index 14b69b388..017d2a7c2 100644 --- a/lib/src/hls/better_player_hls_audio_track.dart +++ b/lib/src/hls/better_player_hls_audio_track.dart @@ -1,16 +1,16 @@ ///Representation of HLS audio track class BetterPlayerHlsAudioTrack { ///Id of track inside HLS playlist - final int id; + final int? id; ///Description of the audio - final String label; + final String? label; ///Language code - final String language; + final String? language; ///Url of audio track - final String url; + final String? url; BetterPlayerHlsAudioTrack({this.id, this.label, this.language, this.url}); } diff --git a/lib/src/hls/better_player_hls_subtitle.dart b/lib/src/hls/better_player_hls_subtitle.dart index f999aac06..0197b7f20 100644 --- a/lib/src/hls/better_player_hls_subtitle.dart +++ b/lib/src/hls/better_player_hls_subtitle.dart @@ -1,16 +1,16 @@ ///Representation of HLS subtitle element. class BetterPlayerHlsSubtitle { ///Language of the subtitle - final String language; + final String? language; ///Name of the subtitle - final String name; + final String? name; ///Url of the subtitle (master playlist) - final String url; + final String? url; ///Urls of specific files - final List realUrls; + final List? realUrls; BetterPlayerHlsSubtitle({ this.language, diff --git a/lib/src/hls/better_player_hls_track.dart b/lib/src/hls/better_player_hls_track.dart index 23b1852a3..8d2471b05 100644 --- a/lib/src/hls/better_player_hls_track.dart +++ b/lib/src/hls/better_player_hls_track.dart @@ -1,13 +1,13 @@ /// Represents HLS track which can be played within player class BetterPlayerHlsTrack { ///Width in px of the track - final int width; + final int? width; ///Height in px of the track - final int height; + final int? height; ///Bitrate in px of the track - final int bitrate; + final int? bitrate; BetterPlayerHlsTrack(this.width, this.height, this.bitrate); @@ -16,6 +16,7 @@ class BetterPlayerHlsTrack { } @override + // ignore: unnecessary_overrides int get hashCode => super.hashCode; @override diff --git a/lib/src/hls/better_player_hls_utils.dart b/lib/src/hls/better_player_hls_utils.dart index f8dc34ea3..30f46d687 100644 --- a/lib/src/hls/better_player_hls_utils.dart +++ b/lib/src/hls/better_player_hls_utils.dart @@ -24,7 +24,6 @@ class BetterPlayerHlsUtils { static Future> parseTracks( String data, String masterPlaylistUrl) async { - assert(data != null, "Data can't be null"); final List tracks = []; try { final parsedPlaylist = await HlsPlaylistParser.create() @@ -50,9 +49,6 @@ class BetterPlayerHlsUtils { ///Parse subtitles from provided m3u8 url static Future> parseSubtitles( String data, String masterPlaylistUrl) async { - assert(data != null, "Data can't be null"); - assert(masterPlaylistUrl != null, "MasterPlaylistUrl can't be null"); - final List subtitles = []; try { final parsedPlaylist = await HlsPlaylistParser.create() @@ -72,11 +68,14 @@ class BetterPlayerHlsUtils { return subtitles; } - static Future _parseSubtitlesPlaylist( + static Future _parseSubtitlesPlaylist( Rendition rendition) async { - assert(rendition != null, "Rendition can't be null"); try { final subtitleData = await getDataFromUrl(rendition.url.toString()); + if (subtitleData == null) { + return null; + } + final parsedSubtitle = await _hlsPlaylistParser.parseString(rendition.url, subtitleData); final hlsMediaPlaylist = parsedSubtitle as HlsMediaPlaylist; @@ -89,7 +88,7 @@ class BetterPlayerHlsUtils { // ignore: use_string_buffers realUrl += "${split[index]}/"; } - realUrl += segment.url; + realUrl += segment.url!; hlsSubtitlesUrls.add(realUrl); } return BetterPlayerHlsSubtitle( @@ -105,8 +104,6 @@ class BetterPlayerHlsUtils { static Future> parseLanguages( String data, String masterPlaylistUrl) async { - assert(data != null, "Data can't be null"); - assert(masterPlaylistUrl != null, "MasterPlaylistUrl can't be null"); final List audios = []; final parsedPlaylist = await HlsPlaylistParser.create() .parseString(Uri.parse(masterPlaylistUrl), data); @@ -125,20 +122,19 @@ class BetterPlayerHlsUtils { return audios; } - static Future getDataFromUrl(String url, - [Map headers]) async { + static Future getDataFromUrl(String url, + [Map? headers]) async { try { - assert(url != null, "Url can't be null!"); final request = await _httpClient.getUrl(Uri.parse(url)); if (headers != null) { - headers.forEach((name, value) => request.headers.add(name, value)); + headers.forEach((name, value) => request.headers.add(name, value!)); } final response = await request.close(); var data = ""; - await response.transform(const Utf8Decoder()).listen((contents) { - data += contents.toString(); - }).asFuture(); + await response.transform(const Utf8Decoder()).listen((content) { + data += content.toString(); + }).asFuture(); return data; } catch (exception) { diff --git a/lib/src/hls/hls_parser/drm_init_data.dart b/lib/src/hls/hls_parser/drm_init_data.dart index 9ebad37ae..dbca2a269 100644 --- a/lib/src/hls/hls_parser/drm_init_data.dart +++ b/lib/src/hls/hls_parser/drm_init_data.dart @@ -7,7 +7,7 @@ class DrmInitData { DrmInitData({this.schemeType, this.schemeData = const []}); final List schemeData; - final String schemeType; + final String? schemeType; @override bool operator ==(dynamic other) { diff --git a/lib/src/hls/hls_parser/exception.dart b/lib/src/hls/hls_parser/exception.dart index baaeff086..faa4487b8 100644 --- a/lib/src/hls/hls_parser/exception.dart +++ b/lib/src/hls/hls_parser/exception.dart @@ -10,5 +10,5 @@ class ParserException implements Exception { class UnrecognizedInputFormatException extends ParserException { UnrecognizedInputFormatException(String message, this.uri) : super(message); - final Uri uri; + final Uri? uri; } diff --git a/lib/src/hls/hls_parser/format.dart b/lib/src/hls/hls_parser/format.dart index e9bcc8d03..0e348e79c 100644 --- a/lib/src/hls/hls_parser/format.dart +++ b/lib/src/hls/hls_parser/format.dart @@ -1,6 +1,3 @@ -import 'package:meta/meta.dart'; -import 'package:flutter/cupertino.dart'; - import 'drm_init_data.dart'; import 'metadata.dart'; import 'util.dart'; @@ -24,23 +21,23 @@ class Format { this.height, this.frameRate, this.channelCount, - String language, + String? language, this.accessibilityChannel, }) : language = language?.toLowerCase(); factory Format.createVideoContainerFormat( - {String id, - String label, - String containerMimeType, - String sampleMimeType, - @required String codecs, - int bitrate, - int averageBitrate, - @required int width, - @required int height, - @required double frameRate, + {String? id, + String? label, + String? containerMimeType, + String? sampleMimeType, + required String? codecs, + int? bitrate, + int? averageBitrate, + required int? width, + required int? height, + required double? frameRate, int selectionFlags = Util.selectionFlagDefault, - int roleFlags}) => + int? roleFlags}) => Format( id: id, label: label, @@ -57,62 +54,62 @@ class Format { ); /// An identifier for the format, or null if unknown or not applicable. - final String id; + final String? id; /// The human readable label, or null if unknown or not applicable. - final String label; + final String? label; /// Track selection flags. /// [Util.selectionFlagDefault] or [Util.selectionFlagForced] or [Util.selectionFlagAutoSelect] - final int selectionFlags; + final int? selectionFlags; /// Track role flags. /// [Util.roleFlagDescribesMusicAndSound] or [Util.roleFlagDescribesVideo] or [Util.roleFlagEasyToRead] or [Util.roleFlagTranscribesDialog] - final int roleFlags; + final int? roleFlags; ///Average bandwidth - final int bitrate; + final int? bitrate; /// The average bandwidth in bits per second, or null if unknown or not applicable. - final int averageBitrate; + final int? averageBitrate; /// Codecs of the format as described in RFC 6381, or null if unknown or not applicable. - final String codecs; + final String? codecs; /// Metadata, or null if unknown or not applicable. - final Metadata metadata; + final Metadata? metadata; /// The mime type of the container, or null if unknown or not applicable. - final String containerMimeType; + final String? containerMimeType; ///The mime type of the elementary stream (i.e. the individual samples), or null if unknown or not applicable. - final String sampleMimeType; + final String? sampleMimeType; ///DRM initialization data if the stream is protected, or null otherwise. - final DrmInitData drmInitData; + final DrmInitData? drmInitData; //todo ここ追加で検討 /// For samples that contain subsamples, this is an offset that should be added to subsample timestamps. /// A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are relative to the timestamps of their parent samples. - final int subsampleOffsetUs; + final int? subsampleOffsetUs; /// The width of the video in pixels, or null if unknown or not applicable. - final int width; + final int? width; /// The height of the video in pixels, or null if unknown or not applicable. - final int height; + final int? height; /// The frame rate in frames per second, or null if unknown or not applicable. - final double frameRate; + final double? frameRate; /// The number of audio channels, or null if unknown or not applicable. - final int channelCount; + final int? channelCount; /// The language of the video, or null if unknown or not applicable. - final String language; + final String? language; /// The Accessibility channel, or null if not known or applicable. - final int accessibilityChannel; + final int? accessibilityChannel; Format copyWithMetadata(Metadata metadata) => Format( id: id, diff --git a/lib/src/hls/hls_parser/hls_master_playlist.dart b/lib/src/hls/hls_parser/hls_master_playlist.dart index c7291ea81..0eb1aefbc 100644 --- a/lib/src/hls/hls_parser/hls_master_playlist.dart +++ b/lib/src/hls/hls_parser/hls_master_playlist.dart @@ -6,7 +6,7 @@ import 'variant.dart'; class HlsMasterPlaylist extends HlsPlaylist { HlsMasterPlaylist({ - String baseUri, + String? baseUri, List tags = const [], // ignore: always_specify_types this.variants = const [], // ignore: always_specify_types this.videos = const [], // ignore: always_specify_types @@ -27,7 +27,7 @@ class HlsMasterPlaylist extends HlsPlaylist { hasIndependentSegments: hasIndependentSegments); /// All of the media playlist URLs referenced by the playlist. - final List mediaPlaylistUrls; + final List mediaPlaylistUrls; /// The variants declared by the playlist. final List variants; @@ -45,22 +45,22 @@ class HlsMasterPlaylist extends HlsPlaylist { final List closedCaptions; ///The format of the audio muxed in the variants. May be null if the playlist does not declare any mixed audio. - final Format muxedAudioFormat; + final Format? muxedAudioFormat; ///The format of the closed captions declared by the playlist. May be empty if the playlist ///explicitly declares no captions are available, or null if the playlist does not declare any ///captions information. - final List muxedCaptionFormats; + final List? muxedCaptionFormats; /// Contains variable definitions, as defined by the #EXT-X-DEFINE tag. - final Map variableDefinitions; + final Map variableDefinitions; /// DRM initialization data derived from #EXT-X-SESSION-KEY tags. final List sessionKeyDrmInitData; - static List _getMediaPlaylistUrls( + static List _getMediaPlaylistUrls( List variants, List> renditionList) { - final uriList = []; + final uriList = []; variants.forEach((element) { uriList.add(element.url); }); diff --git a/lib/src/hls/hls_parser/hls_media_playlist.dart b/lib/src/hls/hls_parser/hls_media_playlist.dart index 4bd15ce80..4ad1bb5c3 100644 --- a/lib/src/hls/hls_parser/hls_media_playlist.dart +++ b/lib/src/hls/hls_parser/hls_media_playlist.dart @@ -1,26 +1,25 @@ import 'package:better_player/src/hls/hls_parser/drm_init_data.dart'; import 'package:better_player/src/hls/hls_parser/playlist.dart'; import 'package:better_player/src/hls/hls_parser/segment.dart'; -import 'package:meta/meta.dart'; class HlsMediaPlaylist extends HlsPlaylist { HlsMediaPlaylist._({ - @required this.playlistType, - @required this.startOffsetUs, - @required this.startTimeUs, - @required this.hasDiscontinuitySequence, - @required this.discontinuitySequence, - @required this.mediaSequence, - @required this.version, - @required this.targetDurationUs, - @required this.hasEndTag, - @required this.hasProgramDateTime, - @required this.protectionSchemes, - @required this.segments, - @required this.durationUs, - @required String baseUri, - @required List tags, - @required bool hasIndependentSegments, + required this.playlistType, + required this.startOffsetUs, + required this.startTimeUs, + required this.hasDiscontinuitySequence, + required this.discontinuitySequence, + required this.mediaSequence, + required this.version, + required this.targetDurationUs, + required this.hasEndTag, + required this.hasProgramDateTime, + required this.protectionSchemes, + required this.segments, + required this.durationUs, + required String baseUri, + required List tags, + required bool hasIndependentSegments, }) : super( baseUri: baseUri, tags: tags, @@ -28,24 +27,24 @@ class HlsMediaPlaylist extends HlsPlaylist { ); factory HlsMediaPlaylist.create({ - @required int playlistType, - @required int startOffsetUs, - @required int startTimeUs, - @required bool hasDiscontinuitySequence, - @required int discontinuitySequence, - @required int mediaSequence, - @required int version, - @required int targetDurationUs, - @required bool hasEndTag, - @required bool hasProgramDateTime, - @required DrmInitData protectionSchemes, - @required List segments, - @required String baseUri, - @required List tags, - @required bool hasIndependentSegments, + required int playlistType, + required int? startOffsetUs, + required int? startTimeUs, + required bool hasDiscontinuitySequence, + required int discontinuitySequence, + required int? mediaSequence, + required int? version, + required int? targetDurationUs, + required bool hasEndTag, + required bool hasProgramDateTime, + required DrmInitData? protectionSchemes, + required List segments, + required String baseUri, + required List tags, + required bool hasIndependentSegments, }) { - final int durationUs = segments.isNotEmpty - ? segments.last.relativeStartTimeUs ?? 0 + segments.last.durationUs ?? 0 + final int? durationUs = segments.isNotEmpty + ? segments.last.relativeStartTimeUs ?? 0 + segments.last.durationUs! : null; if (startOffsetUs != null && startOffsetUs < 0) { @@ -80,11 +79,11 @@ class HlsMediaPlaylist extends HlsPlaylist { final int playlistType; /// The start offset in microseconds, as defined by #EXT-X-START, may be null if unknown. - final int startOffsetUs; + final int? startOffsetUs; /// If [hasProgramDateTime] is true, contains the datetime as microseconds since epoch. /// Otherwise, contains the aggregated duration of removed segments up to this snapshot of the playlist. - final int startTimeUs; + final int? startTimeUs; /// Whether the playlist contains the #EXT-X-DISCONTINUITY-SEQUENCE tag. final bool hasDiscontinuitySequence; @@ -93,13 +92,13 @@ class HlsMediaPlaylist extends HlsPlaylist { final int discontinuitySequence; /// The media sequence number of the first media segment in the playlist, as defined by #EXT-X-MEDIA-SEQUENCE, may be null if unknown. - final int mediaSequence; + final int? mediaSequence; /// The compatibility version, as defined by #EXT-X-VERSION, may be null if unknown. - final int version; + final int? version; /// The target duration in microseconds, as defined by #EXT-X-TARGETDURATION, may be null if unknown. - final int targetDurationUs; + final int? targetDurationUs; /// Whether the playlist contains the #EXT-X-ENDLIST tag. final bool hasEndTag; @@ -108,11 +107,11 @@ class HlsMediaPlaylist extends HlsPlaylist { final bool hasProgramDateTime; /// Contains the CDM protection schemes used by segments in this playlist. Does not contain any key acquisition data. Null if none of the segments in the playlist is CDM-encrypted. - final DrmInitData protectionSchemes; + final DrmInitData? protectionSchemes; /// The list of segments in the playlist. final List segments; /// The total duration of the playlist in microseconds, may be null if unknown. - final int durationUs; + final int? durationUs; } diff --git a/lib/src/hls/hls_parser/hls_playlist_parser.dart b/lib/src/hls/hls_parser/hls_playlist_parser.dart index 57499e266..e6bdb2626 100644 --- a/lib/src/hls/hls_parser/hls_playlist_parser.dart +++ b/lib/src/hls/hls_parser/hls_playlist_parser.dart @@ -16,12 +16,12 @@ import 'package:better_player/src/hls/hls_parser/segment.dart'; import 'package:better_player/src/hls/hls_parser/util.dart'; import 'package:better_player/src/hls/hls_parser/variant.dart'; import 'package:better_player/src/hls/hls_parser/variant_info.dart'; -import 'package:meta/meta.dart'; +import 'package:collection/collection.dart' show IterableExtension; class HlsPlaylistParser { HlsPlaylistParser(this.masterPlaylist); - factory HlsPlaylistParser.create({HlsMasterPlaylist masterPlaylist}) { + factory HlsPlaylistParser.create({HlsMasterPlaylist? masterPlaylist}) { masterPlaylist ??= HlsMasterPlaylist(); return HlsPlaylistParser(masterPlaylist); } @@ -112,12 +112,12 @@ class HlsPlaylistParser { final HlsMasterPlaylist masterPlaylist; - Future parseString(Uri uri, String inputString) async { + Future parseString(Uri? uri, String inputString) async { final List lines = const LineSplitter().convert(inputString); return parse(uri, lines); } - Future parse(Uri uri, List inputLineList) async { + Future parse(Uri? uri, List inputLineList) async { final List lineList = inputLineList .where((line) => line.trim().isNotEmpty) // ignore: always_specify_types .toList(); @@ -130,7 +130,7 @@ class HlsPlaylistParser { final List extraLines = lineList.getRange(1, lineList.length).toList(); - bool isMasterPlayList; + bool? isMasterPlayList; for (final line in extraLines) { if (line.startsWith(tagStreamInf)) { isMasterPlayList = true; @@ -189,23 +189,23 @@ class HlsPlaylistParser { final List closedCaptions = []; // ignore: always_specify_types final Map> urlToVariantInfos = {}; // ignore: always_specify_types - Format muxedAudioFormat; + Format? muxedAudioFormat; bool noClosedCaptions = false; bool hasIndependentSegmentsTag = false; - List muxedCaptionFormats; + List? muxedCaptionFormats; - final Map variableDefinitions = + final Map variableDefinitions = {}; // ignore: always_specify_types while (extraLines.moveNext()) { final String line = extraLines.current; if (line.startsWith(tagDefine)) { - final String key = _parseStringAttr( + final String? key = _parseStringAttr( source: line, pattern: regexpName, variableDefinitions: variableDefinitions); - final String val = _parseStringAttr( + final String? val = _parseStringAttr( source: line, pattern: regexpValue, variableDefinitions: variableDefinitions); @@ -221,18 +221,18 @@ class HlsPlaylistParser { } else if (line.startsWith(tagMedia)) { mediaTags.add(line); } else if (line.startsWith(tagSessionKey)) { - final String keyFormat = _parseStringAttr( + final String? keyFormat = _parseStringAttr( source: line, pattern: regexpKeyFormat, defaultValue: keyFormatIdentity, variableDefinitions: variableDefinitions); - final SchemeData schemeData = _parseDrmSchemeData( + final SchemeData? schemeData = _parseDrmSchemeData( line: line, keyFormat: keyFormat, variableDefinitions: variableDefinitions); if (schemeData != null) { - final String method = _parseStringAttr( + final String? method = _parseStringAttr( source: line, pattern: regexpMethod, variableDefinitions: variableDefinitions); @@ -244,26 +244,26 @@ class HlsPlaylistParser { } } else if (line.startsWith(tagStreamInf)) { noClosedCaptions |= line.contains(attrClosedCaptionsNone); //todo 再検討 - final int bitrate = - int.parse(_parseStringAttr(source: line, pattern: regexpBandwidth)); + final int bitrate = int.parse( + _parseStringAttr(source: line, pattern: regexpBandwidth)!); int averageBitrate = 0; - final String averageBandwidthString = _parseStringAttr( + final String? averageBandwidthString = _parseStringAttr( source: line, pattern: regexpAverageBandwidth, variableDefinitions: variableDefinitions); if (averageBandwidthString != null) { averageBitrate = int.parse(averageBandwidthString); } - final String codecs = _parseStringAttr( + final String? codecs = _parseStringAttr( source: line, pattern: regexpCodecs, variableDefinitions: variableDefinitions); - final String resolutionString = _parseStringAttr( + final String? resolutionString = _parseStringAttr( source: line, pattern: regexpResolutions, variableDefinitions: variableDefinitions); - int width; - int height; + int? width; + int? height; if (resolutionString != null) { final List widthAndHeight = resolutionString.split('x'); width = int.parse(widthAndHeight[0]); @@ -275,27 +275,27 @@ class HlsPlaylistParser { } } - double frameRate; - final String frameRateString = _parseStringAttr( + double? frameRate; + final String? frameRateString = _parseStringAttr( source: line, pattern: regexpFrameRate, variableDefinitions: variableDefinitions); if (frameRateString != null) { frameRate = double.parse(frameRateString); } - final String videoGroupId = _parseStringAttr( + final String? videoGroupId = _parseStringAttr( source: line, pattern: regexpVideo, variableDefinitions: variableDefinitions); - final String audioGroupId = _parseStringAttr( + final String? audioGroupId = _parseStringAttr( source: line, pattern: regexpAudio, variableDefinitions: variableDefinitions); - final String subtitlesGroupId = _parseStringAttr( + final String? subtitlesGroupId = _parseStringAttr( source: line, pattern: regexpSubtitles, variableDefinitions: variableDefinitions); - final String closedCaptionsGroupId = _parseStringAttr( + final String? closedCaptionsGroupId = _parseStringAttr( source: line, pattern: regexpClosedCaptions, variableDefinitions: variableDefinitions); @@ -304,7 +304,7 @@ class HlsPlaylistParser { final String referenceUri = _parseStringAttr( source: extraLines.current, - variableDefinitions: variableDefinitions); + variableDefinitions: variableDefinitions)!; final Uri uri = Uri.parse(baseUri).resolve(referenceUri); final Format format = Format.createVideoContainerFormat( @@ -326,7 +326,7 @@ class HlsPlaylistParser { captionGroupId: closedCaptionsGroupId, )); - List variantInfosForUrl = urlToVariantInfos[uri]; + List? variantInfosForUrl = urlToVariantInfos[uri]; if (variantInfosForUrl == null) { variantInfosForUrl = []; // ignore: always_specify_types urlToVariantInfos[uri] = variantInfosForUrl; @@ -361,15 +361,15 @@ class HlsPlaylistParser { // ignore: always_specify_types mediaTags.forEach((line) { - final String groupId = _parseStringAttr( + final String? groupId = _parseStringAttr( source: line, pattern: regexpGroupId, variableDefinitions: variableDefinitions); - final String name = _parseStringAttr( + final String? name = _parseStringAttr( source: line, pattern: regexpName, variableDefinitions: variableDefinitions); - final String referenceUri = _parseStringAttr( + final String? referenceUri = _parseStringAttr( source: line, pattern: regexpUri, variableDefinitions: variableDefinitions); @@ -377,7 +377,7 @@ class HlsPlaylistParser { Uri uri = Uri.parse(baseUri); if (referenceUri != null) uri = uri.resolve(referenceUri); - final String language = _parseStringAttr( + final String? language = _parseStringAttr( source: line, pattern: regexpLanguage, variableDefinitions: variableDefinitions); @@ -395,13 +395,12 @@ class HlsPlaylistParser { variableDefinitions: variableDefinitions)) { case typeVideo: { - final Variant variant = variants.firstWhere( - (it) => it.videoGroupId == groupId, - orElse: () => null); - String codecs; - int width; - int height; - double frameRate; + final Variant? variant = + variants.firstWhereOrNull((it) => it.videoGroupId == groupId); + String? codecs; + int? width; + int? height; + double? frameRate; if (variant != null) { final Format variantFormat = variant.format; codecs = LibUtil.getCodecsOfType( @@ -410,7 +409,7 @@ class HlsPlaylistParser { height = variantFormat.height; frameRate = variantFormat.frameRate; } - final String sampleMimeType = + final String? sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; format = Format.createVideoContainerFormat( @@ -436,15 +435,15 @@ class HlsPlaylistParser { } case typeAudio: { - final Variant variant = + final Variant? variant = _getVariantWithAudioGroup(variants, groupId); - final String codecs = variant != null + final String? codecs = variant != null ? LibUtil.getCodecsOfType( variant.format.codecs, Util.trackTypeAudio) : null; - final int channelCount = + final int? channelCount = _parseChannelsAttribute(line, variableDefinitions); - final String sampleMimeType = + final String? sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; final Format format = Format( id: formatId, @@ -458,6 +457,7 @@ class HlsPlaylistParser { language: language, ); + // ignore: unnecessary_null_comparison if (uri == null) { muxedAudioFormat = format; } else { @@ -494,7 +494,7 @@ class HlsPlaylistParser { final String instreamId = _parseStringAttr( source: line, pattern: regexpInStreamId, - variableDefinitions: variableDefinitions); + variableDefinitions: variableDefinitions)!; String mimeType; int accessibilityChannel; if (instreamId.startsWith('CC')) { @@ -506,7 +506,7 @@ class HlsPlaylistParser { accessibilityChannel = int.parse(instreamId.substring(7)); } muxedCaptionFormats ??= []; // ignore: always_specify_types - muxedCaptionFormats.add(Format( + muxedCaptionFormats!.add(Format( id: formatId, label: name, sampleMimeType: mimeType, @@ -541,31 +541,31 @@ class HlsPlaylistParser { sessionKeyDrmInitData: sessionKeyDrmInitData); } - static String _parseStringAttr({ - @required String source, - String pattern, - String defaultValue, - Map variableDefinitions, + static String? _parseStringAttr({ + required String? source, + String? pattern, + String? defaultValue, + Map? variableDefinitions, }) { - String value; + String? value; if (pattern == null) { value = source; } else { - value = RegExp(pattern).firstMatch(source)?.group(1); + value = RegExp(pattern).firstMatch(source!)?.group(1); value ??= defaultValue; } return value?.replaceAllMapped( RegExp(regexpVariableReference), - (Match match) => variableDefinitions[match.group(1)] ??= - value.substring(match.start, match.end)); + (Match match) => variableDefinitions![match.group(1)] ??= + value!.substring(match.start, match.end)); } - static SchemeData _parseDrmSchemeData( - {String line, - String keyFormat, - Map variableDefinitions}) { - final String keyFormatVersions = _parseStringAttr( + static SchemeData? _parseDrmSchemeData( + {String? line, + String? keyFormat, + Map? variableDefinitions}) { + final String? keyFormatVersions = _parseStringAttr( source: line, pattern: regexpKeyFormatVersions, defaultValue: '1', @@ -576,7 +576,7 @@ class HlsPlaylistParser { final String uriString = _parseStringAttr( source: line, pattern: regexpUri, - variableDefinitions: variableDefinitions); + variableDefinitions: variableDefinitions)!; final Uint8List data = _getBase64FromUri(uriString); return SchemeData( // uuid: '', //todo 保留 @@ -586,12 +586,12 @@ class HlsPlaylistParser { return SchemeData( // uuid: '', //todo 保留 mimeType: MimeTypes.hls, - data: const Utf8Encoder().convert(line)); + data: const Utf8Encoder().convert(line!)); } else if (keyFormatPlayReady == keyFormat && '1' == keyFormatVersions) { final String uriString = _parseStringAttr( source: line, pattern: regexpUri, - variableDefinitions: variableDefinitions); + variableDefinitions: variableDefinitions)!; final Uint8List data = _getBase64FromUri(uriString); // Uint8List psshData; //todo 保留 return SchemeData(mimeType: MimeTypes.videoMp4, data: data); @@ -618,22 +618,23 @@ class HlsPlaylistParser { } static bool parseOptionalBooleanAttribute({ - @required String line, - @required String pattern, - @required bool defaultValue, + required String line, + required String pattern, + required bool defaultValue, }) { final List list = line.allMatches(pattern).toList(); return list.isEmpty ? defaultValue : list.first.pattern == booleanTrue; } static int _parseRoleFlags( - String line, Map variableDefinitions) { - final String concatenatedCharacteristics = _parseStringAttr( + String line, Map variableDefinitions) { + final String? concatenatedCharacteristics = _parseStringAttr( source: line, pattern: regexpCharacteristics, variableDefinitions: variableDefinitions); if (concatenatedCharacteristics?.isEmpty != false) return 0; - final List characteristics = concatenatedCharacteristics.split(','); + final List characteristics = + concatenatedCharacteristics!.split(','); int roleFlags = 0; if (characteristics.contains('public.accessibility.describes-video')) { roleFlags |= Util.roleFlagDescribesVideo; @@ -656,9 +657,9 @@ class HlsPlaylistParser { return roleFlags; } - static int _parseChannelsAttribute( - String line, Map variableDefinitions) { - final String channelsString = _parseStringAttr( + static int? _parseChannelsAttribute( + String line, Map variableDefinitions) { + final String? channelsString = _parseStringAttr( source: line, pattern: regexpChannels, variableDefinitions: variableDefinitions); @@ -667,15 +668,15 @@ class HlsPlaylistParser { : null; } - static Variant _getVariantWithAudioGroup( - List variants, String groupId) { + static Variant? _getVariantWithAudioGroup( + List variants, String? groupId) { for (final variant in variants) { if (variant.audioGroupId == groupId) return variant; } return null; } - static String _parseEncryptionScheme(String method) => + static String _parseEncryptionScheme(String? method) => methodSampleAesCenc == method || methodSampleAesCtr == method ? CencType.cenc : CencType.cnbs; @@ -688,35 +689,35 @@ class HlsPlaylistParser { static HlsMediaPlaylist _parseMediaPlaylist(HlsMasterPlaylist masterPlaylist, List extraLines, String baseUri) { int playlistType = HlsMediaPlaylist.playlistTypeUnknown; - int startOffsetUs; - int mediaSequence; - int version; - int targetDurationUs; + int? startOffsetUs; + int? mediaSequence; + int? version; + int? targetDurationUs; bool hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments; bool hasEndTag = false; - int segmentByteRangeOffset; - Segment initializationSegment; - final Map variableDefinitions = {}; + int? segmentByteRangeOffset; + Segment? initializationSegment; + final Map variableDefinitions = {}; final List segments = []; final List tags = []; // ignore: always_specify_types - int segmentByteRangeLength; - int segmentMediaSequence = 0; - int segmentDurationUs; - String segmentTitle; - final Map currentSchemeDatas = + int? segmentByteRangeLength; + int? segmentMediaSequence = 0; + int? segmentDurationUs; + String? segmentTitle; + final Map currentSchemeDatas = {}; // ignore: always_specify_types - DrmInitData cachedDrmInitData; - String encryptionScheme; - DrmInitData playlistProtectionSchemes; + DrmInitData? cachedDrmInitData; + String? encryptionScheme; + DrmInitData? playlistProtectionSchemes; bool hasDiscontinuitySequence = false; int playlistDiscontinuitySequence = 0; - int relativeDiscontinuitySequence; - int playlistStartTimeUs; - int segmentStartTimeUs; + int? relativeDiscontinuitySequence; + int? playlistStartTimeUs; + int? segmentStartTimeUs; bool hasGapTag = false; - String fullSegmentEncryptionKeyUri; - String fullSegmentEncryptionIV; + String? fullSegmentEncryptionKeyUri; + String? fullSegmentEncryptionIV; for (final line in extraLines) { if (line.startsWith(tagPrefix)) { @@ -725,7 +726,7 @@ class HlsPlaylistParser { } if (line.startsWith(tagPlaylistType)) { - final String playlistTypeString = _parseStringAttr( + final String? playlistTypeString = _parseStringAttr( source: line, pattern: regexpPlaylistType, variableDefinitions: variableDefinitions); @@ -738,14 +739,14 @@ class HlsPlaylistParser { final String string = _parseStringAttr( source: line, pattern: regexpTimeOffset, - variableDefinitions: {}); // ignore: always_specify_types + variableDefinitions: {})!; // ignore: always_specify_types startOffsetUs = (double.parse(string) * 1000000).toInt(); } else if (line.startsWith(tagInitSegment)) { - final String uri = _parseStringAttr( + final String? uri = _parseStringAttr( source: line, pattern: regexpUri, variableDefinitions: variableDefinitions); - final String byteRange = _parseStringAttr( + final String? byteRange = _parseStringAttr( source: line, pattern: regexpAttrByteRange, variableDefinitions: variableDefinitions); @@ -772,34 +773,34 @@ class HlsPlaylistParser { segmentByteRangeOffset = null; segmentByteRangeLength = null; } else if (line.startsWith(tagTargetDuration)) { - targetDurationUs = int.parse( - _parseStringAttr(source: line, pattern: regexpTargetDuration)) * + targetDurationUs = int.parse(_parseStringAttr( + source: line, pattern: regexpTargetDuration)!) * 100000; } else if (line.startsWith(tagMediaSequence)) { mediaSequence = int.parse( - _parseStringAttr(source: line, pattern: regexpMediaSequence)); + _parseStringAttr(source: line, pattern: regexpMediaSequence)!); segmentMediaSequence = mediaSequence; } else if (line.startsWith(tagVersion)) { version = - int.parse(_parseStringAttr(source: line, pattern: regexpVersion)); + int.parse(_parseStringAttr(source: line, pattern: regexpVersion)!); } else if (line.startsWith(tagDefine)) { - final String importName = _parseStringAttr( + final String? importName = _parseStringAttr( source: line, pattern: regexpImport, variableDefinitions: variableDefinitions); if (importName != null) { - final String value = masterPlaylist.variableDefinitions[importName]; + final String? value = masterPlaylist.variableDefinitions[importName]; if (value != null) { variableDefinitions[importName] = value; } else { // The master playlist does not declare the imported variable. Ignore. } } else { - final String key = _parseStringAttr( + final String? key = _parseStringAttr( source: line, pattern: regexpName, variableDefinitions: variableDefinitions); - final String value = _parseStringAttr( + final String? value = _parseStringAttr( source: line, pattern: regexpValue, variableDefinitions: variableDefinitions); @@ -807,7 +808,7 @@ class HlsPlaylistParser { } } else if (line.startsWith(tagMediaDuration)) { final String string = - _parseStringAttr(source: line, pattern: regexpMediaDuration); + _parseStringAttr(source: line, pattern: regexpMediaDuration)!; segmentDurationUs = (double.parse(string) * 1000000).toInt(); segmentTitle = _parseStringAttr( source: line, @@ -815,11 +816,11 @@ class HlsPlaylistParser { defaultValue: '', variableDefinitions: variableDefinitions); } else if (line.startsWith(tagKey)) { - final String method = _parseStringAttr( + final String? method = _parseStringAttr( source: line, pattern: regexpMethod, variableDefinitions: variableDefinitions); - final String keyFormat = _parseStringAttr( + final String? keyFormat = _parseStringAttr( source: line, pattern: regexpKeyFormat, defaultValue: keyFormatIdentity, @@ -848,7 +849,7 @@ class HlsPlaylistParser { } } else { encryptionScheme ??= _parseEncryptionScheme(method); - final SchemeData schemeData = _parseDrmSchemeData( + final SchemeData? schemeData = _parseDrmSchemeData( line: line, keyFormat: keyFormat, variableDefinitions: variableDefinitions); @@ -862,7 +863,7 @@ class HlsPlaylistParser { final String byteRange = _parseStringAttr( source: line, pattern: regexpByteRange, - variableDefinitions: variableDefinitions); + variableDefinitions: variableDefinitions)!; final List splitByteRange = byteRange.split('@'); segmentByteRangeLength = int.parse(splitByteRange[0]); if (splitByteRange.length > 1) { @@ -888,19 +889,19 @@ class HlsPlaylistParser { } else if (line == tagEndList) { hasEndTag = true; } else if (!line.startsWith('#')) { - String segmentEncryptionIV; + String? segmentEncryptionIV; if (fullSegmentEncryptionKeyUri == null) { segmentEncryptionIV = null; } else if (fullSegmentEncryptionIV != null) { segmentEncryptionIV = fullSegmentEncryptionIV; } else { - segmentEncryptionIV = segmentMediaSequence.toRadixString(16); + segmentEncryptionIV = segmentMediaSequence!.toRadixString(16); } + segmentMediaSequence = segmentMediaSequence! + 1; - segmentMediaSequence++; if (segmentByteRangeLength == null) segmentByteRangeOffset = null; - if (cachedDrmInitData?.schemeData?.isNotEmpty != true && + if (cachedDrmInitData?.schemeData.isNotEmpty != true && currentSchemeDatas.isNotEmpty) { final List schemeDatas = currentSchemeDatas.values.toList(); @@ -914,7 +915,7 @@ class HlsPlaylistParser { } } - final String url = _parseStringAttr( + final String? url = _parseStringAttr( source: line, variableDefinitions: variableDefinitions); segments.add(Segment( url: url, diff --git a/lib/src/hls/hls_parser/hls_track_metadata_entry.dart b/lib/src/hls/hls_parser/hls_track_metadata_entry.dart index 7c0645ddd..9d7d215a6 100644 --- a/lib/src/hls/hls_parser/hls_track_metadata_entry.dart +++ b/lib/src/hls/hls_parser/hls_track_metadata_entry.dart @@ -7,14 +7,14 @@ class HlsTrackMetadataEntry { /// The GROUP-ID value of this track, if the track is derived from an EXT-X-MEDIA tag. Null if the /// track is not derived from an EXT-X-MEDIA TAG. - final String groupId; + final String? groupId; /// The NAME value of this track, if the track is derived from an EXT-X-MEDIA tag. Null if the /// track is not derived from an EXT-X-MEDIA TAG. - final String name; + final String? name; /// The EXT-X-STREAM-INF tags attributes associated with this track. This field is non-applicable (and therefore empty) if this track is derived from an EXT-X-MEDIA tag. - final List variantInfos; + final List? variantInfos; @override bool operator ==(dynamic other) { diff --git a/lib/src/hls/hls_parser/mime_types.dart b/lib/src/hls/hls_parser/mime_types.dart index a404c4bb6..c17711be7 100644 --- a/lib/src/hls/hls_parser/mime_types.dart +++ b/lib/src/hls/hls_parser/mime_types.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'util.dart'; class MimeTypes { @@ -77,7 +76,7 @@ class MimeTypes { static final List _customMimeTypes = []; - static String _getMimeTypeFromMp4ObjectType(int objectType) { + static String? _getMimeTypeFromMp4ObjectType(int objectType) { switch (objectType) { case 0x20: return MimeTypes.videoMp4v; @@ -125,9 +124,8 @@ class MimeTypes { } } - static String getMediaMimeType(String codecValue) { + static String? getMediaMimeType(String codecValue) { String codec = codecValue; - if (codec == null) return null; codec = codec.trim().toLowerCase(); if (codec.startsWith('avc1') || codec.startsWith('avc3')) { @@ -152,7 +150,7 @@ class MimeTypes { return MimeTypes.videoVp8; } if (codec.startsWith('mp4a')) { - String mimeType; + String? mimeType; if (codec.startsWith('mp4a.')) { final String objectTypeString = codec.substring(5); if (objectTypeString.length >= 2) { @@ -196,7 +194,7 @@ class MimeTypes { return getCustomMimeTypeForCodec(codec); } - static String getCustomMimeTypeForCodec(String codec) { + static String? getCustomMimeTypeForCodec(String codec) { for (final customMimeType in _customMimeTypes) { if (codec.startsWith(customMimeType.codecPrefix)) { return customMimeType.mimeType; @@ -206,7 +204,7 @@ class MimeTypes { return null; } - static int getTrackType(String mimeType) { + static int getTrackType(String? mimeType) { if (mimeType?.isNotEmpty == false) return Util.trackTypeUnknown; if (isAudio(mimeType)) return Util.trackTypeAudio; @@ -235,7 +233,7 @@ class MimeTypes { } } - static int getTrackTypeForCustomMimeType(String mimeType) { + static int getTrackTypeForCustomMimeType(String? mimeType) { for (final it in _customMimeTypes) { if (it.mimeType == mimeType) return it.trackType; } @@ -243,20 +241,20 @@ class MimeTypes { return Util.trackTypeUnknown; } - static String getTopLevelType(String mimeType) { + static String? getTopLevelType(String? mimeType) { if (mimeType == null) return null; final int indexOfSlash = mimeType.indexOf('/'); if (indexOfSlash == -1) return null; return mimeType.substring(0, indexOfSlash); } - static bool isAudio(String mimeType) => + static bool isAudio(String? mimeType) => baseTypeAudio == getTopLevelType(mimeType); - static bool isVideo(String mimeType) => + static bool isVideo(String? mimeType) => baseTypeVideo == getTopLevelType(mimeType); - static bool isText(String mimeType) => + static bool isText(String? mimeType) => baseTypeText == getTopLevelType(mimeType); static int getTrackTypeOfCodec(String codec) { @@ -266,9 +264,9 @@ class MimeTypes { class CustomMimeType { CustomMimeType({ - @required this.mimeType, - @required this.codecPrefix, - @required this.trackType, + required this.mimeType, + required this.codecPrefix, + required this.trackType, }); final String mimeType; diff --git a/lib/src/hls/hls_parser/playlist.dart b/lib/src/hls/hls_parser/playlist.dart index 51eff355c..a409fc73c 100644 --- a/lib/src/hls/hls_parser/playlist.dart +++ b/lib/src/hls/hls_parser/playlist.dart @@ -1,14 +1,12 @@ -import 'package:meta/meta.dart'; - abstract class HlsPlaylist { HlsPlaylist({ - @required this.baseUri, - @required this.tags, - @required this.hasIndependentSegments, + required this.baseUri, + required this.tags, + required this.hasIndependentSegments, }); /// The base uri. Used to resolve relative paths. - final String baseUri; + final String? baseUri; /// The list of tags in the playlist. final List tags; diff --git a/lib/src/hls/hls_parser/rendition.dart b/lib/src/hls/hls_parser/rendition.dart index 883ad5ce8..1033c4e14 100644 --- a/lib/src/hls/hls_parser/rendition.dart +++ b/lib/src/hls/hls_parser/rendition.dart @@ -1,22 +1,21 @@ import 'package:better_player/src/hls/hls_parser/format.dart'; -import 'package:meta/meta.dart'; class Rendition { Rendition( {this.url, - @required this.format, - @required this.groupId, - @required this.name}); + required this.format, + required this.groupId, + required this.name}); /// The rendition's url, or null if the tag does not have a URI attribute. - final Uri url; + final Uri? url; /// Format information associated with this rendition. final Format format; /// The group to which this rendition belongs. - final String groupId; + final String? groupId; /// The name of the rendition. - final String name; + final String? name; } diff --git a/lib/src/hls/hls_parser/scheme_data.dart b/lib/src/hls/hls_parser/scheme_data.dart index 4cdf67c97..49557dde0 100644 --- a/lib/src/hls/hls_parser/scheme_data.dart +++ b/lib/src/hls/hls_parser/scheme_data.dart @@ -1,13 +1,12 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:meta/meta.dart'; class SchemeData { SchemeData({ // @required this.uuid, this.licenseServerUrl, - @required this.mimeType, + required this.mimeType, this.data, this.requiresSecureDecryption, }); @@ -16,19 +15,19 @@ class SchemeData { // final String uuid; /// The URL of the server to which license requests should be made. May be null if unknown. - final String licenseServerUrl; + final String? licenseServerUrl; /// The mimeType of [data]. final String mimeType; /// The initialization base data. /// you should build pssh manually for use. - final Uint8List data; + final Uint8List? data; /// Whether secure decryption is required. - final bool requiresSecureDecryption; + final bool? requiresSecureDecryption; - SchemeData copyWithData(Uint8List data) => SchemeData( + SchemeData copyWithData(Uint8List? data) => SchemeData( // uuid: uuid, licenseServerUrl: licenseServerUrl, mimeType: mimeType, diff --git a/lib/src/hls/hls_parser/segment.dart b/lib/src/hls/hls_parser/segment.dart index 3eb9c90dc..0e3e2796f 100644 --- a/lib/src/hls/hls_parser/segment.dart +++ b/lib/src/hls/hls_parser/segment.dart @@ -1,54 +1,53 @@ import 'package:better_player/src/hls/hls_parser/drm_init_data.dart'; -import 'package:meta/meta.dart'; class Segment { Segment({ - @required this.url, + required this.url, this.initializationSegment, this.durationUs, this.title, this.relativeDiscontinuitySequence, this.relativeStartTimeUs, this.drmInitData, - @required this.fullSegmentEncryptionKeyUri, - @required this.encryptionIV, - @required this.byterangeOffset, - @required this.byterangeLength, + required this.fullSegmentEncryptionKeyUri, + required this.encryptionIV, + required this.byterangeOffset, + required this.byterangeLength, this.hasGapTag = false, }); - final String url; + final String? url; /// The media initialization section for this segment, as defined by #EXT-X-MAP. May be null if the media playlist does not define a media section for this segment. /// The same instance is used for all segments that share an EXT-X-MAP tag. - final Segment initializationSegment; + final Segment? initializationSegment; /// The duration of the segment in microseconds, as defined by #EXTINF. - final int durationUs; + final int? durationUs; /// The human readable title of the segment, or null if the title is unknown. - final String title; + final String? title; /// The number of #EXT-X-DISCONTINUITY tags in the playlist before the segment, or null if the it's unknown. - final int relativeDiscontinuitySequence; + final int? relativeDiscontinuitySequence; /// The start time of the segment in microseconds, relative to the start of the playlist, or null if the it's unknown. - final int relativeStartTimeUs; + final int? relativeStartTimeUs; /// DRM initialization data for sample decryption, or null if the segment does not use CDM-DRM protection. - final DrmInitData drmInitData; + final DrmInitData? drmInitData; /// The encryption identity key uri as defined by #EXT-X-KEY, or null if the segment does not use full segment encryption with identity key. - final String fullSegmentEncryptionKeyUri; + final String? fullSegmentEncryptionKeyUri; /// The encryption initialization vector as defined by #EXT-X-KEY, or null if the segment is not encrypted. - final String encryptionIV; + final String? encryptionIV; /// The segment's byte range offset, as defined by #EXT-X-BYTERANGE. - final int byterangeOffset; + final int? byterangeOffset; /// The segment's byte range length, as defined by #EXT-X-BYTERANGE, or null if no byte range is specified. - final int byterangeLength; + final int? byterangeLength; /// Whether the segment is tagged with #EXT-X-GAP. final bool hasGapTag; diff --git a/lib/src/hls/hls_parser/util.dart b/lib/src/hls/hls_parser/util.dart index 12d8d5e35..5186175ca 100644 --- a/lib/src/hls/hls_parser/util.dart +++ b/lib/src/hls/hls_parser/util.dart @@ -37,7 +37,7 @@ class LibUtil { static bool isLineBreak(int codeUnit) => (codeUnit == '\n'.codeUnitAt(0)) || (codeUnit == '\r'.codeUnitAt(0)); - static String getCodecsOfType(String codecs, int trackType) { + static String? getCodecsOfType(String? codecs, int trackType) { final output = Util.splitCodecs(codecs) .where((codec) => trackType == MimeTypes.getTrackTypeOfCodec(codec)) .join(','); @@ -60,18 +60,18 @@ class LibUtil { timezoneShift = 0; } else { timezoneShift = - int.parse(match.group(12)) * 60 + int.parse(match.group(13)); + int.parse(match.group(12)!) * 60 + int.parse(match.group(13)!); if ('-' == match.group(11)) timezoneShift *= -1; } //todo UTCではなくGMT? final DateTime dateTime = DateTime.utc( - int.parse(match.group(1)), - int.parse(match.group(2)), - int.parse(match.group(3)), - int.parse(match.group(4)), - int.parse(match.group(5)), - int.parse(match.group(6))); + int.parse(match.group(1)!), + int.parse(match.group(2)!), + int.parse(match.group(3)!), + int.parse(match.group(4)!), + int.parse(match.group(5)!), + int.parse(match.group(6)!)); if (match.group(8)?.isNotEmpty == true) { //todo ここ実装再検討 } @@ -85,9 +85,7 @@ class LibUtil { } static int msToUs(int timeMs) => - (timeMs == null || timeMs == Util.timeEndOfSource) - ? timeMs - : (timeMs * 1000); + (timeMs == Util.timeEndOfSource) ? timeMs : (timeMs * 1000); } class Util { @@ -125,9 +123,9 @@ class Util { static const int timeEndOfSource = 0; - static List splitCodecs(String codecs) => codecs?.isNotEmpty != true + static List splitCodecs(String? codecs) => codecs?.isNotEmpty != true ? [] - : codecs.trim().split(RegExp('(\\s*,\\s*)')); + : codecs!.trim().split(RegExp('(\\s*,\\s*)')); } class CencType { diff --git a/lib/src/hls/hls_parser/variant.dart b/lib/src/hls/hls_parser/variant.dart index ea031b03d..90d4b8a0e 100644 --- a/lib/src/hls/hls_parser/variant.dart +++ b/lib/src/hls/hls_parser/variant.dart @@ -1,14 +1,13 @@ import 'package:better_player/src/hls/hls_parser/format.dart'; -import 'package:meta/meta.dart'; class Variant { Variant({ - @required this.url, - @required this.format, - @required this.videoGroupId, - @required this.audioGroupId, - @required this.subtitleGroupId, - @required this.captionGroupId, + required this.url, + required this.format, + required this.videoGroupId, + required this.audioGroupId, + required this.subtitleGroupId, + required this.captionGroupId, }); /// The variant's url. @@ -18,16 +17,16 @@ class Variant { final Format format; /// The video rendition group referenced by this variant, or {@code null}. - final String videoGroupId; + final String? videoGroupId; /// The audio rendition group referenced by this variant, or {@code null}. - final String audioGroupId; + final String? audioGroupId; /// The subtitle rendition group referenced by this variant, or {@code null}. - final String subtitleGroupId; + final String? subtitleGroupId; /// The caption rendition group referenced by this variant, or {@code null}. - final String captionGroupId; + final String? captionGroupId; /// Returns a copy of this instance with the given {@link Format}. Variant copyWithFormat(Format format) => Variant( diff --git a/lib/src/hls/hls_parser/variant_info.dart b/lib/src/hls/hls_parser/variant_info.dart index 7e19b7ef9..74c4ecb38 100644 --- a/lib/src/hls/hls_parser/variant_info.dart +++ b/lib/src/hls/hls_parser/variant_info.dart @@ -10,23 +10,23 @@ class VariantInfo { }); /// The bitrate as declared by the EXT-X-STREAM-INF tag. */ - final int bitrate; + final int? bitrate; /// The VIDEO value as defined in the EXT-X-STREAM-INF tag, or null if the VIDEO attribute is not /// present. - final String videoGroupId; + final String? videoGroupId; /// The AUDIO value as defined in the EXT-X-STREAM-INF tag, or null if the AUDIO attribute is not /// present. - final String audioGroupId; + final String? audioGroupId; /// The SUBTITLES value as defined in the EXT-X-STREAM-INF tag, or null if the SUBTITLES /// attribute is not present. - final String subtitleGroupId; + final String? subtitleGroupId; /// The CLOSED-CAPTIONS value as defined in the EXT-X-STREAM-INF tag, or null if the /// CLOSED-CAPTIONS attribute is not present. - final String captionGroupId; + final String? captionGroupId; @override bool operator ==(dynamic other) { diff --git a/lib/src/list/better_player_list_video_player.dart b/lib/src/list/better_player_list_video_player.dart index 41420d5d3..884eb9e89 100644 --- a/lib/src/list/better_player_list_video_player.dart +++ b/lib/src/list/better_player_list_video_player.dart @@ -26,7 +26,7 @@ class BetterPlayerListVideoPlayer extends StatefulWidget { ///Flag to determine if video should be auto paused final bool autoPause; - final BetterPlayerListVideoPlayerController + final BetterPlayerListVideoPlayerController? betterPlayerListVideoPlayerController; const BetterPlayerListVideoPlayer( @@ -36,14 +36,9 @@ class BetterPlayerListVideoPlayer extends StatefulWidget { this.autoPlay = true, this.autoPause = true, this.betterPlayerListVideoPlayerController, - Key key, - }) : assert(dataSource != null, "Data source can't be null"), - assert(configuration != null, "Configuration can't be null"), - assert( - playFraction != null && playFraction >= 0.0 && playFraction <= 1.0, + Key? key, + }) : assert(playFraction >= 0.0 && playFraction <= 1.0, "Play fraction can't be null and must be between 0.0 and 1.0"), - assert(autoPlay != null, "Auto play can't be null"), - assert(autoPause != null, "Auto pause can't be null"), super(key: key); @override @@ -54,7 +49,7 @@ class BetterPlayerListVideoPlayer extends StatefulWidget { class _BetterPlayerListVideoPlayerState extends State with AutomaticKeepAliveClientMixin { - BetterPlayerController _betterPlayerController; + BetterPlayerController? _betterPlayerController; bool _isDisposing = false; @override @@ -70,14 +65,14 @@ class _BetterPlayerListVideoPlayerState ); if (widget.betterPlayerListVideoPlayerController != null) { - widget.betterPlayerListVideoPlayerController + widget.betterPlayerListVideoPlayerController! .setBetterPlayerController(_betterPlayerController); } } @override void dispose() { - _betterPlayerController.dispose(); + _betterPlayerController!.dispose(); _isDisposing = true; super.dispose(); } @@ -86,25 +81,25 @@ class _BetterPlayerListVideoPlayerState Widget build(BuildContext context) { super.build(context); return AspectRatio( - aspectRatio: _betterPlayerController.getAspectRatio() ?? + aspectRatio: _betterPlayerController!.getAspectRatio() ?? BetterPlayerUtils.calculateAspectRatio(context), child: BetterPlayer( key: Key("${_getUniqueKey()}_player"), - controller: _betterPlayerController, + controller: _betterPlayerController!, ), ); } void onVisibilityChanged(double visibleFraction) async { - final bool isPlaying = _betterPlayerController.isPlaying(); - final bool initialized = _betterPlayerController.isVideoInitialized(); + final bool? isPlaying = _betterPlayerController!.isPlaying(); + final bool? initialized = _betterPlayerController!.isVideoInitialized(); if (visibleFraction >= widget.playFraction) { - if (widget.autoPlay && initialized && !isPlaying && !_isDisposing) { - _betterPlayerController.play(); + if (widget.autoPlay && initialized! && !isPlaying! && !_isDisposing) { + _betterPlayerController!.play(); } } else { - if (widget.autoPause && initialized && isPlaying && !_isDisposing) { - _betterPlayerController.pause(); + if (widget.autoPause && initialized! && isPlaying! && !_isDisposing) { + _betterPlayerController!.pause(); } } } diff --git a/lib/src/list/better_player_list_video_player_controller.dart b/lib/src/list/better_player_list_video_player_controller.dart index 78abb1bc3..278701d75 100644 --- a/lib/src/list/better_player_list_video_player_controller.dart +++ b/lib/src/list/better_player_list_video_player_controller.dart @@ -3,10 +3,9 @@ import 'package:better_player/better_player.dart'; ///Controller of Better Player List Video Player. class BetterPlayerListVideoPlayerController { - BetterPlayerController _betterPlayerController; + BetterPlayerController? _betterPlayerController; void setVolume(double volume) { - assert(volume != null, "Volume can't be null"); _betterPlayerController?.setVolume(volume); } @@ -24,7 +23,7 @@ class BetterPlayerListVideoPlayerController { // ignore: use_setters_to_change_properties void setBetterPlayerController( - BetterPlayerController betterPlayerController) { + BetterPlayerController? betterPlayerController) { _betterPlayerController = betterPlayerController; } } diff --git a/lib/src/playlist/better_player_playlist.dart b/lib/src/playlist/better_player_playlist.dart index fe495175a..2f08d1719 100644 --- a/lib/src/playlist/better_player_playlist.dart +++ b/lib/src/playlist/better_player_playlist.dart @@ -16,17 +16,11 @@ class BetterPlayerPlaylist extends StatefulWidget { final BetterPlayerPlaylistConfiguration betterPlayerPlaylistConfiguration; const BetterPlayerPlaylist({ - Key key, - @required this.betterPlayerDataSourceList, - @required this.betterPlayerConfiguration, - @required this.betterPlayerPlaylistConfiguration, - }) : assert(betterPlayerDataSourceList != null, - "BetterPlayerDataSourceList can't be null or empty"), - assert(betterPlayerConfiguration != null, - "BetterPlayerConfiguration can't be null"), - assert(betterPlayerPlaylistConfiguration != null, - "BetterPlayerPlaylistConfiguration can't be null"), - super(key: key); + Key? key, + required this.betterPlayerDataSourceList, + required this.betterPlayerConfiguration, + required this.betterPlayerPlaylistConfiguration, + }) : super(key: key); @override BetterPlayerPlaylistState createState() => BetterPlayerPlaylistState(); @@ -34,13 +28,13 @@ class BetterPlayerPlaylist extends StatefulWidget { ///State of BetterPlayerPlaylist, used to access BetterPlayerPlaylistController. class BetterPlayerPlaylistState extends State { - BetterPlayerPlaylistController _betterPlayerPlaylistController; + BetterPlayerPlaylistController? _betterPlayerPlaylistController; - BetterPlayerController get _betterPlayerController => - _betterPlayerPlaylistController.betterPlayerController; + BetterPlayerController? get _betterPlayerController => + _betterPlayerPlaylistController!.betterPlayerController; ///Get BetterPlayerPlaylistController - BetterPlayerPlaylistController get betterPlayerPlaylistController => + BetterPlayerPlaylistController? get betterPlayerPlaylistController => _betterPlayerPlaylistController; @override @@ -56,17 +50,17 @@ class BetterPlayerPlaylistState extends State { @override Widget build(BuildContext context) { return AspectRatio( - aspectRatio: _betterPlayerController.getAspectRatio() ?? + aspectRatio: _betterPlayerController!.getAspectRatio() ?? BetterPlayerUtils.calculateAspectRatio(context), child: BetterPlayer( - controller: _betterPlayerController, + controller: _betterPlayerController!, ), ); } @override void dispose() { - _betterPlayerPlaylistController.dispose(); + _betterPlayerPlaylistController!.dispose(); super.dispose(); } } diff --git a/lib/src/playlist/better_player_playlist_configuration.dart b/lib/src/playlist/better_player_playlist_configuration.dart index 1ef33a6cf..e3f39d331 100644 --- a/lib/src/playlist/better_player_playlist_configuration.dart +++ b/lib/src/playlist/better_player_playlist_configuration.dart @@ -14,6 +14,5 @@ class BetterPlayerPlaylistConfiguration { this.nextVideoDelay = const Duration(milliseconds: 3000), this.loopVideos = true, this.initialStartIndex = 0, - }) : assert(nextVideoDelay != null), - assert(loopVideos != null); + }); } diff --git a/lib/src/playlist/better_player_playlist_controller.dart b/lib/src/playlist/better_player_playlist_controller.dart index aa10c1c7b..fd468e5ff 100644 --- a/lib/src/playlist/better_player_playlist_controller.dart +++ b/lib/src/playlist/better_player_playlist_controller.dart @@ -16,13 +16,13 @@ class BetterPlayerPlaylistController { final BetterPlayerPlaylistConfiguration betterPlayerPlaylistConfiguration; ///BetterPlayerController instance - BetterPlayerController _betterPlayerController; + BetterPlayerController? _betterPlayerController; ///Currently playing data source index int _currentDataSourceIndex = 0; ///Next video change listener subscription - StreamSubscription _nextVideoTimeStreamSubscription; + StreamSubscription? _nextVideoTimeStreamSubscription; ///Flag that determines whenever player is changing video bool _changingToNextVideo = false; @@ -32,13 +32,8 @@ class BetterPlayerPlaylistController { this.betterPlayerConfiguration = const BetterPlayerConfiguration(), this.betterPlayerPlaylistConfiguration = const BetterPlayerPlaylistConfiguration(), - }) : assert( - _betterPlayerDataSourceList != null && - _betterPlayerDataSourceList.isNotEmpty, - "Better Player data source list can't be empty"), - assert(betterPlayerConfiguration != null, "BetterPlayerConfiguration"), - assert(betterPlayerPlaylistConfiguration != null, - "BetterPlayerPlaylistConfiguration can't be null") { + }) : assert(_betterPlayerDataSourceList.isNotEmpty, + "Better Player data source list can't be empty") { _setup(); } @@ -49,16 +44,15 @@ class BetterPlayerPlaylistController { betterPlayerPlaylistConfiguration: betterPlayerPlaylistConfiguration, ); - var initialStartIndex = - betterPlayerPlaylistConfiguration.initialStartIndex ?? 0; + var initialStartIndex = betterPlayerPlaylistConfiguration.initialStartIndex; if (initialStartIndex >= _betterPlayerDataSourceList.length) { initialStartIndex = 0; } _currentDataSourceIndex = initialStartIndex; setupDataSource(_currentDataSourceIndex); - _betterPlayerController.addEventsListener(_handleEvent); - _nextVideoTimeStreamSubscription = _betterPlayerController + _betterPlayerController!.addEventsListener(_handleEvent); + _nextVideoTimeStreamSubscription = _betterPlayerController! .nextVideoTimeStreamController.stream .listen((time) { if (time != null && time == 0) { @@ -77,8 +71,8 @@ class BetterPlayerPlaylistController { if (nextDataSourceId == -1) { return; } - if (_betterPlayerController.isFullScreen) { - _betterPlayerController.exitFullScreen(); + if (_betterPlayerController!.isFullScreen) { + _betterPlayerController!.exitFullScreen(); } _changingToNextVideo = true; setupDataSource(nextDataSourceId); @@ -92,7 +86,7 @@ class BetterPlayerPlaylistController { if (betterPlayerEvent.betterPlayerEventType == BetterPlayerEventType.finished) { if (_getNextDataSourceIndex() != -1) { - _betterPlayerController.startNextVideoTimer(); + _betterPlayerController!.startNextVideoTimer(); } } } @@ -101,14 +95,12 @@ class BetterPlayerPlaylistController { ///in constructor. Index must void setupDataSource(int index) { assert( - index != null && - index >= 0 && - index < _betterPlayerDataSourceList.length, + index >= 0 && index < _betterPlayerDataSourceList.length, "Index must be greater than 0 and less than size of data source " "list - 1"); if (index <= _dataSourceLength) { _currentDataSourceIndex = index; - _betterPlayerController + _betterPlayerController! .setupDataSource(_betterPlayerDataSourceList[index]); } } @@ -135,10 +127,10 @@ class BetterPlayerPlaylistController { int get currentDataSourceIndex => _currentDataSourceIndex; ///Get size of [_betterPlayerDataSourceList] - int get _dataSourceLength => _betterPlayerDataSourceList.length ?? 0; + int get _dataSourceLength => _betterPlayerDataSourceList.length; ///Get BetterPlayerController instance - BetterPlayerController get betterPlayerController => _betterPlayerController; + BetterPlayerController? get betterPlayerController => _betterPlayerController; ///Cleanup BetterPlayerPlaylistController void dispose() { diff --git a/lib/src/subtitles/better_player_subtitle.dart b/lib/src/subtitles/better_player_subtitle.dart index 191d16f68..2664c8ef0 100644 --- a/lib/src/subtitles/better_player_subtitle.dart +++ b/lib/src/subtitles/better_player_subtitle.dart @@ -2,13 +2,13 @@ import 'package:better_player/src/core/better_player_utils.dart'; class BetterPlayerSubtitle { static const String timerSeparator = ' --> '; - final int index; - final Duration start; - final Duration end; - final List texts; + final int? index; + final Duration? start; + final Duration? end; + final List? texts; ///VTT OR SRT - final String type; + final String? type; BetterPlayerSubtitle._({ this.index, @@ -52,7 +52,7 @@ class BetterPlayerSubtitle { static BetterPlayerSubtitle _handle3LinesAndMoreSubtitles( List scanner, bool isWebVTT) { try { - int index = -1; + int? index = -1; List timeSplit = []; int firstLineOfText = 0; if (scanner[0].contains(timerSeparator)) { @@ -76,7 +76,6 @@ class BetterPlayerSubtitle { } static Duration _stringToDuration(String value) { - assert(value != null); try { final valueSplit = value.split(" "); String componentValue; @@ -99,10 +98,10 @@ class BetterPlayerSubtitle { } final result = Duration( - hours: int.tryParse(component[0]), - minutes: int.tryParse(component[1]), - seconds: int.tryParse(secsAndMillsSplit[0]), - milliseconds: int.tryParse(secsAndMillsSplit[1])); + hours: int.tryParse(component[0])!, + minutes: int.tryParse(component[1])!, + seconds: int.tryParse(secsAndMillsSplit[0])!, + milliseconds: int.tryParse(secsAndMillsSplit[1])!); return result; } catch (exception) { BetterPlayerUtils.log("Failed to process value: $value"); diff --git a/lib/src/subtitles/better_player_subtitles_drawer.dart b/lib/src/subtitles/better_player_subtitles_drawer.dart index a3c39d64b..eb27894cf 100644 --- a/lib/src/subtitles/better_player_subtitles_drawer.dart +++ b/lib/src/subtitles/better_player_subtitles_drawer.dart @@ -14,19 +14,16 @@ import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart class BetterPlayerSubtitlesDrawer extends StatefulWidget { final List subtitles; final BetterPlayerController betterPlayerController; - final BetterPlayerSubtitlesConfiguration betterPlayerSubtitlesConfiguration; + final BetterPlayerSubtitlesConfiguration? betterPlayerSubtitlesConfiguration; final Stream playerVisibilityStream; const BetterPlayerSubtitlesDrawer({ - Key key, - @required this.subtitles, - @required this.betterPlayerController, + Key? key, + required this.subtitles, + required this.betterPlayerController, this.betterPlayerSubtitlesConfiguration, - @required this.playerVisibilityStream, - }) : assert(subtitles != null), - assert(betterPlayerController != null), - assert(playerVisibilityStream != null), - super(key: key); + required this.playerVisibilityStream, + }) : super(key: key); @override _BetterPlayerSubtitlesDrawerState createState() => @@ -38,15 +35,15 @@ class _BetterPlayerSubtitlesDrawerState final RegExp htmlRegExp = // ignore: unnecessary_raw_strings RegExp(r"<[^>]*>", multiLine: true); - TextStyle _innerTextStyle; - TextStyle _outerTextStyle; + late TextStyle _innerTextStyle; + late TextStyle _outerTextStyle; - VideoPlayerValue _latestValue; - BetterPlayerSubtitlesConfiguration _configuration; + VideoPlayerValue? _latestValue; + BetterPlayerSubtitlesConfiguration? _configuration; bool _playerVisible = false; ///Stream used to detect if play controls are visible or not - StreamSubscription _visibilityStreamSubscription; + late StreamSubscription _visibilityStreamSubscription; @override void initState() { @@ -63,28 +60,28 @@ class _BetterPlayerSubtitlesDrawerState _configuration = setupDefaultConfiguration(); } - widget.betterPlayerController.videoPlayerController + widget.betterPlayerController.videoPlayerController! .addListener(_updateState); _outerTextStyle = TextStyle( - fontSize: _configuration.fontSize, - fontFamily: _configuration.fontFamily, + fontSize: _configuration!.fontSize, + fontFamily: _configuration!.fontFamily, foreground: Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _configuration.outlineSize - ..color = _configuration.outlineColor); + ..strokeWidth = _configuration!.outlineSize + ..color = _configuration!.outlineColor); _innerTextStyle = TextStyle( - fontFamily: _configuration.fontFamily, - color: _configuration.fontColor, - fontSize: _configuration.fontSize); + fontFamily: _configuration!.fontFamily, + color: _configuration!.fontColor, + fontSize: _configuration!.fontSize); super.initState(); } @override void dispose() { - widget.betterPlayerController.videoPlayerController + widget.betterPlayerController.videoPlayerController! .removeListener(_updateState); _visibilityStreamSubscription.cancel(); super.dispose(); @@ -95,14 +92,14 @@ class _BetterPlayerSubtitlesDrawerState if (mounted) { setState(() { _latestValue = - widget.betterPlayerController.videoPlayerController.value; + widget.betterPlayerController.videoPlayerController!.value; }); } } @override Widget build(BuildContext context) { - final List subtitles = _getSubtitlesAtCurrentPosition(); + final List subtitles = _getSubtitlesAtCurrentPosition()!; final List textWidgets = subtitles.map((text) => _buildSubtitleTextWidget(text)).toList(); @@ -112,10 +109,10 @@ class _BetterPlayerSubtitlesDrawerState child: Padding( padding: EdgeInsets.only( bottom: _playerVisible - ? _configuration.bottomPadding + 30 - : _configuration.bottomPadding, - left: _configuration.leftPadding, - right: _configuration.rightPadding), + ? _configuration!.bottomPadding + 30 + : _configuration!.bottomPadding, + left: _configuration!.leftPadding, + right: _configuration!.rightPadding), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: textWidgets, @@ -124,14 +121,14 @@ class _BetterPlayerSubtitlesDrawerState ); } - List _getSubtitlesAtCurrentPosition() { + List? _getSubtitlesAtCurrentPosition() { if (_latestValue == null) { return []; } - final Duration position = _latestValue.position; + final Duration position = _latestValue!.position; for (final BetterPlayerSubtitle subtitle in widget.betterPlayerController.subtitlesLines) { - if (subtitle.start <= position && subtitle.end >= position) { + if (subtitle.start! <= position && subtitle.end! >= position) { return subtitle.texts; } } @@ -142,7 +139,7 @@ class _BetterPlayerSubtitlesDrawerState return Row(children: [ Expanded( child: Align( - alignment: _configuration.alignment ?? Alignment.center, + alignment: _configuration!.alignment, child: _getTextWithStroke(subtitleText), ), ), @@ -150,14 +147,11 @@ class _BetterPlayerSubtitlesDrawerState } Widget _getTextWithStroke(String subtitleText) { - String subtitleTextToDisplay = subtitleText; - - subtitleTextToDisplay ??= ""; return Container( - color: _configuration.backgroundColor ?? Colors.transparent, + color: _configuration!.backgroundColor, child: Stack( children: [ - if (_configuration.outlineEnabled) + if (_configuration!.outlineEnabled) _buildHtmlWidget(subtitleText, _outerTextStyle) else const SizedBox(), @@ -168,8 +162,6 @@ class _BetterPlayerSubtitlesDrawerState } Widget _buildHtmlWidget(String text, TextStyle textStyle) { - assert(text != null); - assert(textStyle != null); return HtmlWidget( text, textStyle: textStyle, diff --git a/lib/src/subtitles/better_player_subtitles_factory.dart b/lib/src/subtitles/better_player_subtitles_factory.dart index a85ab106e..bcc54755b 100644 --- a/lib/src/subtitles/better_player_subtitles_factory.dart +++ b/lib/src/subtitles/better_player_subtitles_factory.dart @@ -13,7 +13,6 @@ import 'better_player_subtitles_source_type.dart'; class BetterPlayerSubtitlesFactory { static Future> parseSubtitles( BetterPlayerSubtitlesSource source) async { - assert(source != null); switch (source.type) { case BetterPlayerSubtitlesSourceType.file: return _parseSubtitlesFromFile(source); @@ -30,8 +29,8 @@ class BetterPlayerSubtitlesFactory { BetterPlayerSubtitlesSource source) async { try { final List subtitles = []; - for (final String url in source.urls) { - final file = File(url); + for (final String? url in source.urls!) { + final file = File(url!); if (file.existsSync()) { final String fileContent = await file.readAsString(); final subtitlesCache = _parseString(fileContent); @@ -52,8 +51,8 @@ class BetterPlayerSubtitlesFactory { try { final client = HttpClient(); final List subtitles = []; - for (final String url in source.urls) { - final request = await client.getUrl(Uri.parse(url)); + for (final String? url in source.urls!) { + final request = await client.getUrl(Uri.parse(url!)); final response = await request.close(); final data = await response.transform(const Utf8Decoder()).join(); final cacheList = _parseString(data); @@ -73,7 +72,7 @@ class BetterPlayerSubtitlesFactory { static List _parseSubtitlesFromMemory( BetterPlayerSubtitlesSource source) { try { - return _parseString(source.content); + return _parseString(source.content!); } catch (exception) { BetterPlayerUtils.log("Failed to read subtitles from memory: $exception"); } @@ -81,8 +80,6 @@ class BetterPlayerSubtitlesFactory { } static List _parseString(String value) { - assert(value != null); - List components = value.split('\r\n\r\n'); if (components.length == 1) { components = value.split('\n\n'); @@ -96,8 +93,7 @@ class BetterPlayerSubtitlesFactory { continue; } final subtitle = BetterPlayerSubtitle(component, isWebVTT); - if (subtitle != null && - subtitle.start != null && + if (subtitle.start != null && subtitle.end != null && subtitle.texts != null) { subtitlesObj.add(subtitle); diff --git a/lib/src/subtitles/better_player_subtitles_source.dart b/lib/src/subtitles/better_player_subtitles_source.dart index 5b4c2a9c2..29aa6f448 100644 --- a/lib/src/subtitles/better_player_subtitles_source.dart +++ b/lib/src/subtitles/better_player_subtitles_source.dart @@ -5,19 +5,19 @@ import 'better_player_subtitles_source_type.dart'; /// Player. class BetterPlayerSubtitlesSource { ///Source type - final BetterPlayerSubtitlesSourceType type; + final BetterPlayerSubtitlesSourceType? type; ///Name of the subtitles, default value is "Default subtitles" - final String name; + final String? name; ///Url of the subtitles, used with file or network subtitles - final List urls; + final List? urls; ///Content of subtitles, used when type is memory - final String content; + final String? content; ///Subtitles selected by default, without user interaction - final bool selectedByDefault; + final bool? selectedByDefault; BetterPlayerSubtitlesSource({ this.type, @@ -29,11 +29,11 @@ class BetterPlayerSubtitlesSource { ///Creates list with only one subtitles static List single({ - BetterPlayerSubtitlesSourceType type, + BetterPlayerSubtitlesSourceType? type, String name = "Default subtitles", - String url, - String content, - bool selectedByDefault, + String? url, + String? content, + bool? selectedByDefault, }) => [ BetterPlayerSubtitlesSource( diff --git a/lib/src/video_player/method_channel_video_player.dart b/lib/src/video_player/method_channel_video_player.dart index 6073f5f76..f9ce6d706 100644 --- a/lib/src/video_player/method_channel_video_player.dart +++ b/lib/src/video_player/method_channel_video_player.dart @@ -25,7 +25,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future dispose(int textureId) { + Future dispose(int? textureId) { return _channel.invokeMethod( 'dispose', {'textureId': textureId}, @@ -33,15 +33,15 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future create() async { - final Map response = + Future create() async { + final Map? response = await _channel.invokeMapMethod('create'); - return response['textureId'] as int; + return response?['textureId'] as int?; } @override - Future setDataSource(int textureId, DataSource dataSource) async { - Map dataSourceDescription; + Future setDataSource(int? textureId, DataSource dataSource) async { + Map? dataSourceDescription; switch (dataSource.sourceType) { case DataSourceType.asset: dataSourceDescription = { @@ -94,18 +94,18 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { }; break; } - - return _channel.invokeMethod( + await _channel.invokeMethod( 'setDataSource', { 'textureId': textureId, 'dataSource': dataSourceDescription, }, ); + return; } @override - Future setLooping(int textureId, bool looping) { + Future setLooping(int? textureId, bool looping) { return _channel.invokeMethod( 'setLooping', { @@ -116,7 +116,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future play(int textureId) { + Future play(int? textureId) { return _channel.invokeMethod( 'play', {'textureId': textureId}, @@ -124,7 +124,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future pause(int textureId) { + Future pause(int? textureId) { return _channel.invokeMethod( 'pause', {'textureId': textureId}, @@ -132,7 +132,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future setVolume(int textureId, double volume) { + Future setVolume(int? textureId, double volume) { return _channel.invokeMethod( 'setVolume', { @@ -143,7 +143,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future setSpeed(int textureId, double speed) { + Future setSpeed(int? textureId, double speed) { return _channel.invokeMethod( 'setSpeed', { @@ -155,7 +155,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { @override Future setTrackParameters( - int textureId, int width, int height, int bitrate) { + int? textureId, int? width, int? height, int? bitrate) { return _channel.invokeMethod( 'setTrackParameters', { @@ -168,32 +168,33 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future seekTo(int textureId, Duration position) { + Future seekTo(int? textureId, Duration? position) { return _channel.invokeMethod( 'seekTo', { 'textureId': textureId, - 'location': position.inMilliseconds, + 'location': position!.inMilliseconds, }, ); } @override - Future getPosition(int textureId) async { + Future getPosition(int? textureId) async { return Duration( - milliseconds: await _channel.invokeMethod( - 'position', - {'textureId': textureId}, - ), - ); + milliseconds: await _channel.invokeMethod( + 'position', + {'textureId': textureId}, + ) ?? + 0); } @override - Future getAbsolutePosition(int textureId) async { + Future getAbsolutePosition(int? textureId) async { final int milliseconds = await _channel.invokeMethod( - 'absolutePosition', - {'textureId': textureId}, - ); + 'absolutePosition', + {'textureId': textureId}, + ) ?? + 0; if (milliseconds <= 0) return null; @@ -201,8 +202,8 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future enablePictureInPicture(int textureId, double top, double left, - double width, double height) async { + Future enablePictureInPicture(int? textureId, double? top, double? left, + double? width, double? height) async { return _channel.invokeMethod( 'enablePictureInPicture', { @@ -216,7 +217,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future isPictureInPictureEnabled(int textureId) { + Future isPictureInPictureEnabled(int? textureId) { return _channel.invokeMethod( 'isPictureInPictureSupported', { @@ -226,7 +227,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future disablePictureInPicture(int textureId) { + Future disablePictureInPicture(int? textureId) { return _channel.invokeMethod( 'disablePictureInPicture', { @@ -236,7 +237,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future setAudioTrack(int textureId, String name, int index) { + Future setAudioTrack(int? textureId, String? name, int? index) { return _channel.invokeMethod( 'setAudioTrack', { @@ -248,7 +249,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future setMixWithOthers(int textureId, bool mixWithOthers) { + Future setMixWithOthers(int? textureId, bool mixWithOthers) { return _channel.invokeMethod( 'setMixWithOthers', { @@ -259,16 +260,16 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Stream videoEventsFor(int textureId) { + Stream videoEventsFor(int? textureId) { return _eventChannelFor(textureId) .receiveBroadcastStream() .map((dynamic event) { - Map map; + late Map map; if (event is Map) { map = event; } - final String eventType = map["event"] as String; - final String key = map["key"] as String; + final String? eventType = map["event"] as String?; + final String? key = map["key"] as String?; switch (eventType) { case 'initialized': double width = 0; @@ -360,11 +361,11 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Widget buildView(int textureId) { - return Texture(textureId: textureId); + Widget buildView(int? textureId) { + return Texture(textureId: textureId!); } - EventChannel _eventChannelFor(int textureId) { + EventChannel _eventChannelFor(int? textureId) { return EventChannel('better_player_channel/videoEvents$textureId'); } diff --git a/lib/src/video_player/video_player.dart b/lib/src/video_player/video_player.dart index d6012bc8f..4758e45c6 100644 --- a/lib/src/video_player/video_player.dart +++ b/lib/src/video_player/video_player.dart @@ -29,7 +29,7 @@ class VideoPlayerValue { /// Constructs a video with the given values. Only [duration] is required. The /// rest will initialize with default values when unset. VideoPlayerValue({ - @required this.duration, + required this.duration, this.size, this.position = const Duration(), this.absolutePosition, @@ -54,7 +54,7 @@ class VideoPlayerValue { /// The total duration of the video. /// /// Is null when [initialized] is false. - final Duration duration; + final Duration? duration; /// The current playback position. final Duration position; @@ -62,7 +62,7 @@ class VideoPlayerValue { /// The current absolute playback position. /// /// Is null when is not available. - final DateTime absolutePosition; + final DateTime? absolutePosition; /// The currently buffered ranges. final List buffered; @@ -85,12 +85,12 @@ class VideoPlayerValue { /// A description of the error if present. /// /// If [hasError] is false this is [null]. - final String errorDescription; + final String? errorDescription; /// The [size] of the currently loaded video. /// /// Is null when [initialized] is false. - final Size size; + final Size? size; ///Is in Picture in Picture Mode final bool isPip; @@ -108,7 +108,7 @@ class VideoPlayerValue { if (size == null) { return 1.0; } - final double aspectRatio = size.width / size.height; + final double aspectRatio = size!.width / size!.height; if (aspectRatio <= 0) { return 1.0; } @@ -118,18 +118,18 @@ class VideoPlayerValue { /// Returns a new instance that has the same values as this current instance, /// except for any overrides passed in as arguments to [copyWidth]. VideoPlayerValue copyWith({ - Duration duration, - Size size, - Duration position, - DateTime absolutePosition, - List buffered, - bool isPlaying, - bool isLooping, - bool isBuffering, - double volume, - String errorDescription, - double speed, - bool isPip, + Duration? duration, + Size? size, + Duration? position, + DateTime? absolutePosition, + List? buffered, + bool? isPlaying, + bool? isLooping, + bool? isBuffering, + double? volume, + String? errorDescription, + double? speed, + bool? isPip, }) { return VideoPlayerValue( duration: duration ?? this.duration, @@ -183,19 +183,19 @@ class VideoPlayerController extends ValueNotifier { final StreamController videoEventStreamController = StreamController.broadcast(); final Completer _creatingCompleter = Completer(); - int _textureId; + int? _textureId; - Timer _timer; + Timer? _timer; bool _isDisposed = false; - Completer _initializingCompleter; - StreamSubscription _eventSubscription; + late Completer _initializingCompleter; + StreamSubscription? _eventSubscription; bool get _created => _creatingCompleter.isCompleted; /// This is just exposed for testing. It shouldn't be used by anyone depending /// on the plugin. @visibleForTesting - int get textureId => _textureId; + int? get textureId => _textureId; /// Attempts to open the given [dataSource] and load metadata about the video. Future _create() async { @@ -282,13 +282,13 @@ class VideoPlayerController extends ValueNotifier { /// package and null otherwise. Future setAssetDataSource( String dataSource, { - String package, - bool showNotification, - String title, - String author, - String imageUrl, - String notificationChannelName, - Duration overriddenDuration, + String? package, + bool? showNotification, + String? title, + String? author, + String? imageUrl, + String? notificationChannelName, + Duration? overriddenDuration, }) { return _setDataSource( DataSource( @@ -314,19 +314,19 @@ class VideoPlayerController extends ValueNotifier { /// the video format detection code. Future setNetworkDataSource( String dataSource, { - VideoFormat formatHint, - Map headers, + VideoFormat? formatHint, + Map? headers, bool useCache = false, - int maxCacheSize, - int maxCacheFileSize, - bool showNotification, - String title, - String author, - String imageUrl, - String notificationChannelName, - Duration overriddenDuration, - String licenseUrl, - Map drmHeaders, + int? maxCacheSize, + int? maxCacheFileSize, + bool? showNotification, + String? title, + String? author, + String? imageUrl, + String? notificationChannelName, + Duration? overriddenDuration, + String? licenseUrl, + Map? drmHeaders, }) { return _setDataSource( DataSource( @@ -355,12 +355,12 @@ class VideoPlayerController extends ValueNotifier { /// `'file://${file.path}'`. Future setFileDataSource( File file, { - bool showNotification, - String title, - String author, - String imageUrl, - String notificationChannelName, - Duration overriddenDuration, + bool? showNotification, + String? title, + String? author, + String? imageUrl, + String? notificationChannelName, + Duration? overriddenDuration, }) { return _setDataSource( DataSource( @@ -397,16 +397,14 @@ class VideoPlayerController extends ValueNotifier { @override Future dispose() async { - if (_creatingCompleter != null) { - await _creatingCompleter.future; - if (!_isDisposed) { - _isDisposed = true; - value = VideoPlayerValue.uninitialized(); - _timer?.cancel(); - await _eventSubscription?.cancel(); - await _videoPlayerPlatform.dispose(_textureId); - videoEventStreamController.close(); - } + await _creatingCompleter.future; + if (!_isDisposed) { + _isDisposed = true; + value = VideoPlayerValue.uninitialized(); + _timer?.cancel(); + await _eventSubscription?.cancel(); + await _videoPlayerPlatform.dispose(_textureId); + videoEventStreamController.close(); } _isDisposed = true; super.dispose(); @@ -455,8 +453,8 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposed) { return; } - final Duration newPosition = await position; - final DateTime newAbsolutePosition = await absolutePosition; + final Duration? newPosition = await position; + final DateTime? newAbsolutePosition = await absolutePosition; // ignore: invariant_booleans if (_isDisposed) { return; @@ -484,7 +482,7 @@ class VideoPlayerController extends ValueNotifier { } /// The position in the current video. - Future get position async { + Future get position async { if (!value.initialized && _isDisposed) { return null; } @@ -493,7 +491,7 @@ class VideoPlayerController extends ValueNotifier { /// The absolute position in the current video stream /// (i.e. EXT-X-PROGRAM-DATE-TIME in HLS). - Future get absolutePosition async { + Future get absolutePosition async { if (!value.initialized && _isDisposed) { return null; } @@ -505,13 +503,13 @@ class VideoPlayerController extends ValueNotifier { /// /// If [moment] is outside of the video's full range it will be automatically /// and silently clamped. - Future seekTo(Duration position) async { + Future seekTo(Duration? position) async { if (_isDisposed) { return; } - Duration positionToSeek = position; - if (position > value.duration) { + Duration? positionToSeek = position; + if (position! > value.duration!) { positionToSeek = value.duration; } else if (position < const Duration()) { positionToSeek = const Duration(); @@ -525,7 +523,7 @@ class VideoPlayerController extends ValueNotifier { /// [volume] indicates a value between 0.0 (silent) and 1.0 (full volume) on a /// linear scale. Future setVolume(double volume) async { - value = value.copyWith(volume: volume.clamp(0.0, 1.0) as double); + value = value.copyWith(volume: volume.clamp(0.0, 1.0)); await _applyVolume(); } @@ -542,13 +540,13 @@ class VideoPlayerController extends ValueNotifier { /// [width] specifies width of the selected track /// [height] specifies height of the selected track /// [bitrate] specifies bitrate of the selected track - Future setTrackParameters(int width, int height, int bitrate) async { + Future setTrackParameters(int? width, int? height, int? bitrate) async { await _videoPlayerPlatform.setTrackParameters( _textureId, width, height, bitrate); } Future enablePictureInPicture( - {double top, double left, double width, double height}) async { + {double? top, double? left, double? width, double? height}) async { await _videoPlayerPlatform.enablePictureInPicture( textureId, top, left, width, height); } @@ -557,12 +555,12 @@ class VideoPlayerController extends ValueNotifier { await _videoPlayerPlatform.disablePictureInPicture(textureId); } - void _updatePosition(Duration position, {DateTime absolutePosition}) { + void _updatePosition(Duration? position, {DateTime? absolutePosition}) { value = value.copyWith(position: position); value = value.copyWith(absolutePosition: absolutePosition); } - Future isPictureInPictureSupported() async { + Future isPictureInPictureSupported() async { if (_textureId == null) { return false; } @@ -573,7 +571,7 @@ class VideoPlayerController extends ValueNotifier { value = value.copyWith(); } - void setAudioTrack(String name, int index) { + void setAudioTrack(String? name, int? index) { _videoPlayerPlatform.setAudioTrack(_textureId, name, index); } @@ -585,11 +583,11 @@ class VideoPlayerController extends ValueNotifier { /// Widget that displays the video controlled by [controller]. class VideoPlayer extends StatefulWidget { /// Uses the given [controller] for all video rendered in this widget. - const VideoPlayer(this.controller, {Key key}) : super(key: key); + const VideoPlayer(this.controller, {Key? key}) : super(key: key); /// The [VideoPlayerController] responsible for the video being rendered in /// this widget. - final VideoPlayerController controller; + final VideoPlayerController? controller; @override _VideoPlayerState createState() => _VideoPlayerState(); @@ -598,7 +596,7 @@ class VideoPlayer extends StatefulWidget { class _VideoPlayerState extends State { _VideoPlayerState() { _listener = () { - final int newTextureId = widget.controller.textureId; + final int? newTextureId = widget.controller!.textureId; if (newTextureId != _textureId) { setState(() { _textureId = newTextureId; @@ -607,30 +605,30 @@ class _VideoPlayerState extends State { }; } - VoidCallback _listener; - int _textureId; + late VoidCallback _listener; + int? _textureId; @override void initState() { super.initState(); - _textureId = widget.controller.textureId; + _textureId = widget.controller!.textureId; // Need to listen for initialization events since the actual texture ID // becomes available after asynchronous initialization finishes. - widget.controller.addListener(_listener); + widget.controller!.addListener(_listener); } @override void didUpdateWidget(VideoPlayer oldWidget) { super.didUpdateWidget(oldWidget); - oldWidget.controller.removeListener(_listener); - _textureId = widget.controller.textureId; - widget.controller.addListener(_listener); + oldWidget.controller!.removeListener(_listener); + _textureId = widget.controller!.textureId; + widget.controller!.addListener(_listener); } @override void deactivate() { super.deactivate(); - widget.controller.removeListener(_listener); + widget.controller!.removeListener(_listener); } @override @@ -683,8 +681,8 @@ class VideoProgressColors { class _VideoScrubber extends StatefulWidget { const _VideoScrubber({ - @required this.child, - @required this.controller, + required this.child, + required this.controller, }); final Widget child; @@ -702,11 +700,14 @@ class _VideoScrubberState extends State<_VideoScrubber> { @override Widget build(BuildContext context) { void seekToRelativePosition(Offset globalPosition) { - final RenderBox box = context.findRenderObject() as RenderBox; - final Offset tapPos = box.globalToLocal(globalPosition); - final double relative = tapPos.dx / box.size.width; - final Duration position = controller.value.duration * relative; - controller.seekTo(position); + final RenderObject? renderObject = context.findRenderObject(); + if (renderObject != null) { + final RenderBox box = renderObject as RenderBox; + final Offset tapPos = box.globalToLocal(globalPosition); + final double relative = tapPos.dx / box.size.width; + final Duration position = controller.value.duration! * relative; + controller.seekTo(position); + } } return GestureDetector( @@ -758,10 +759,10 @@ class VideoProgressIndicator extends StatefulWidget { /// to `top: 5.0`. VideoProgressIndicator( this.controller, { - VideoProgressColors colors, + VideoProgressColors? colors, this.allowScrubbing, this.padding = const EdgeInsets.only(top: 5.0), - Key key, + Key? key, }) : colors = colors ?? VideoProgressColors(), super(key: key); @@ -778,7 +779,7 @@ class VideoProgressIndicator extends StatefulWidget { /// accordingly. The widget ignores such input when false. /// /// Defaults to false. - final bool allowScrubbing; + final bool? allowScrubbing; /// This allows for visual padding around the progress indicator that can /// still detect gestures via [allowScrubbing]. @@ -800,7 +801,7 @@ class _VideoProgressIndicatorState extends State { }; } - VoidCallback listener; + late VoidCallback listener; VideoPlayerController get controller => widget.controller; @@ -822,7 +823,7 @@ class _VideoProgressIndicatorState extends State { Widget build(BuildContext context) { Widget progressIndicator; if (controller.value.initialized) { - final int duration = controller.value.duration.inMilliseconds; + final int duration = controller.value.duration!.inMilliseconds; final int position = controller.value.position.inMilliseconds; int maxBuffering = 0; @@ -858,7 +859,7 @@ class _VideoProgressIndicatorState extends State { padding: widget.padding, child: progressIndicator, ); - if (widget.allowScrubbing) { + if (widget.allowScrubbing!) { return _VideoScrubber( controller: controller, child: paddedProgressIndicator, @@ -892,17 +893,17 @@ class ClosedCaption extends StatelessWidget { /// [VideoPlayerValue.caption]. /// /// If [text] is null, nothing will be displayed. - const ClosedCaption({Key key, this.text, this.textStyle}) : super(key: key); + const ClosedCaption({Key? key, this.text, this.textStyle}) : super(key: key); /// The text that will be shown in the closed caption, or null if no caption /// should be shown. - final String text; + final String? text; /// Specifies how the text in the closed caption should look. /// /// If null, defaults to [DefaultTextStyle.of(context).style] with size 36 /// font colored white. - final TextStyle textStyle; + final TextStyle? textStyle; @override Widget build(BuildContext context) { @@ -927,7 +928,7 @@ class ClosedCaption extends StatelessWidget { ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 2.0), - child: Text(text, style: effectiveTextStyle), + child: Text(text!, style: effectiveTextStyle), ), ), ), diff --git a/lib/src/video_player/video_player_platform_interface.dart b/lib/src/video_player/video_player_platform_interface.dart index 852b0de63..3796b59da 100644 --- a/lib/src/video_player/video_player_platform_interface.dart +++ b/lib/src/video_player/video_player_platform_interface.dart @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; // Package imports: -import 'package:meta/meta.dart' show required, visibleForTesting; +import 'package:meta/meta.dart' show visibleForTesting; import 'method_channel_video_player.dart'; @@ -65,99 +65,99 @@ abstract class VideoPlayerPlatform { } /// Clears one video. - Future dispose(int textureId) { + Future dispose(int? textureId) { throw UnimplementedError('dispose() has not been implemented.'); } /// Creates an instance of a video player and returns its textureId. - Future create() { + Future create() { throw UnimplementedError('create() has not been implemented.'); } /// Set data source of video. - Future setDataSource(int textureId, DataSource dataSource) { + Future setDataSource(int? textureId, DataSource dataSource) { throw UnimplementedError('setDataSource() has not been implemented.'); } /// Returns a Stream of [VideoEventType]s. - Stream videoEventsFor(int textureId) { + Stream videoEventsFor(int? textureId) { throw UnimplementedError('videoEventsFor() has not been implemented.'); } /// Sets the looping attribute of the video. - Future setLooping(int textureId, bool looping) { + Future setLooping(int? textureId, bool looping) { throw UnimplementedError('setLooping() has not been implemented.'); } /// Starts the video playback. - Future play(int textureId) { + Future play(int? textureId) { throw UnimplementedError('play() has not been implemented.'); } /// Stops the video playback. - Future pause(int textureId) { + Future pause(int? textureId) { throw UnimplementedError('pause() has not been implemented.'); } /// Sets the volume to a range between 0.0 and 1.0. - Future setVolume(int textureId, double volume) { + Future setVolume(int? textureId, double volume) { throw UnimplementedError('setVolume() has not been implemented.'); } /// Sets the video speed to a range between 0.0 and 2.0 - Future setSpeed(int textureId, double speed) { + Future setSpeed(int? textureId, double speed) { throw UnimplementedError('setSpeed() has not been implemented.'); } /// Sets the video track parameters (used to select quality of the video) Future setTrackParameters( - int textureId, int width, int height, int bitrate) { + int? textureId, int? width, int? height, int? bitrate) { throw UnimplementedError('setTrackParameters() has not been implemented.'); } /// Sets the video position to a [Duration] from the start. - Future seekTo(int textureId, Duration position) { + Future seekTo(int? textureId, Duration? position) { throw UnimplementedError('seekTo() has not been implemented.'); } /// Gets the video position as [Duration] from the start. - Future getPosition(int textureId) { + Future getPosition(int? textureId) { throw UnimplementedError('getPosition() has not been implemented.'); } /// Gets the video position as [DateTime]. - Future getAbsolutePosition(int textureId) { + Future getAbsolutePosition(int? textureId) { throw UnimplementedError('getAbsolutePosition() has not been implemented.'); } ///Enables PiP mode. - Future enablePictureInPicture( - int textureId, double top, double left, double width, double height) { + Future enablePictureInPicture(int? textureId, double? top, double? left, + double? width, double? height) { throw UnimplementedError( 'enablePictureInPicture() has not been implemented.'); } ///Disables PiP mode. - Future disablePictureInPicture(int textureId) { + Future disablePictureInPicture(int? textureId) { throw UnimplementedError( 'disablePictureInPicture() has not been implemented.'); } - Future isPictureInPictureEnabled(int textureId) { + Future isPictureInPictureEnabled(int? textureId) { throw UnimplementedError( 'isPictureInPictureEnabled() has not been implemented.'); } - Future setAudioTrack(int textureId, String name, int index) { + Future setAudioTrack(int? textureId, String? name, int? index) { throw UnimplementedError('setAudio() has not been implemented.'); } - Future setMixWithOthers(int textureId, bool mixWithOthers) { + Future setMixWithOthers(int? textureId, bool mixWithOthers) { throw UnimplementedError('setMixWithOthers() has not been implemented.'); } /// Returns a widget displaying the video with a given textureID. - Widget buildView(int textureId) { + Widget buildView(int? textureId) { throw UnimplementedError('buildView() has not been implemented.'); } @@ -194,7 +194,7 @@ class DataSource { /// package and null otherwise. /// DataSource({ - @required this.sourceType, + required this.sourceType, this.uri, this.formatHint, this.asset, @@ -226,14 +226,14 @@ class DataSource { /// /// This will be in different formats depending on the [DataSourceType] of /// the original video. - final String uri; + final String? uri; /// **Android only**. Will override the platform's generic file format /// detection with whatever is set here. - final VideoFormat formatHint; + final VideoFormat? formatHint; /// **Android only**. String representation of a formatHint. - String get rawFormalHint { + String? get rawFormalHint { switch (formatHint) { case VideoFormat.ss: return 'ss'; @@ -243,49 +243,49 @@ class DataSource { return 'dash'; case VideoFormat.other: return 'other'; + default: + return null; } - - return null; } /// The name of the asset. Only set for [DataSourceType.asset] videos. - final String asset; + final String? asset; /// The package that the asset was loaded from. Only set for /// [DataSourceType.asset] videos. - final String package; + final String? package; - final Map headers; + final Map? headers; final bool useCache; - final int maxCacheSize; + final int? maxCacheSize; - final int maxCacheFileSize; + final int? maxCacheFileSize; - final bool showNotification; + final bool? showNotification; - final String title; + final String? title; - final String author; + final String? author; - final String imageUrl; + final String? imageUrl; - final String notificationChannelName; + final String? notificationChannelName; - final Duration overriddenDuration; + final Duration? overriddenDuration; - final String licenseUrl; + final String? licenseUrl; - final Map drmHeaders; + final Map? drmHeaders; /// Key to compare DataSource String get key { - String result = ""; + String? result = ""; - if (uri != null && uri.isNotEmpty) { + if (uri != null && uri!.isNotEmpty) { result = uri; - } else if (package != null && package.isNotEmpty) { + } else if (package != null && package!.isNotEmpty) { result = "$package:$asset"; } else { result = asset; @@ -295,7 +295,7 @@ class DataSource { result = "$result:$rawFormalHint"; } - return result; + return result!; } @override @@ -347,8 +347,8 @@ class VideoEvent { /// Depending on the [eventType], the [duration], [size] and [buffered] /// arguments can be null. VideoEvent({ - @required this.eventType, - @required this.key, + required this.eventType, + required this.key, this.duration, this.size, this.buffered, @@ -361,25 +361,25 @@ class VideoEvent { /// Data source of the video. /// /// Used to determine which video the event belongs to. - final String key; + final String? key; /// Duration of the video. /// /// Only used if [eventType] is [VideoEventType.initialized]. - final Duration duration; + final Duration? duration; /// Size of the video. /// /// Only used if [eventType] is [VideoEventType.initialized]. - final Size size; + final Size? size; /// Buffered parts of the video. /// /// Only used if [eventType] is [VideoEventType.bufferingUpdate]. - final List buffered; + final List? buffered; ///Seek position - final Duration position; + final Duration? position; @override bool operator ==(Object other) { diff --git a/pubspec.lock b/pubspec.lock index 7b17efd13..09f6d8bef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,82 +7,87 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" collection: dependency: "direct main" description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.2" + version: "0.17.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.2" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.0.0" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "5.2.1" + version: "6.1.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_localizations: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -99,126 +104,119 @@ packages: name: flutter_widget_from_html_core url: "https://pub.dartlang.org" source: hosted - version: "0.5.1+4" + version: "0.6.0-rc.2021030401" html: dependency: transitive description: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+4" - import_js_library: - dependency: transitive - description: - name: import_js_library - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" + version: "0.15.0" intl: dependency: transitive description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.17.0" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3" lint: dependency: "direct dev" description: name: lint url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.5.2" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.24" + version: "2.0.1" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.0.0" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+6" + version: "2.0.0" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+3" + version: "2.0.0" pedantic: dependency: "direct main" description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" + version: "1.11.0" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.0.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "3.0.13" + version: "4.1.0" sky_engine: dependency: transitive description: flutter @@ -230,98 +228,105 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" visibility_detector: dependency: "direct main" description: name: visibility_detector url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" + version: "0.2.0-nullsafety.1" wakelock: dependency: "direct main" description: name: wakelock url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.4.0" + wakelock_macos: + dependency: transitive + description: + name: wakelock_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+1" + version: "0.2.0" wakelock_web: dependency: transitive description: name: wakelock_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+3" + version: "0.2.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.4" + version: "2.0.0" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" sdks: - dart: ">=2.10.0-110 <2.11.0" - flutter: ">=1.20.0 <2.0.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index cee7f5c46..fc4c22597 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,30 +1,32 @@ name: better_player description: Advanced video player based on video_player and Chewie. It's solves many typical use cases and it's easy to run. -version: 0.0.61 +version: 0.0.62 authors: - Jakub Homlala homepage: https://github.com/jhomlala/betterplayer environment: - sdk: ">=2.2.2 <3.0.0" + sdk: '>=2.12.0 <3.0.0' flutter: ">=1.10.0 <2.0.0" dependencies: flutter: sdk: flutter - cupertino_icons: ^1.0.0 - wakelock: ^0.2.1+1 - pedantic: ^1.8.0 - meta: ^1.2.3 - flutter_widget_from_html_core: ^0.5.0+5 - visibility_detector: ^0.1.5 - path_provider: ^1.6.24 - collection: ^1.14.13 + cupertino_icons: ^1.0.2 + wakelock: ^0.4.0 + pedantic: ^1.11.0 + meta: ^1.3.0 + flutter_widget_from_html_core: ^0.6.0-rc.2021030201 + visibility_detector: ^0.2.0-nullsafety.1 + path_provider: ^2.0.1 + collection: ^1.15.0 dev_dependencies: flutter_test: sdk: flutter - lint: ^1.0.0 + lint: ^1.5.1 + flutter_localizations: + sdk: flutter flutter: plugin: diff --git a/test/better_player_controller_test.dart b/test/better_player_controller_test.dart new file mode 100644 index 000000000..74ec8becf --- /dev/null +++ b/test/better_player_controller_test.dart @@ -0,0 +1,74 @@ +import 'package:better_player/better_player.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'better_player_mock_controller.dart'; +import 'better_player_test_utils.dart'; +import 'mock_method_channel.dart'; + +MockMethodChannel? mockMethodChannel; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + "Controller tests", + () { + setUpAll(() { + mockMethodChannel = MockMethodChannel(); + }); + test("BetterPlayerController - create without data source", () { + final BetterPlayerMockController betterPlayerMockController = + BetterPlayerMockController(const BetterPlayerConfiguration()); + expect(betterPlayerMockController.betterPlayerDataSource, null); + expect(betterPlayerMockController.videoPlayerController, null); + }); + + test("BetterPlayerController - setup data source", () async { + final BetterPlayerMockController betterPlayerMockController = + BetterPlayerMockController(const BetterPlayerConfiguration()); + await betterPlayerMockController.setupDataSource( + BetterPlayerDataSource.network( + BetterPlayerTestUtils.forBiggerBlazesUrl)); + expect(betterPlayerMockController.betterPlayerDataSource != null, true); + expect(betterPlayerMockController.videoPlayerController != null, true); + }); + + test( + "BetterPlayerController - play should change isPlaying flag", + () async { + final BetterPlayerController betterPlayerController = + BetterPlayerController(const BetterPlayerConfiguration(), + betterPlayerDataSource: BetterPlayerDataSource.network( + BetterPlayerTestUtils.forBiggerBlazesUrl)); + betterPlayerController.play(); + expect(betterPlayerController.isPlaying(), true); + }, + ); + + test( + "BetterPlayerController - pause should change isPlaying flag", + () async { + final BetterPlayerController betterPlayerController = + BetterPlayerController(const BetterPlayerConfiguration(), + betterPlayerDataSource: BetterPlayerDataSource.network( + BetterPlayerTestUtils.forBiggerBlazesUrl)); + betterPlayerController.play(); + expect(betterPlayerController.isPlaying(), true); + betterPlayerController.pause(); + expect(betterPlayerController.isPlaying(), false); + }, + ); + + test("BetterPlayerController - full screen and auto play should work", + () async { + final BetterPlayerMockController betterPlayerMockController = + BetterPlayerMockController(const BetterPlayerConfiguration( + fullScreenByDefault: true, autoPlay: true)); + await betterPlayerMockController.setupDataSource( + BetterPlayerDataSource.network( + BetterPlayerTestUtils.forBiggerBlazesUrl)); + expect(betterPlayerMockController.isFullScreen, true); + expect(betterPlayerMockController.isPlaying(), true); + }); + }, + ); +} diff --git a/test/better_player_mock_controller.dart b/test/better_player_mock_controller.dart new file mode 100644 index 000000000..7b291f7ee --- /dev/null +++ b/test/better_player_mock_controller.dart @@ -0,0 +1,7 @@ +import 'package:better_player/better_player.dart'; + +class BetterPlayerMockController extends BetterPlayerController { + BetterPlayerMockController( + BetterPlayerConfiguration betterPlayerConfiguration) + : super(betterPlayerConfiguration); +} diff --git a/test/better_player_test.dart b/test/better_player_test.dart index ab73b3a23..8067e2c01 100644 --- a/test/better_player_test.dart +++ b/test/better_player_test.dart @@ -1 +1,48 @@ -void main() {} +import 'package:better_player/better_player.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:visibility_detector/visibility_detector.dart'; + +import 'better_player_mock_controller.dart'; +import 'better_player_test_utils.dart'; +import 'mock_method_channel.dart'; + +void main() { + setUpAll(() { + VisibilityDetectorController.instance.updateInterval = Duration.zero; + // ignore: unused_local_variable + final MockMethodChannel mockMethodChannel = MockMethodChannel(); + }); + + testWidgets("Better Player simple player - network", + (WidgetTester tester) async { + await tester.pumpWidget(_wrapWidget( + BetterPlayer.network(BetterPlayerTestUtils.bugBuckBunnyVideoUrl))); + expect(find.byWidgetPredicate((widget) => widget is BetterPlayer), + findsOneWidget); + }); + + testWidgets("Better Player simple player - file", + (WidgetTester tester) async { + await tester.pumpWidget(_wrapWidget( + BetterPlayer.network(BetterPlayerTestUtils.bugBuckBunnyVideoUrl))); + expect(find.byWidgetPredicate((widget) => widget is BetterPlayer), + findsOneWidget); + }); + + testWidgets("BetterPlayer - with controller", (WidgetTester tester) async { + final BetterPlayerMockController betterPlayerController = + BetterPlayerMockController(const BetterPlayerConfiguration()); + await tester.pumpWidget(_wrapWidget(BetterPlayer( + controller: betterPlayerController, + ))); + expect(find.byWidgetPredicate((widget) => widget is BetterPlayer), + findsOneWidget); + }); +} + +///Wrap widget with material app to handle all features like navigation and +///localization properly. +Widget _wrapWidget(Widget widget) { + return MaterialApp(home: widget); +} diff --git a/test/better_player_test_utils.dart b/test/better_player_test_utils.dart new file mode 100644 index 000000000..93da7519e --- /dev/null +++ b/test/better_player_test_utils.dart @@ -0,0 +1,8 @@ +class BetterPlayerTestUtils { + static const String bugBuckBunnyVideoUrl = + "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; + static const String forBiggerBlazesUrl = + "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"; + static const String elephantDreamStreamUrl = + "http://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8"; +} diff --git a/test/mock_method_channel.dart b/test/mock_method_channel.dart new file mode 100644 index 000000000..d04d67704 --- /dev/null +++ b/test/mock_method_channel.dart @@ -0,0 +1,48 @@ +import 'package:flutter/services.dart'; + +class MockMethodChannel { + final MethodChannel channel = const MethodChannel("better_player_channel"); + final List eventsChannels = []; + + MockMethodChannel() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + if (methodCall.method == "create") { + final int id = getNextId(); + _createEventChannel(id); + return _getCreateResult(id); + } + if (methodCall.method == "setDataSource") { + return null; + } + return {}; + }); + } + + int getNextId() { + return eventsChannels.length; + } + + Map _getCreateResult(int id) => + {"textureId": id}; + + Map _getInitResult() => { + "event": "initialized", + "height": 720.0, + "width:": 1280.0, + "duration": 100 + }; + + void _createEventChannel(int id) { + final MethodChannel eventChannel = + MethodChannel("better_player_channel/videoEvents$id"); + + eventChannel.setMockMethodCallHandler((MethodCall methodCall) async { + ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + "better_player_channel/videoEvents$id", + const StandardMethodCodec().encodeSuccessEnvelope(_getInitResult()), + (ByteData? data) {}); + }); + + eventsChannels.add(eventChannel); + } +}