From 331aeda392655cc876b4d89e4344585719d8f0e7 Mon Sep 17 00:00:00 2001 From: "k.akmalova" Date: Wed, 23 Oct 2024 11:33:46 +0300 Subject: [PATCH 1/3] feat: fixed speed for android and added qualities --- example/lib/app/app.dart | 20 ++- lib/src/chewie_player.dart | 62 +++++++- lib/src/cupertino/cupertino_controls.dart | 106 +++++++++++--- lib/src/material/material_controls.dart | 57 ++++++-- lib/src/material/widgets/quality_dialog.dart | 48 ++++++ lib/src/models/options_translation.dart | 12 +- lib/src/player_with_controls.dart | 146 ++++++++++--------- 7 files changed, 350 insertions(+), 101 deletions(-) create mode 100644 lib/src/material/widgets/quality_dialog.dart 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..c4a47e353 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,8 @@ class ChewieController extends ChangeNotifier { this.progressIndicatorDelay, this.hideControlsTimer = defaultHideControlsTimer, this.controlsSafeAreaMinimum = EdgeInsets.zero, + this.allowQualityChanging = false, + this.qualities = const {}, }) : assert( playbackSpeeds.every((speed) => speed > 0), 'The playbackSpeeds values must all be greater than 0', @@ -363,6 +366,8 @@ class ChewieController extends ChangeNotifier { Animation, ChewieControllerProvider, )? routePageBuilder, + bool? allowQualityChanging, + Map? qualities, }) { return ChewieController( draggableProgressBar: draggableProgressBar ?? this.draggableProgressBar, @@ -455,7 +460,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 +580,13 @@ 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; + static ChewieController of(BuildContext context) { final chewieControllerProvider = context.dependOnInheritedWidgetOfExactType()!; @@ -584,10 +596,14 @@ 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 { await videoPlayerController.setLooping(looping); @@ -611,6 +627,8 @@ class ChewieController extends ChangeNotifier { if (fullScreenByDefault) { videoPlayerController.addListener(_fullScreenListener); } + + _quality = qualities.keys.last; } Future _fullScreenListener() async { @@ -663,6 +681,46 @@ class ChewieController extends ChangeNotifier { void setSubtitle(List newSubtitle) { subtitle = Subtitles(newSubtitle); } + + Future setQuality(String quality) async { + final currentPosition = await videoPlayerController.position; + + 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.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..47eb3d739 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,13 @@ class _CupertinoControlsState extends State void didChangeDependencies() { final oldController = _chewieController; _chewieController = ChewieController.of(context); + _chewieController?.addListener(() { + setState(() { + controller = chewieController.videoPlayerController; + _dispose(); + _initialize(); + }); + }); controller = chewieController.videoPlayerController; if (oldController != chewieController) { @@ -161,6 +167,19 @@ class _CupertinoControlsState extends State ) { 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)); @@ -179,8 +198,7 @@ class _CupertinoControlsState extends State useRootNavigator: chewieController.useRootNavigator, builder: (context) => CupertinoOptionsDialog( options: options, - cancelButtonText: - chewieController.optionsTranslation?.cancelButtonText, + cancelButtonText: chewieController.optionsTranslation?.cancelButtonText, ), ); if (_latestValue.isPlaying) { @@ -285,8 +303,7 @@ class _CupertinoControlsState extends State if (chewieController.allowPlaybackSpeedChanging) _buildSpeedButton(controller, iconColor, barHeight), if (chewieController.additionalOptions != null && - chewieController - .additionalOptions!(context).isNotEmpty) + chewieController.additionalOptions!(context).isNotEmpty) _buildOptionsButton(iconColor, barHeight), ], ), @@ -347,10 +364,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 +646,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 +767,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 +795,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 +807,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 +884,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..41476247c 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,13 @@ class _MaterialControlsState extends State void didChangeDependencies() { final oldController = _chewieController; _chewieController = ChewieController.of(context); + _chewieController?.addListener(() { + setState(() { + controller = chewieController.videoPlayerController; + _dispose(); + _initialize(); + }); + }); controller = chewieController.videoPlayerController; if (oldController != chewieController) { @@ -161,7 +169,7 @@ class _MaterialControlsState extends State ); } - Widget _buildOptionsButton() { + List _buildOptions(BuildContext context) { final options = [ OptionItem( onTap: () async { @@ -169,16 +177,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 +205,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 +474,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), ), ), ); From b3cb43bc04d665bf0b407337b5001e3812d02825 Mon Sep 17 00:00:00 2001 From: "k.akmalova" Date: Wed, 23 Oct 2024 12:37:19 +0300 Subject: [PATCH 2/3] fix: added mounted --- lib/src/cupertino/cupertino_controls.dart | 8 +++++--- lib/src/material/material_controls.dart | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/cupertino/cupertino_controls.dart b/lib/src/cupertino/cupertino_controls.dart index 47eb3d739..a636de78b 100644 --- a/lib/src/cupertino/cupertino_controls.dart +++ b/lib/src/cupertino/cupertino_controls.dart @@ -145,11 +145,13 @@ class _CupertinoControlsState extends State with SingleTicker final oldController = _chewieController; _chewieController = ChewieController.of(context); _chewieController?.addListener(() { - setState(() { - controller = chewieController.videoPlayerController; + if (mounted) { + setState(() { + controller = chewieController.videoPlayerController; + }); _dispose(); _initialize(); - }); + } }); controller = chewieController.videoPlayerController; diff --git a/lib/src/material/material_controls.dart b/lib/src/material/material_controls.dart index 41476247c..b4b610884 100644 --- a/lib/src/material/material_controls.dart +++ b/lib/src/material/material_controls.dart @@ -134,11 +134,13 @@ class _MaterialControlsState extends State final oldController = _chewieController; _chewieController = ChewieController.of(context); _chewieController?.addListener(() { - setState(() { - controller = chewieController.videoPlayerController; + if (mounted) { + setState(() { + controller = chewieController.videoPlayerController; + }); _dispose(); _initialize(); - }); + } }); controller = chewieController.videoPlayerController; From 5e18b4159ea287bbf5542f442a02ef94c376b9fe Mon Sep 17 00:00:00 2001 From: "k.akmalova" Date: Thu, 24 Oct 2024 19:54:57 +0300 Subject: [PATCH 3/3] fix: fixed speed --- lib/src/chewie_player.dart | 15 +++++++--- lib/src/cupertino/cupertino_controls.dart | 35 +++++++++++++---------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/lib/src/chewie_player.dart b/lib/src/chewie_player.dart index c4a47e353..380f71861 100644 --- a/lib/src/chewie_player.dart +++ b/lib/src/chewie_player.dart @@ -310,6 +310,7 @@ class ChewieController extends ChangeNotifier { 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', @@ -368,6 +369,7 @@ class ChewieController extends ChangeNotifier { )? routePageBuilder, bool? allowQualityChanging, Map? qualities, + void Function(VideoPlayerController)? onVideoControllerChanged, }) { return ChewieController( draggableProgressBar: draggableProgressBar ?? this.draggableProgressBar, @@ -420,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, ); } @@ -587,6 +589,9 @@ class ChewieController extends ChangeNotifier { /// 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()!; @@ -605,6 +610,8 @@ class ChewieController extends ChangeNotifier { String get quality => _quality; Future _initialize() async { + _quality = qualities.keys.last; + await videoPlayerController.setLooping(looping); if ((autoInitialize || autoPlay) && @@ -627,8 +634,6 @@ class ChewieController extends ChangeNotifier { if (fullScreenByDefault) { videoPlayerController.addListener(_fullScreenListener); } - - _quality = qualities.keys.last; } Future _fullScreenListener() async { @@ -684,6 +689,7 @@ class ChewieController extends ChangeNotifier { Future setQuality(String quality) async { final currentPosition = await videoPlayerController.position; + final speed = videoPlayerController.value.playbackSpeed; videoPlayerController.dispose(); @@ -716,6 +722,7 @@ class ChewieController extends ChangeNotifier { await videoPlayerController.initialize(); await videoPlayerController.seekTo(currentPosition!); + await videoPlayerController.setPlaybackSpeed(speed); await videoPlayerController.play(); _quality = quality; diff --git a/lib/src/cupertino/cupertino_controls.dart b/lib/src/cupertino/cupertino_controls.dart index a636de78b..a015ebdf5 100644 --- a/lib/src/cupertino/cupertino_controls.dart +++ b/lib/src/cupertino/cupertino_controls.dart @@ -163,10 +163,7 @@ class _CupertinoControlsState extends State with SingleTicker super.didChangeDependencies(); } - GestureDetector _buildOptionsButton( - Color iconColor, - double barHeight, - ) { + List _buildOptions(BuildContext context) { final options = []; if (chewieController.allowQualityChanging) { @@ -187,19 +184,26 @@ class _CupertinoControlsState extends State with SingleTicker 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, + options: _buildOptions(context), cancelButtonText: chewieController.optionsTranslation?.cancelButtonText, ), ); @@ -305,7 +309,8 @@ class _CupertinoControlsState extends State with SingleTicker if (chewieController.allowPlaybackSpeedChanging) _buildSpeedButton(controller, iconColor, barHeight), if (chewieController.additionalOptions != null && - chewieController.additionalOptions!(context).isNotEmpty) + chewieController.additionalOptions!(context).isNotEmpty || + chewieController.allowQualityChanging) _buildOptionsButton(iconColor, barHeight), ], ), @@ -652,14 +657,14 @@ class _CupertinoControlsState extends State with SingleTicker _hideTimer?.cancel(); final chosenQuality = await showCupertinoModalPopup( - context: context, - semanticsDismissible: true, - useRootNavigator: chewieController.useRootNavigator, - builder: (context) => _QualityDialog( - qualities: chewieController.qualities.keys.toList(), - selected: chewieController.quality, - ), - ); + 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);