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

Support different resolutions of video #492

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
22 changes: 21 additions & 1 deletion example/lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class _ChewieDemoState extends State<ChewieDemo> {

Future<void> initializePlayer() async {
_videoPlayerController1 = VideoPlayerController.network(
'https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4');
'https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4');
_videoPlayerController2 = VideoPlayerController.network(
'https://assets.mixkit.co/videos/preview/mixkit-a-girl-blowing-a-bubble-gum-at-an-amusement-park-1226-large.mp4');
await Future.wait([
Expand All @@ -52,6 +52,16 @@ class _ChewieDemoState extends State<ChewieDemo> {
videoPlayerController: _videoPlayerController1,
autoPlay: true,
looping: true,
resolutions: {
"480p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4",
"640p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_640_3MG.mp4",
"1280p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4",
"1920p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1920_18MG.mp4"
},
subtitle: Subtitles([
Subtitle(
index: 0,
Expand Down Expand Up @@ -139,6 +149,16 @@ class _ChewieDemoState extends State<ChewieDemo> {
videoPlayerController: _videoPlayerController1,
autoPlay: true,
looping: true,
resolutions: {
"480p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4",
"640p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_640_3MG.mp4",
"1280p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4",
"1920p":
"https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1920_18MG.mp4"
},
subtitle: Subtitles([
Subtitle(
index: 0,
Expand Down
87 changes: 63 additions & 24 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/material/models/options_translation.dart';
Expand All @@ -14,6 +15,9 @@ import 'package:chewie/src/models/subtitle_model.dart';

import 'material/models/option_item.dart';

import 'models/video_player_controller_extension.dart'
show VideoPlayerControllerExtension;

typedef ChewieRoutePageBuilder = Widget Function(
BuildContext context,
Animation<double> animation,
Expand Down Expand Up @@ -42,13 +46,19 @@ class Chewie extends StatefulWidget {

class ChewieState extends State<Chewie> {
bool _isFullScreen = false;
late PlayerNotifier notifier;
late _ChewieControllerProvider controllerProvider;

@override
void initState() {
super.initState();
widget.controller.addListener(listener);
notifier = PlayerNotifier.init();
controllerProvider = _ChewieControllerProvider(
controller: widget.controller,
child: ChangeNotifierProvider<PlayerNotifier>.value(
value: PlayerNotifier(),
builder: (context, w) => const PlayerWithControls(),
),
);
}

@override
Expand Down Expand Up @@ -77,13 +87,7 @@ class ChewieState extends State<Chewie> {

@override
Widget build(BuildContext context) {
return _ChewieControllerProvider(
controller: widget.controller,
child: ChangeNotifierProvider<PlayerNotifier>.value(
value: notifier,
builder: (context, w) => const PlayerWithControls(),
),
);
return controllerProvider;
}

Widget _buildFullScreenVideo(
Expand Down Expand Up @@ -120,14 +124,6 @@ class ChewieState extends State<Chewie> {
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
final controllerProvider = _ChewieControllerProvider(
controller: widget.controller,
child: ChangeNotifierProvider<PlayerNotifier>.value(
value: notifier,
builder: (context, w) => const PlayerWithControls(),
),
);

if (widget.controller.routePageBuilder == null) {
return _defaultRoutePageBuilder(
context, animation, secondaryAnimation, controllerProvider);
Expand Down Expand Up @@ -221,7 +217,7 @@ class ChewieState extends State<Chewie> {
class ChewieController extends ChangeNotifier {
ChewieController({
required this.videoPlayerController,
this.optionsTranslation,
this.resolutions,
this.aspectRatio,
this.autoInitialize = false,
this.autoPlay = false,
Expand All @@ -234,6 +230,7 @@ class ChewieController extends ChangeNotifier {
this.overlay,
this.showControlsOnInitialize = true,
this.showOptions = true,
this.optionsTranslation,
this.optionsBuilder,
this.additionalOptions,
this.showControls = true,
Expand All @@ -259,6 +256,7 @@ class ChewieController extends ChangeNotifier {

ChewieController copyWith({
VideoPlayerController? videoPlayerController,
Map<String, String>? resolutions,
OptionsTranslation? optionsTranslation,
double? aspectRatio,
bool? autoInitialize,
Expand Down Expand Up @@ -296,6 +294,7 @@ class ChewieController extends ChangeNotifier {
return ChewieController(
videoPlayerController:
videoPlayerController ?? this.videoPlayerController,
resolutions: resolutions ?? this.resolutions,
optionsTranslation: optionsTranslation ?? this.optionsTranslation,
aspectRatio: aspectRatio ?? this.aspectRatio,
autoInitialize: autoInitialize ?? this.autoInitialize,
Expand Down Expand Up @@ -339,11 +338,24 @@ class ChewieController extends ChangeNotifier {
);
}

/// Set your custom resolutions here like for example:
/// ```dart
/// {
/// '480p': 'video_480p.mp4',
/// '720p': 'video_720p.mp4',
/// '1080p': 'video_1080p.mp4',
/// }
/// ```
///
/// Default: `null` -> resolutions/quality button will be hidden
final Map<String, String>? resolutions;

/// If false, the options button in MaterialUI and MaterialDesktopUI
/// won't be shown.
final bool showOptions;

/// Pass your translations for the options like:
/// - Resolution
/// - PlaybackSpeed
/// - Subtitles
/// - Cancel
Expand All @@ -370,7 +382,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 @@ -469,7 +481,9 @@ class ChewieController extends ChangeNotifier {

bool get isPlaying => videoPlayerController.value.isPlaying;

Future _initialize() async {
Future _initialize({
Duration? continueAt,
}) async {
await videoPlayerController.setLooping(looping);

if ((autoInitialize || autoPlay) &&
Expand All @@ -485,8 +499,12 @@ class ChewieController extends ChangeNotifier {
await videoPlayerController.play();
}

if (startAt != null) {
await videoPlayerController.seekTo(startAt!);
if (continueAt != null) {
await videoPlayerController.seekTo(continueAt);
} else {
if (startAt != null) {
await videoPlayerController.seekTo(startAt!);
}
}

if (fullScreenByDefault) {
Expand Down Expand Up @@ -544,6 +562,28 @@ class ChewieController extends ChangeNotifier {
void setSubtitle(List<Subtitle> newSubtitle) {
subtitle = Subtitles(newSubtitle);
}

Future<void> setResolution(String url) async {
final position = await videoPlayerController.position;

switch (videoPlayerController.dataSourceType) {
case DataSourceType.asset:
videoPlayerController =
videoPlayerController.copyWithAsset(dataSource: url);
break;
case DataSourceType.file:
videoPlayerController = videoPlayerController.copyWithFile(
file: File.fromUri(Uri.parse(url)));
break;
case DataSourceType.network:
videoPlayerController =
videoPlayerController.copyWithNetwork(dataSource: url);
break;
default:
}

await _initialize(continueAt: position);
}
}

class _ChewieControllerProvider extends InheritedWidget {
Expand All @@ -556,6 +596,5 @@ class _ChewieControllerProvider extends InheritedWidget {
final ChewieController controller;

@override
bool updateShouldNotify(_ChewieControllerProvider old) =>
controller != old.controller;
bool updateShouldNotify(_ChewieControllerProvider old) => true;
}
50 changes: 49 additions & 1 deletion lib/src/material/material_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import 'package:chewie/src/material/models/option_item.dart';
import 'package:chewie/src/material/widgets/options_dialog.dart';
import 'package:chewie/src/notifiers/index.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/src/models/subtitle_model.dart';

import 'widgets/playback_speed_dialog.dart';
import 'widgets/resolution_dialog.dart';

class MaterialControls extends StatefulWidget {
const MaterialControls({Key? key}) : super(key: key);
Expand Down Expand Up @@ -159,7 +161,7 @@ class _MaterialControlsState extends State<MaterialControls>
iconData: Icons.speed,
title: chewieController.optionsTranslation?.playbackSpeedButtonText ??
'Playback speed',
)
),
];

if (chewieController.subtitle != null &&
Expand All @@ -179,6 +181,21 @@ class _MaterialControlsState extends State<MaterialControls>
);
}

if (chewieController.resolutions != null &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with if (chewieController.resolutions?.isNotEmpty ?? false) {.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx will do it

chewieController.resolutions!.isNotEmpty) {
options.add(
OptionItem(
onTap: () {
Navigator.pop(context);
_onResolutionTap();
},
iconData: Icons.settings,
title: chewieController.optionsTranslation?.resolutionButtonText ??
'Resolution',
),
);
}

if (chewieController.additionalOptions != null &&
chewieController.additionalOptions!(context).isNotEmpty) {
options.addAll(chewieController.additionalOptions!(context));
Expand Down Expand Up @@ -442,6 +459,37 @@ class _MaterialControlsState extends State<MaterialControls>
});
}

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

final choosenResolution = await showModalBottomSheet<String>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename variable to chosenResolution.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx will do it

context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => ResolutionDialog(
reslutions: chewieController.resolutions!,
selectedResolution: notifier.selectedResolution,
cancelButtonText: chewieController.optionsTranslation?.cancelButtonText,
),
);

if (choosenResolution != null) {
notifier.selectedResolution = choosenResolution;

await chewieController
.setResolution(chewieController.resolutions![choosenResolution]!);
}

if (_latestValue.isPlaying) {
_startHideTimer();
}
SchedulerBinding.instance!.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}

void _cancelAndRestartTimer() {
_hideTimer?.cancel();
_startHideTimer();
Expand Down
8 changes: 7 additions & 1 deletion lib/src/material/models/options_translation.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
class OptionsTranslation {
OptionsTranslation({
this.resolutionButtonText,
this.playbackSpeedButtonText,
this.subtitlesButtonText,
this.cancelButtonText,
});

String? resolutionButtonText;
String? playbackSpeedButtonText;
String? subtitlesButtonText;
String? cancelButtonText;

OptionsTranslation copyWith({
String? resolutionButtonText,
String? playbackSpeedButtonText,
String? subtitlesButtonText,
String? cancelButtonText,
}) {
return OptionsTranslation(
resolutionButtonText: resolutionButtonText ?? this.resolutionButtonText,
playbackSpeedButtonText:
playbackSpeedButtonText ?? this.playbackSpeedButtonText,
subtitlesButtonText: subtitlesButtonText ?? this.subtitlesButtonText,
Expand All @@ -24,20 +28,22 @@ class OptionsTranslation {

@override
String toString() =>
'OptionsTranslation(playbackSpeedButtonText: $playbackSpeedButtonText, subtitlesButtonText: $subtitlesButtonText, cancelButtonText: $cancelButtonText)';
'OptionsTranslation(resolutionButtonText: $resolutionButtonText, playbackSpeedButtonText: $playbackSpeedButtonText, subtitlesButtonText: $subtitlesButtonText, cancelButtonText: $cancelButtonText)';

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is OptionsTranslation &&
other.resolutionButtonText == resolutionButtonText &&
other.playbackSpeedButtonText == playbackSpeedButtonText &&
other.subtitlesButtonText == subtitlesButtonText &&
other.cancelButtonText == cancelButtonText;
}

@override
int get hashCode =>
resolutionButtonText.hashCode ^
playbackSpeedButtonText.hashCode ^
subtitlesButtonText.hashCode ^
cancelButtonText.hashCode;
Expand Down
Loading