diff --git a/example/lib/app/app.dart b/example/lib/app/app.dart index 768b4e86d..b3938d3c9 100644 --- a/example/lib/app/app.dart +++ b/example/lib/app/app.dart @@ -41,9 +41,10 @@ class _ChewieDemoState extends State { } List srcs = [ - "https://assets.mixkit.co/videos/preview/mixkit-spinning-around-the-earth-29351-large.mp4", - "https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4", - "https://assets.mixkit.co/videos/preview/mixkit-a-girl-blowing-a-bubble-gum-at-an-amusement-park-1226-large.mp4" + "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", + // "https://assets.mixkit.co/videos/preview/mixkit-spinning-around-the-earth-29351-large.mp4", + // "https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4", + // "https://assets.mixkit.co/videos/preview/mixkit-a-girl-blowing-a-bubble-gum-at-an-amusement-park-1226-large.mp4" ]; Future initializePlayer() async { @@ -153,6 +154,19 @@ class _ChewieDemoState extends State { // color: Colors.grey, // ), // autoInitialize: true, + allowQualityChanging: true, + qualities: { + '240p': + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4', + '360p': + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4', + '540p': + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4', + '720p': + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4', + '1080p': + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4', + }, ); } diff --git a/lib/src/chewie_player.dart b/lib/src/chewie_player.dart index 5ebe0620f..380f71861 100644 --- a/lib/src/chewie_player.dart +++ b/lib/src/chewie_player.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:chewie/src/chewie_progress_colors.dart'; import 'package:chewie/src/models/option_item.dart'; @@ -74,7 +75,7 @@ class ChewieState extends State { if (isControllerFullScreen && !_isFullScreen) { _isFullScreen = isControllerFullScreen; await _pushFullScreenWidget(context); - } else if (_isFullScreen) { + } else if (!isControllerFullScreen && _isFullScreen) { Navigator.of( context, rootNavigator: widget.controller.useRootNavigator, @@ -307,6 +308,9 @@ class ChewieController extends ChangeNotifier { this.progressIndicatorDelay, this.hideControlsTimer = defaultHideControlsTimer, this.controlsSafeAreaMinimum = EdgeInsets.zero, + this.allowQualityChanging = false, + this.qualities = const {}, + this.onVideoControllerChanged, }) : assert( playbackSpeeds.every((speed) => speed > 0), 'The playbackSpeeds values must all be greater than 0', @@ -363,6 +367,9 @@ class ChewieController extends ChangeNotifier { Animation, ChewieControllerProvider, )? routePageBuilder, + bool? allowQualityChanging, + Map? qualities, + void Function(VideoPlayerController)? onVideoControllerChanged, }) { return ChewieController( draggableProgressBar: draggableProgressBar ?? this.draggableProgressBar, @@ -415,8 +422,8 @@ class ChewieController extends ChangeNotifier { this.deviceOrientationsAfterFullScreen, routePageBuilder: routePageBuilder ?? this.routePageBuilder, hideControlsTimer: hideControlsTimer ?? this.hideControlsTimer, - progressIndicatorDelay: - progressIndicatorDelay ?? this.progressIndicatorDelay, + progressIndicatorDelay: progressIndicatorDelay ?? this.progressIndicatorDelay, + onVideoControllerChanged: onVideoControllerChanged ?? this.onVideoControllerChanged, ); } @@ -455,7 +462,7 @@ class ChewieController extends ChangeNotifier { Subtitles? subtitle; /// The controller for the video you want to play - final VideoPlayerController videoPlayerController; + VideoPlayerController videoPlayerController; /// Initialize the Video on Startup. This will prep the video for playback. final bool autoInitialize; @@ -575,6 +582,16 @@ class ChewieController extends ChangeNotifier { /// Defaults to [EdgeInsets.zero]. final EdgeInsets controlsSafeAreaMinimum; + /// Defines if the quality control should be shown + final bool allowQualityChanging; + + /// Defines the map of qualities user can change, where the key is the name of the resolution, + /// the value is a url of the video with this resolution + final Map qualities; + + /// Called when the video controller changes + final void Function(VideoPlayerController)? onVideoControllerChanged; + static ChewieController of(BuildContext context) { final chewieControllerProvider = context.dependOnInheritedWidgetOfExactType()!; @@ -584,11 +601,17 @@ class ChewieController extends ChangeNotifier { bool _isFullScreen = false; + late String _quality; + bool get isFullScreen => _isFullScreen; bool get isPlaying => videoPlayerController.value.isPlaying; + String get quality => _quality; + Future _initialize() async { + _quality = qualities.keys.last; + await videoPlayerController.setLooping(looping); if ((autoInitialize || autoPlay) && @@ -663,6 +686,48 @@ class ChewieController extends ChangeNotifier { void setSubtitle(List newSubtitle) { subtitle = Subtitles(newSubtitle); } + + Future setQuality(String quality) async { + final currentPosition = await videoPlayerController.position; + final speed = videoPlayerController.value.playbackSpeed; + + videoPlayerController.dispose(); + + final dataSource = qualities[quality]!; + videoPlayerController = switch (videoPlayerController.dataSourceType) { + DataSourceType.asset => VideoPlayerController.asset( + dataSource, + closedCaptionFile: videoPlayerController.closedCaptionFile, + package: videoPlayerController.package, + videoPlayerOptions: videoPlayerController.videoPlayerOptions, + ), + DataSourceType.contentUri => VideoPlayerController.contentUri( + Uri.parse(dataSource), + closedCaptionFile: videoPlayerController.closedCaptionFile, + videoPlayerOptions: videoPlayerController.videoPlayerOptions, + ), + DataSourceType.file => VideoPlayerController.file( + File(dataSource), + closedCaptionFile: videoPlayerController.closedCaptionFile, + videoPlayerOptions: videoPlayerController.videoPlayerOptions, + httpHeaders: videoPlayerController.httpHeaders, + ), + DataSourceType.network => VideoPlayerController.networkUrl( + Uri.parse(dataSource), + closedCaptionFile: videoPlayerController.closedCaptionFile, + videoPlayerOptions: videoPlayerController.videoPlayerOptions, + httpHeaders: videoPlayerController.httpHeaders, + ), + }; + + await videoPlayerController.initialize(); + await videoPlayerController.seekTo(currentPosition!); + await videoPlayerController.setPlaybackSpeed(speed); + await videoPlayerController.play(); + + _quality = quality; + notifyListeners(); + } } class ChewieControllerProvider extends InheritedWidget { diff --git a/lib/src/cupertino/cupertino_controls.dart b/lib/src/cupertino/cupertino_controls.dart index ea0bf4d80..a015ebdf5 100644 --- a/lib/src/cupertino/cupertino_controls.dart +++ b/lib/src/cupertino/cupertino_controls.dart @@ -35,8 +35,7 @@ class CupertinoControls extends StatefulWidget { } } -class _CupertinoControlsState extends State - with SingleTickerProviderStateMixin { +class _CupertinoControlsState extends State with SingleTickerProviderStateMixin { late PlayerNotifier notifier; late VideoPlayerValue _latestValue; double? _latestVolume; @@ -145,6 +144,15 @@ class _CupertinoControlsState extends State void didChangeDependencies() { final oldController = _chewieController; _chewieController = ChewieController.of(context); + _chewieController?.addListener(() { + if (mounted) { + setState(() { + controller = chewieController.videoPlayerController; + }); + _dispose(); + _initialize(); + } + }); controller = chewieController.videoPlayerController; if (oldController != chewieController) { @@ -155,32 +163,48 @@ class _CupertinoControlsState extends State super.didChangeDependencies(); } - GestureDetector _buildOptionsButton( - Color iconColor, - double barHeight, - ) { + List _buildOptions(BuildContext context) { final options = []; + if (chewieController.allowQualityChanging) { + options.add( + OptionItem( + onTap: () async { + Navigator.pop(context); + _onQualityButtonTap(); + }, + iconData: Icons.settings, + title: chewieController.optionsTranslation?.qualityButtonText ?? 'Quality', + ), + ); + } + if (chewieController.additionalOptions != null && chewieController.additionalOptions!(context).isNotEmpty) { options.addAll(chewieController.additionalOptions!(context)); } + return options; + } + + GestureDetector _buildOptionsButton( + Color iconColor, + double barHeight, + ) { return GestureDetector( onTap: () async { _hideTimer?.cancel(); if (chewieController.optionsBuilder != null) { - await chewieController.optionsBuilder!(context, options); + await chewieController.optionsBuilder!(context, _buildOptions(context)); } else { await showCupertinoModalPopup( context: context, semanticsDismissible: true, useRootNavigator: chewieController.useRootNavigator, builder: (context) => CupertinoOptionsDialog( - options: options, - cancelButtonText: - chewieController.optionsTranslation?.cancelButtonText, + options: _buildOptions(context), + cancelButtonText: chewieController.optionsTranslation?.cancelButtonText, ), ); if (_latestValue.isPlaying) { @@ -285,8 +309,8 @@ class _CupertinoControlsState extends State if (chewieController.allowPlaybackSpeedChanging) _buildSpeedButton(controller, iconColor, barHeight), if (chewieController.additionalOptions != null && - chewieController - .additionalOptions!(context).isNotEmpty) + chewieController.additionalOptions!(context).isNotEmpty || + chewieController.allowQualityChanging) _buildOptionsButton(iconColor, barHeight), ], ), @@ -347,10 +371,9 @@ class _CupertinoControlsState extends State } Widget _buildHitArea() { - final bool isFinished = (_latestValue.position >= _latestValue.duration) && - _latestValue.duration.inSeconds > 0; - final bool showPlayButton = - widget.showPlayButton && !_latestValue.isPlaying && !_dragging; + final bool isFinished = + (_latestValue.position >= _latestValue.duration) && _latestValue.duration.inSeconds > 0; + final bool showPlayButton = widget.showPlayButton && !_latestValue.isPlaying && !_dragging; return GestureDetector( onTap: _latestValue.isPlaying @@ -630,6 +653,28 @@ class _CupertinoControlsState extends State ); } + Future _onQualityButtonTap() async { + _hideTimer?.cancel(); + + final chosenQuality = await showCupertinoModalPopup( + context: context, + semanticsDismissible: true, + useRootNavigator: chewieController.useRootNavigator, + builder: (context) => _QualityDialog( + qualities: chewieController.qualities.keys.toList(), + selected: chewieController.quality, + ), + ); + + if (chosenQuality != null && chosenQuality != chewieController.quality) { + _chewieController!.setQuality(chosenQuality); + } + + if (_latestValue.isPlaying) { + _startHideTimer(); + } + } + void _cancelAndRestartTimer() { _hideTimer?.cancel(); @@ -729,8 +774,8 @@ class _CupertinoControlsState extends State } void _playPause() { - final isFinished = _latestValue.position >= _latestValue.duration && - _latestValue.duration.inSeconds > 0; + final isFinished = + _latestValue.position >= _latestValue.duration && _latestValue.duration.inSeconds > 0; setState(() { if (controller.value.isPlaying) { @@ -757,8 +802,7 @@ class _CupertinoControlsState extends State Future _skipBack() async { _cancelAndRestartTimer(); final beginning = Duration.zero.inMilliseconds; - final skip = - (_latestValue.position - const Duration(seconds: 15)).inMilliseconds; + final skip = (_latestValue.position - const Duration(seconds: 15)).inMilliseconds; await controller.seekTo(Duration(milliseconds: math.max(skip, beginning))); // Restoring the video speed to selected speed // A delay of 1 second is added to ensure a smooth transition of speed after reversing the video as reversing is an asynchronous function @@ -770,8 +814,7 @@ class _CupertinoControlsState extends State Future _skipForward() async { _cancelAndRestartTimer(); final end = _latestValue.duration.inMilliseconds; - final skip = - (_latestValue.position + const Duration(seconds: 15)).inMilliseconds; + final skip = (_latestValue.position + const Duration(seconds: 15)).inMilliseconds; await controller.seekTo(Duration(milliseconds: math.min(skip, end))); // Restoring the video speed to selected speed // A delay of 1 second is added to ensure a smooth transition of speed after forwarding the video as forwaring is an asynchronous function @@ -848,8 +891,42 @@ class _PlaybackSpeedDialog extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (e == _selected) - Icon(Icons.check, size: 20.0, color: selectedColor), + if (e == _selected) Icon(Icons.check, size: 20.0, color: selectedColor), + Text(e.toString()), + ], + ), + ), + ) + .toList(), + ); + } +} + +class _QualityDialog extends StatelessWidget { + const _QualityDialog({ + required List qualities, + required String selected, + }) : _qualities = qualities, + _selected = selected; + + final List _qualities; + final String _selected; + + @override + Widget build(BuildContext context) { + final selectedColor = CupertinoTheme.of(context).primaryColor; + + return CupertinoActionSheet( + actions: _qualities + .map( + (e) => CupertinoActionSheetAction( + onPressed: () { + Navigator.of(context).pop(e); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (e == _selected) Icon(Icons.check, size: 20.0, color: selectedColor), Text(e.toString()), ], ), diff --git a/lib/src/material/material_controls.dart b/lib/src/material/material_controls.dart index 9b20f0722..b4b610884 100644 --- a/lib/src/material/material_controls.dart +++ b/lib/src/material/material_controls.dart @@ -8,6 +8,7 @@ import 'package:chewie/src/helpers/utils.dart'; import 'package:chewie/src/material/material_progress_bar.dart'; import 'package:chewie/src/material/widgets/options_dialog.dart'; import 'package:chewie/src/material/widgets/playback_speed_dialog.dart'; +import 'package:chewie/src/material/widgets/quality_dialog.dart'; import 'package:chewie/src/models/option_item.dart'; import 'package:chewie/src/models/subtitle_model.dart'; import 'package:chewie/src/notifiers/index.dart'; @@ -132,6 +133,15 @@ class _MaterialControlsState extends State void didChangeDependencies() { final oldController = _chewieController; _chewieController = ChewieController.of(context); + _chewieController?.addListener(() { + if (mounted) { + setState(() { + controller = chewieController.videoPlayerController; + }); + _dispose(); + _initialize(); + } + }); controller = chewieController.videoPlayerController; if (oldController != chewieController) { @@ -161,7 +171,7 @@ class _MaterialControlsState extends State ); } - Widget _buildOptionsButton() { + List _buildOptions(BuildContext context) { final options = [ OptionItem( onTap: () async { @@ -169,16 +179,26 @@ class _MaterialControlsState extends State _onSpeedButtonTap(); }, iconData: Icons.speed, - title: chewieController.optionsTranslation?.playbackSpeedButtonText ?? - 'Playback speed', - ) + title: chewieController.optionsTranslation?.playbackSpeedButtonText ?? 'Playback speed', + ), + if (chewieController.allowQualityChanging) + OptionItem( + onTap: () async { + Navigator.pop(context); + _onQualityButtonTap(); + }, + iconData: Icons.settings, + title: chewieController.optionsTranslation?.qualityButtonText ?? 'Quality', + ), ]; - if (chewieController.additionalOptions != null && chewieController.additionalOptions!(context).isNotEmpty) { options.addAll(chewieController.additionalOptions!(context)); } + return options; + } + Widget _buildOptionsButton() { return AnimatedOpacity( opacity: notifier.hideStuff ? 0.0 : 1.0, duration: const Duration(milliseconds: 250), @@ -187,16 +207,15 @@ class _MaterialControlsState extends State _hideTimer?.cancel(); if (chewieController.optionsBuilder != null) { - await chewieController.optionsBuilder!(context, options); + await chewieController.optionsBuilder!(context, _buildOptions(context)); } else { await showModalBottomSheet( context: context, isScrollControlled: true, useRootNavigator: chewieController.useRootNavigator, builder: (context) => OptionsDialog( - options: options, - cancelButtonText: - chewieController.optionsTranslation?.cancelButtonText, + options: _buildOptions(context), + cancelButtonText: chewieController.optionsTranslation?.cancelButtonText, ), ); } @@ -457,6 +476,28 @@ class _MaterialControlsState extends State } } + Future _onQualityButtonTap() async { + _hideTimer?.cancel(); + + final chosenQuality = await showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: chewieController.useRootNavigator, + builder: (context) => QualityDialog( + qualities: chewieController.qualities.keys.toList(), + selected: chewieController.quality, + ), + ); + + if (chosenQuality != null && chosenQuality != chewieController.quality) { + _chewieController!.setQuality(chosenQuality); + } + + if (_latestValue.isPlaying) { + _startHideTimer(); + } + } + Widget _buildPosition(Color? iconColor) { final position = _latestValue.position; final duration = _latestValue.duration; diff --git a/lib/src/material/widgets/quality_dialog.dart b/lib/src/material/widgets/quality_dialog.dart new file mode 100644 index 000000000..35d3ec24f --- /dev/null +++ b/lib/src/material/widgets/quality_dialog.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +class QualityDialog extends StatelessWidget { + const QualityDialog({ + super.key, + required List qualities, + required String selected, + }) : _qualities = qualities, + _selected = selected; + + final List _qualities; + final String _selected; + + @override + Widget build(BuildContext context) { + final Color selectedColor = Theme.of(context).primaryColor; + + return ListView.builder( + shrinkWrap: true, + physics: const ScrollPhysics(), + itemBuilder: (context, index) { + final quality = _qualities[index]; + return ListTile( + dense: true, + title: Row( + children: [ + if (quality == _selected) + Icon( + Icons.check, + size: 20.0, + color: selectedColor, + ) + else + Container(width: 20.0), + const SizedBox(width: 16.0), + Text(quality), + ], + ), + selected: quality == _selected, + onTap: () { + Navigator.of(context).pop(quality); + }, + ); + }, + itemCount: _qualities.length, + ); + } +} diff --git a/lib/src/models/options_translation.dart b/lib/src/models/options_translation.dart index ccbe97654..e36f5e95e 100644 --- a/lib/src/models/options_translation.dart +++ b/lib/src/models/options_translation.dart @@ -3,28 +3,32 @@ class OptionsTranslation { this.playbackSpeedButtonText, this.subtitlesButtonText, this.cancelButtonText, + this.qualityButtonText, }); String? playbackSpeedButtonText; String? subtitlesButtonText; String? cancelButtonText; + String? qualityButtonText; OptionsTranslation copyWith({ String? playbackSpeedButtonText, String? subtitlesButtonText, String? cancelButtonText, + String? qualityButtonText, }) { return OptionsTranslation( playbackSpeedButtonText: playbackSpeedButtonText ?? this.playbackSpeedButtonText, subtitlesButtonText: subtitlesButtonText ?? this.subtitlesButtonText, cancelButtonText: cancelButtonText ?? this.cancelButtonText, + qualityButtonText: qualityButtonText ?? this.qualityButtonText, ); } @override String toString() => - 'OptionsTranslation(playbackSpeedButtonText: $playbackSpeedButtonText, subtitlesButtonText: $subtitlesButtonText, cancelButtonText: $cancelButtonText)'; + 'OptionsTranslation(playbackSpeedButtonText: $playbackSpeedButtonText, subtitlesButtonText: $subtitlesButtonText, cancelButtonText: $cancelButtonText, qualityButtonText: $qualityButtonText)'; @override bool operator ==(Object other) { @@ -33,12 +37,14 @@ class OptionsTranslation { return other is OptionsTranslation && other.playbackSpeedButtonText == playbackSpeedButtonText && other.subtitlesButtonText == subtitlesButtonText && - other.cancelButtonText == cancelButtonText; + other.cancelButtonText == cancelButtonText && + other.qualityButtonText == qualityButtonText; } @override int get hashCode => playbackSpeedButtonText.hashCode ^ subtitlesButtonText.hashCode ^ - cancelButtonText.hashCode; + cancelButtonText.hashCode ^ + qualityButtonText.hashCode; } diff --git a/lib/src/player_with_controls.dart b/lib/src/player_with_controls.dart index 65d51a69b..c2bb26f3f 100644 --- a/lib/src/player_with_controls.dart +++ b/lib/src/player_with_controls.dart @@ -5,93 +5,107 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:video_player/video_player.dart'; -class PlayerWithControls extends StatelessWidget { +class PlayerWithControls extends StatefulWidget { const PlayerWithControls({super.key}); @override - Widget build(BuildContext context) { - final ChewieController chewieController = ChewieController.of(context); + State createState() => _PlayerWithControlsState(); +} + +class _PlayerWithControlsState extends State { + late final ChewieController _chewieController; + + @override + void didChangeDependencies() { + _chewieController = ChewieController.of(context) + ..addListener(() { + if (mounted) setState(() {}); + }); - double calculateAspectRatio(BuildContext context) { - final size = MediaQuery.of(context).size; - final width = size.width; - final height = size.height; + super.didChangeDependencies(); + } - return width > height ? width / height : height / width; - } + double calculateAspectRatio(BuildContext context) { + final size = MediaQuery.of(context).size; + final width = size.width; + final height = size.height; - Widget buildControls( - BuildContext context, - ChewieController chewieController, - ) { - return chewieController.showControls - ? chewieController.customControls ?? const AdaptiveControls() - : const SizedBox(); - } + return width > height ? width / height : height / width; + } - Widget buildPlayerWithControls( - ChewieController chewieController, - BuildContext context, - ) { - return Stack( - children: [ + Widget buildControls( + BuildContext context, + ChewieController chewieController, + ) { + return chewieController.showControls + ? chewieController.customControls ?? const AdaptiveControls() + : const SizedBox(); + } + + Widget buildPlayerWithControls( + ChewieController chewieController, + BuildContext context, + ) { + return Stack( + children: [ if (chewieController.placeholder != null) chewieController.placeholder!, - InteractiveViewer( - transformationController: chewieController.transformationController, - maxScale: chewieController.maxScale, - panEnabled: chewieController.zoomAndPan, - scaleEnabled: chewieController.zoomAndPan, - child: Center( - child: AspectRatio( - aspectRatio: chewieController.aspectRatio ?? - chewieController.videoPlayerController.value.aspectRatio, - child: VideoPlayer(chewieController.videoPlayerController), - ), + InteractiveViewer( + transformationController: chewieController.transformationController, + maxScale: chewieController.maxScale, + panEnabled: chewieController.zoomAndPan, + scaleEnabled: chewieController.zoomAndPan, + child: Center( + child: AspectRatio( + aspectRatio: chewieController.aspectRatio ?? + chewieController.videoPlayerController.value.aspectRatio, + child: VideoPlayer(chewieController.videoPlayerController), ), ), - if (chewieController.overlay != null) chewieController.overlay!, - if (Theme.of(context).platform != TargetPlatform.iOS) - Consumer( - builder: ( - BuildContext context, - PlayerNotifier notifier, - Widget? widget, - ) => - Visibility( - visible: !notifier.hideStuff, - child: AnimatedOpacity( - opacity: notifier.hideStuff ? 0.0 : 0.8, - duration: const Duration( - milliseconds: 250, - ), - child: const DecoratedBox( - decoration: BoxDecoration(color: Colors.black54), - child: SizedBox.expand(), - ), + ), + if (chewieController.overlay != null) chewieController.overlay!, + if (Theme.of(context).platform != TargetPlatform.iOS) + Consumer( + builder: ( + BuildContext context, + PlayerNotifier notifier, + Widget? widget, + ) => + Visibility( + visible: !notifier.hideStuff, + child: AnimatedOpacity( + opacity: notifier.hideStuff ? 0.0 : 0.8, + duration: const Duration( + milliseconds: 250, + ), + child: const DecoratedBox( + decoration: BoxDecoration(color: Colors.black54), + child: SizedBox.expand(), ), ), ), - if (!chewieController.isFullScreen) - buildControls(context, chewieController) - else - SafeArea( - bottom: false, - child: buildControls(context, chewieController), - ), - ], - ); - } + ), + if (!chewieController.isFullScreen) + buildControls(context, chewieController) + else + SafeArea( + bottom: false, + child: buildControls(context, chewieController), + ), + ], + ); + } - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { return Center( child: SizedBox( height: constraints.maxHeight, width: constraints.maxWidth, child: AspectRatio( aspectRatio: calculateAspectRatio(context), - child: buildPlayerWithControls(chewieController, context), + child: buildPlayerWithControls(_chewieController, context), ), ), );