Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed pop the wrong page when changing the speed and added support for multiple resolutions #867

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions example/lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ class _ChewieDemoState extends State<ChewieDemo> {
}

List<String> 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<void> initializePlayer() async {
Expand Down Expand Up @@ -153,6 +154,19 @@ class _ChewieDemoState extends State<ChewieDemo> {
// 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',
},
);
}

Expand Down
73 changes: 69 additions & 4 deletions lib/src/chewie_player.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -74,7 +75,7 @@ class ChewieState extends State<Chewie> {
if (isControllerFullScreen && !_isFullScreen) {
_isFullScreen = isControllerFullScreen;
await _pushFullScreenWidget(context);
} else if (_isFullScreen) {
} else if (!isControllerFullScreen && _isFullScreen) {
Navigator.of(
context,
rootNavigator: widget.controller.useRootNavigator,
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -363,6 +367,9 @@ class ChewieController extends ChangeNotifier {
Animation<double>,
ChewieControllerProvider,
)? routePageBuilder,
bool? allowQualityChanging,
Map<String, String>? qualities,
void Function(VideoPlayerController)? onVideoControllerChanged,
}) {
return ChewieController(
draggableProgressBar: draggableProgressBar ?? this.draggableProgressBar,
Expand Down Expand Up @@ -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,
);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, String> qualities;

/// Called when the video controller changes
final void Function(VideoPlayerController)? onVideoControllerChanged;

static ChewieController of(BuildContext context) {
final chewieControllerProvider =
context.dependOnInheritedWidgetOfExactType<ChewieControllerProvider>()!;
Expand All @@ -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<dynamic> _initialize() async {
_quality = qualities.keys.last;

await videoPlayerController.setLooping(looping);

if ((autoInitialize || autoPlay) &&
Expand Down Expand Up @@ -663,6 +686,48 @@ class ChewieController extends ChangeNotifier {
void setSubtitle(List<Subtitle> newSubtitle) {
subtitle = Subtitles(newSubtitle);
}

Future<void> 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 {
Expand Down
125 changes: 101 additions & 24 deletions lib/src/cupertino/cupertino_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ class CupertinoControls extends StatefulWidget {
}
}

class _CupertinoControlsState extends State<CupertinoControls>
with SingleTickerProviderStateMixin {
class _CupertinoControlsState extends State<CupertinoControls> with SingleTickerProviderStateMixin {
late PlayerNotifier notifier;
late VideoPlayerValue _latestValue;
double? _latestVolume;
Expand Down Expand Up @@ -145,6 +144,15 @@ class _CupertinoControlsState extends State<CupertinoControls>
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) {
Expand All @@ -155,32 +163,48 @@ class _CupertinoControlsState extends State<CupertinoControls>
super.didChangeDependencies();
}

GestureDetector _buildOptionsButton(
Color iconColor,
double barHeight,
) {
List<OptionItem> _buildOptions(BuildContext context) {
final options = <OptionItem>[];

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<OptionItem>(
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) {
Expand Down Expand Up @@ -285,8 +309,8 @@ class _CupertinoControlsState extends State<CupertinoControls>
if (chewieController.allowPlaybackSpeedChanging)
_buildSpeedButton(controller, iconColor, barHeight),
if (chewieController.additionalOptions != null &&
chewieController
.additionalOptions!(context).isNotEmpty)
chewieController.additionalOptions!(context).isNotEmpty ||
chewieController.allowQualityChanging)
_buildOptionsButton(iconColor, barHeight),
],
),
Expand Down Expand Up @@ -347,10 +371,9 @@ class _CupertinoControlsState extends State<CupertinoControls>
}

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
Expand Down Expand Up @@ -630,6 +653,28 @@ class _CupertinoControlsState extends State<CupertinoControls>
);
}

Future<void> _onQualityButtonTap() async {
_hideTimer?.cancel();

final chosenQuality = await showCupertinoModalPopup<String>(
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();

Expand Down Expand Up @@ -729,8 +774,8 @@ class _CupertinoControlsState extends State<CupertinoControls>
}

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) {
Expand All @@ -757,8 +802,7 @@ class _CupertinoControlsState extends State<CupertinoControls>
Future<void> _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
Expand All @@ -770,8 +814,7 @@ class _CupertinoControlsState extends State<CupertinoControls>
Future<void> _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
Expand Down Expand Up @@ -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<String> qualities,
required String selected,
}) : _qualities = qualities,
_selected = selected;

final List<String> _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()),
],
),
Expand Down
Loading