diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index 592ceee..e8efba1 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 592ceee..399e934 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 0000000..1e8c3c9 --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/lib/main.dart b/example/lib/main.dart index f2535f7..09ecb15 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -36,7 +36,7 @@ class VideoScreen extends StatelessWidget { preferredSize: Size(0.0, 0.0), ), body: ListView(children: [ - VimeoPlayer(id: '395212534', autoPlay: true), + VimeoPlayer(id: '505942118', autoPlay: true, allowFullScreen: true), ])); } } diff --git a/example/lib/src/fullscreen_player.dart b/example/lib/src/fullscreen_player.dart deleted file mode 100644 index b674f18..0000000 --- a/example/lib/src/fullscreen_player.dart +++ /dev/null @@ -1,432 +0,0 @@ -library vimeoplayer; - -import 'package:flutter/material.dart'; -import 'package:video_player/video_player.dart'; -import 'package:flutter/services.dart'; -import 'quality_links.dart'; -import 'dart:async'; - -//Класс видео плеера во весь экран -class FullscreenPlayer extends StatefulWidget { - final String id; - final bool autoPlay; - final bool looping; - final VideoPlayerController controller; - final position; - final Future initFuture; - final String qualityValue; - - FullscreenPlayer({ - @required this.id, - this.autoPlay, - this.looping, - this.controller, - this.position, - this.initFuture, - this.qualityValue, - Key key, - }) : super(key: key); - - @override - _FullscreenPlayerState createState() => _FullscreenPlayerState( - id, autoPlay, looping, controller, position, initFuture, qualityValue); -} - -class _FullscreenPlayerState extends State { - String _id; - bool autoPlay = false; - bool looping = false; - bool _overlay = true; - bool fullScreen = true; - - VideoPlayerController controller; - VideoPlayerController _controller; - - int position; - - Future initFuture; - var qualityValue; - - _FullscreenPlayerState(this._id, this.autoPlay, this.looping, this.controller, - this.position, this.initFuture, this.qualityValue); - - // Quality Class - QualityLinks _quality; - Map _qualityValues; - - //Переменная перемотки - bool _seek = true; - - //Переменные видео - double videoHeight; - double videoWidth; - double videoMargin; - - //Переменные под зоны дабл-тапа - double doubleTapRMarginFS = 36; - double doubleTapRWidthFS = 700; - double doubleTapRHeightFS = 300; - double doubleTapLMarginFS = 10; - double doubleTapLWidthFS = 700; - double doubleTapLHeightFS = 400; - - @override - void initState() { - //Инициализация контроллеров видео при получении данных из Vimeo - _controller = controller; - if (autoPlay) _controller.play(); - - // Подгрузка списка качеств видео - _quality = QualityLinks(_id); //Create class - _quality.getQualitiesSync().then((value) { - _qualityValues = value; - }); - - setState(() { - SystemChrome.setPreferredOrientations( - [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); - SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]); - }); - - super.initState(); - } - - //Ослеживаем пользовательского нажатие назад и переводим - // на экран с плеером не в режиме фуллскрин, возвращаем ориентацию - Future _onWillPop() { - setState(() { - _controller.pause(); - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); - SystemChrome.setEnabledSystemUIOverlays( - [SystemUiOverlay.top, SystemUiOverlay.bottom]); - }); - Navigator.pop(context, _controller.value.position.inSeconds); - return Future.value(true); - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: _onWillPop, - child: Scaffold( - body: Center( - child: Stack( - alignment: AlignmentDirectional.center, - children: [ - GestureDetector( - child: FutureBuilder( - future: initFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - //Управление шириной и высотой видео - double delta = MediaQuery.of(context).size.width - - MediaQuery.of(context).size.height * - _controller.value.aspectRatio; - if (MediaQuery.of(context).orientation == - Orientation.portrait || - delta < 0) { - videoHeight = MediaQuery.of(context).size.width / - _controller.value.aspectRatio; - videoWidth = MediaQuery.of(context).size.width; - videoMargin = 0; - } else { - videoHeight = MediaQuery.of(context).size.height; - videoWidth = - videoHeight * _controller.value.aspectRatio; - videoMargin = - (MediaQuery.of(context).size.width - videoWidth) / - 2; - } - //Переменные дабл тапа, зависимые от размеров видео - doubleTapRWidthFS = videoWidth; - doubleTapRHeightFS = videoHeight - 36; - doubleTapLWidthFS = videoWidth; - doubleTapLHeightFS = videoHeight; - - //Сразу при входе в режим фуллскрин перематываем - // на нужное место - if (_seek && fullScreen) { - _controller.seekTo(Duration(seconds: position)); - _seek = false; - } - - //Переходи на нужное место при смене качества - if (_seek && _controller.value.duration.inSeconds > 2) { - _controller.seekTo(Duration(seconds: position)); - _seek = false; - } - SystemChrome.setEnabledSystemUIOverlays( - [SystemUiOverlay.bottom]); - - //Отрисовка элементов плеера - return Stack( - children: [ - Container( - height: videoHeight, - width: videoWidth, - margin: EdgeInsets.only(left: videoMargin), - child: VideoPlayer(_controller), - ), - _videoOverlay(), - ], - ); - } else { - return Center( - heightFactor: 6, - child: CircularProgressIndicator( - strokeWidth: 4, - valueColor: AlwaysStoppedAnimation( - Color(0xFF22A3D2)), - )); - } - }), - //Редактируем размер области дабл тапа при показе оверлея. - // Сделано для открытия кнопок "Во весь экран" и "Качество" - onTap: () { - setState(() { - _overlay = !_overlay; - if (_overlay) { - doubleTapRHeightFS = videoHeight - 36; - doubleTapLHeightFS = videoHeight - 10; - doubleTapRMarginFS = 36; - doubleTapLMarginFS = 10; - } else if (!_overlay) { - doubleTapRHeightFS = videoHeight + 36; - doubleTapLHeightFS = videoHeight; - doubleTapRMarginFS = 0; - doubleTapLMarginFS = 0; - } - }); - }, - ), - GestureDetector( - child: Container( - width: doubleTapLWidthFS / 2 - 30, - height: doubleTapLHeightFS - 44, - margin: - EdgeInsets.fromLTRB(0, 0, doubleTapLWidthFS / 2 + 30, 40), - decoration: BoxDecoration( - //color: Colors.red, - ), - ), - //Редактируем размер области дабл тапа при показе оверлея. - // Сделано для открытия кнопок "Во весь экран" и "Качество" - onTap: () { - setState(() { - _overlay = !_overlay; - if (_overlay) { - doubleTapRHeightFS = videoHeight - 36; - doubleTapLHeightFS = videoHeight - 10; - doubleTapRMarginFS = 36; - doubleTapLMarginFS = 10; - } else if (!_overlay) { - doubleTapRHeightFS = videoHeight + 36; - doubleTapLHeightFS = videoHeight; - doubleTapRMarginFS = 0; - doubleTapLMarginFS = 0; - } - }); - }, - onDoubleTap: () { - setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds - 10)); - }); - }), - GestureDetector( - child: Container( - width: doubleTapRWidthFS / 2 - 45, - height: doubleTapRHeightFS - 80, - margin: EdgeInsets.fromLTRB(doubleTapRWidthFS / 2 + 45, 0, 0, - doubleTapLMarginFS + 20), - decoration: BoxDecoration( - //color: Colors.red, - ), - ), - //Редактируем размер области дабл тапа при показе оверлея. - // Сделано для открытия кнопок "Во весь экран" и "Качество" - onTap: () { - setState(() { - _overlay = !_overlay; - if (_overlay) { - doubleTapRHeightFS = videoHeight - 36; - doubleTapLHeightFS = videoHeight - 10; - doubleTapRMarginFS = 36; - doubleTapLMarginFS = 10; - } else if (!_overlay) { - doubleTapRHeightFS = videoHeight + 36; - doubleTapLHeightFS = videoHeight; - doubleTapRMarginFS = 0; - doubleTapLMarginFS = 0; - } - }); - }, - onDoubleTap: () { - setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds + 10)); - }); - }), - ], - )))); - } - - //================================ Quality ================================// - void _settingModalBottomSheet(context) { - showModalBottomSheet( - context: context, - builder: (BuildContext bc) { - final children = []; - _qualityValues.forEach((elem, value) => (children.add(new ListTile( - title: new Text(" ${elem.toString()} fps"), - onTap: () => { - //Обновление состояние приложения и перерисовка - setState(() { - _controller.pause(); - _controller = VideoPlayerController.network(value); - _controller.setLooping(true); - _seek = true; - initFuture = _controller.initialize(); - _controller.play(); - }), - })))); - - return Container( - height: videoHeight, - child: ListView( - children: children, - ), - ); - }); - } - - //================================ OVERLAY ================================// - Widget _videoOverlay() { - return _overlay - ? Stack( - children: [ - GestureDetector( - child: Center( - child: Container( - width: videoWidth, - height: videoHeight, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerRight, - end: Alignment.centerLeft, - colors: [ - const Color(0x662F2C47), - const Color(0x662F2C47) - ], - ), - ), - ), - ), - ), - Center( - child: IconButton( - padding: EdgeInsets.only( - top: videoHeight / 2 - 50, - bottom: videoHeight / 2 - 30, - ), - icon: _controller.value.isPlaying - ? Icon(Icons.pause, size: 60.0) - : Icon(Icons.play_arrow, size: 60.0), - onPressed: () { - setState(() { - _controller.value.isPlaying - ? _controller.pause() - : _controller.play(); - }); - }), - ), - Container( - margin: EdgeInsets.only( - top: videoHeight - 80, left: videoWidth + videoMargin - 50), - child: IconButton( - alignment: AlignmentDirectional.center, - icon: Icon(Icons.fullscreen, size: 30.0), - onPressed: () { - setState(() { - _controller.pause(); - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitDown, - DeviceOrientation.portraitUp - ]); - SystemChrome.setEnabledSystemUIOverlays( - [SystemUiOverlay.top, SystemUiOverlay.bottom]); - }); - Navigator.pop( - context, _controller.value.position.inSeconds); - }), - ), - Container( - margin: EdgeInsets.only(left: videoWidth + videoMargin - 48), - child: IconButton( - icon: Icon(Icons.settings, size: 26.0), - onPressed: () { - position = _controller.value.position.inSeconds; - _seek = true; - _settingModalBottomSheet(context); - setState(() {}); - }), - ), - Container( - //===== Ползунок =====// - margin: EdgeInsets.only( - top: videoHeight - 40, left: videoMargin), //CHECK IT - child: _videoOverlaySlider(), - ) - ], - ) - : Center(); - } - - //=================== ПОЛЗУНОК ===================// - Widget _videoOverlaySlider() { - return ValueListenableBuilder( - valueListenable: _controller, - builder: (context, VideoPlayerValue value, child) { - if (!value.hasError && value.initialized) { - return Row( - children: [ - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.position.inMinutes.toString() + - ':' + - (value.position.inSeconds - value.position.inMinutes * 60) - .toString()), - ), - Container( - height: 20, - width: videoWidth - 92, - child: VideoProgressIndicator( - _controller, - allowScrubbing: true, - colors: VideoProgressColors( - playedColor: Color(0xFF22A3D2), - backgroundColor: Color(0x5515162B), - bufferedColor: Color(0x5583D8F7), - ), - padding: EdgeInsets.only(top: 8.0, bottom: 8.0), - ), - ), - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.duration.inMinutes.toString() + - ':' + - (value.duration.inSeconds - value.duration.inMinutes * 60) - .toString()), - ), - ], - ); - } else { - return Container(); - } - }, - ); - } -} diff --git a/example/lib/vimeoplayer.dart b/example/lib/vimeoplayer.dart index 6c6d506..44ae212 100644 --- a/example/lib/vimeoplayer.dart +++ b/example/lib/vimeoplayer.dart @@ -1,11 +1,11 @@ library vimeoplayer; +import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:flutter/services.dart'; import 'src/quality_links.dart'; import 'dart:async'; -import 'src/fullscreen_player.dart'; //Класс видео плеера class VimeoPlayer extends StatefulWidget { @@ -13,18 +13,29 @@ class VimeoPlayer extends StatefulWidget { final bool autoPlay; final bool looping; final int position; + final bool allowFullScreen; + final bool allowPlaybackSpeedChanging; VimeoPlayer({ @required this.id, this.autoPlay, this.looping, this.position, + @required this.allowFullScreen, + this.allowPlaybackSpeedChanging = false, Key key, - }) : super(key: key); + }) : assert(id != null && allowFullScreen != null), + super(key: key); @override - _VimeoPlayerState createState() => - _VimeoPlayerState(id, autoPlay, looping, position); + _VimeoPlayerState createState() => _VimeoPlayerState( + id, + autoPlay, + looping, + position, + allowFullScreen, + allowPlaybackSpeedChanging, + ); } class _VimeoPlayerState extends State { @@ -33,12 +44,23 @@ class _VimeoPlayerState extends State { bool looping = false; bool _overlay = true; bool fullScreen = false; + bool allowFullScreen = false; + bool allowPlaybackSpeedChanging = false; int position; - _VimeoPlayerState(this._id, this.autoPlay, this.looping, this.position); + _VimeoPlayerState( + this._id, + this.autoPlay, + this.looping, + this.position, + this.allowFullScreen, + this.allowPlaybackSpeedChanging, + ); //Custom controller - VideoPlayerController _controller; + VideoPlayerController _videoPlayerController; + ChewieController _chewieController; + Future initFuture; //Quality Class @@ -64,6 +86,8 @@ class _VimeoPlayerState extends State { @override void initState() { + fullScreen = allowFullScreen; + //Create class _quality = QualityLinks(_id); @@ -71,21 +95,37 @@ class _VimeoPlayerState extends State { _quality.getQualitiesSync().then((value) { _qualityValues = value; _qualityValue = value[value.lastKey()]; - _controller = VideoPlayerController.network(_qualityValue); - _controller.setLooping(looping); - if (autoPlay) _controller.play(); - initFuture = _controller.initialize(); + _videoPlayerController = VideoPlayerController.network(_qualityValue); + initFuture = _videoPlayerController.initialize().then((value) { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + // Prepare the video to be played and display the first frame + autoInitialize: true, + allowFullScreen: fullScreen, + deviceOrientationsOnEnterFullScreen: [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight], + systemOverlaysOnEnterFullScreen: [SystemUiOverlay.bottom], + deviceOrientationsAfterFullScreen: [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp], + systemOverlaysAfterFullScreen: [SystemUiOverlay.top, SystemUiOverlay.bottom], + aspectRatio: _videoPlayerController.value.aspectRatio, + looping: looping, + autoPlay: autoPlay, + allowPlaybackSpeedChanging: allowPlaybackSpeedChanging, + // Errors can occur for example when trying to play a videos + // from a non-existent URL + errorBuilder: (context, errorMessage) { + return Center(child: Text(errorMessage, style: TextStyle(color: Colors.white))); + }, + ); + }); //Обновление состояние приложения и перерисовка setState(() { - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); }); }); //На странице видео преимущество за портретной ориентацией - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); super.initState(); @@ -105,50 +145,40 @@ class _VimeoPlayerState extends State { if (snapshot.connectionState == ConnectionState.done) { //Управление шириной и высотой видео double delta = MediaQuery.of(context).size.width - - MediaQuery.of(context).size.height * - _controller.value.aspectRatio; + MediaQuery.of(context).size.height * _videoPlayerController.value.aspectRatio; //Рассчет ширины и высоты видео плеера относительно сторон // и ориентации устройства - if (MediaQuery.of(context).orientation == - Orientation.portrait || - delta < 0) { - videoHeight = MediaQuery.of(context).size.width / - _controller.value.aspectRatio; + if (MediaQuery.of(context).orientation == Orientation.portrait || delta < 0) { + videoHeight = MediaQuery.of(context).size.width / _videoPlayerController.value.aspectRatio; videoWidth = MediaQuery.of(context).size.width; videoMargin = 0; } else { videoHeight = MediaQuery.of(context).size.height; - videoWidth = videoHeight * _controller.value.aspectRatio; - videoMargin = - (MediaQuery.of(context).size.width - videoWidth) / 2; + videoWidth = videoHeight * _videoPlayerController.value.aspectRatio; + videoMargin = (MediaQuery.of(context).size.width - videoWidth) / 2; } //Начинаем с того же места, где и остановились при смене качества - if (_seek && _controller.value.duration.inSeconds > 2) { - _controller.seekTo(Duration(seconds: position)); + if (_seek && _videoPlayerController.value.duration.inSeconds > 2) { + _videoPlayerController.seekTo(Duration(seconds: position)); _seek = false; } - //Отрисовка элементов плеера - return Stack( - children: [ - Container( - height: videoHeight, - width: videoWidth, - margin: EdgeInsets.only(left: videoMargin), - child: VideoPlayer(_controller), - ), - _videoOverlay(), - ], - ); + //Prevent exception if it failes when initialising the vimeo video player + if (_chewieController != null) { + return Container( + margin: EdgeInsets.only(left: videoMargin), + child: Chewie(controller: _chewieController), + ); + } + return Container(); } else { return Center( heightFactor: 6, child: CircularProgressIndicator( strokeWidth: 4, - valueColor: - AlwaysStoppedAnimation(Color(0xFF22A3D2)), + valueColor: AlwaysStoppedAnimation(Color(0xFF22A3D2)), )); } }), @@ -176,11 +206,10 @@ class _VimeoPlayerState extends State { child: Container( width: doubleTapLWidth / 2 - 30, height: doubleTapLHeight - 46, - margin: EdgeInsets.fromLTRB( - 0, 10, doubleTapLWidth / 2 + 30, doubleTapLMargin + 20), + margin: EdgeInsets.fromLTRB(0, 10, doubleTapLWidth / 2 + 30, doubleTapLMargin + 20), decoration: BoxDecoration( - //color: Colors.red, - ), + //color: Colors.red, + ), ), // Изменение размера блоков дабл тапа. Нужно для открытия кнопок @@ -203,8 +232,7 @@ class _VimeoPlayerState extends State { }, onDoubleTap: () { setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds - 10)); + _videoPlayerController.seekTo(Duration(seconds: _videoPlayerController.value.position.inSeconds - 10)); }); }), GestureDetector( @@ -212,11 +240,10 @@ class _VimeoPlayerState extends State { //======= Перемотка вперед =======// width: doubleTapRWidth / 2 - 45, height: doubleTapRHeight - 60, - margin: EdgeInsets.fromLTRB(doubleTapRWidth / 2 + 45, - doubleTapRMargin, 0, doubleTapRMargin + 20), + margin: EdgeInsets.fromLTRB(doubleTapRWidth / 2 + 45, doubleTapRMargin, 0, doubleTapRMargin + 20), decoration: BoxDecoration( - //color: Colors.red, - ), + //color: Colors.red, + ), ), // Изменение размера блоков дабл тапа. Нужно для открытия кнопок // "Во весь экран" и "Качество" при включенном overlay @@ -238,215 +265,18 @@ class _VimeoPlayerState extends State { }, onDoubleTap: () { setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds + 10)); + _videoPlayerController.seekTo(Duration(seconds: _videoPlayerController.value.position.inSeconds + 10)); }); }), ], )); } - //================================ Quality ================================// - void _settingModalBottomSheet(context) { - showModalBottomSheet( - context: context, - builder: (BuildContext bc) { - //Формирования списка качества - final children = []; - _qualityValues.forEach((elem, value) => (children.add(new ListTile( - title: new Text(" ${elem.toString()} fps"), - onTap: () => { - //Обновление состояние приложения и перерисовка - setState(() { - _controller.pause(); - _qualityValue = value; - _controller = - VideoPlayerController.network(_qualityValue); - _controller.setLooping(true); - _seek = true; - initFuture = _controller.initialize(); - _controller.play(); - }), - })))); - //Вывод элементов качество списком - return Container( - child: Wrap( - children: children, - ), - ); - }); - } - - //================================ OVERLAY ================================// - Widget _videoOverlay() { - return _overlay - ? Stack( - children: [ - GestureDetector( - child: Center( - child: Container( - width: videoWidth, - height: videoHeight, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerRight, - end: Alignment.centerLeft, - colors: [ - const Color(0x662F2C47), - const Color(0x662F2C47) - ], - ), - ), - ), - ), - ), - Center( - child: IconButton( - padding: EdgeInsets.only( - top: videoHeight / 2 - 30, - bottom: videoHeight / 2 - 30), - icon: _controller.value.isPlaying - ? Icon(Icons.pause, size: 60.0) - : Icon(Icons.play_arrow, size: 60.0), - onPressed: () { - setState(() { - _controller.value.isPlaying - ? _controller.pause() - : _controller.play(); - }); - }), - ), - Container( - margin: EdgeInsets.only( - top: videoHeight - 70, left: videoWidth + videoMargin - 50), - child: IconButton( - alignment: AlignmentDirectional.center, - icon: Icon(Icons.fullscreen, size: 30.0), - onPressed: () async { - setState(() { - _controller.pause(); - }); - //Создание новой страницы с плеером во весь экран, - // предача данных в плеер и возвращение позиции при - // возвращении обратно. Пока что мы не вернулись из - // фуллскрина - программа в ожидании - position = await Navigator.push( - context, - PageRouteBuilder( - opaque: false, - pageBuilder: (BuildContext context, _, __) => - FullscreenPlayer( - id: _id, - autoPlay: true, - controller: _controller, - position: - _controller.value.position.inSeconds, - initFuture: initFuture, - qualityValue: _qualityValue), - transitionsBuilder: (___, - Animation animation, - ____, - Widget child) { - return FadeTransition( - opacity: animation, - child: ScaleTransition( - scale: animation, child: child), - ); - })); - setState(() { - _controller.play(); - _seek = true; - }); - }), - ), - Container( - margin: EdgeInsets.only(left: videoWidth + videoMargin - 48), - child: IconButton( - icon: Icon(Icons.settings, size: 26.0), - onPressed: () { - position = _controller.value.position.inSeconds; - _seek = true; - _settingModalBottomSheet(context); - setState(() {}); - }), - ), - Container( - //===== Ползунок =====// - margin: EdgeInsets.only( - top: videoHeight - 26, left: videoMargin), //CHECK IT - child: _videoOverlaySlider(), - ) - ], - ) - : Center( - child: Container( - height: 5, - width: videoWidth, - margin: EdgeInsets.only(top: videoHeight - 5), - child: VideoProgressIndicator( - _controller, - allowScrubbing: true, - colors: VideoProgressColors( - playedColor: Color(0xFF22A3D2), - backgroundColor: Color(0x5515162B), - bufferedColor: Color(0x5583D8F7), - ), - padding: EdgeInsets.only(top: 2), - ), - ), - ); - } - - //=================== ПОЛЗУНОК ===================// - Widget _videoOverlaySlider() { - return ValueListenableBuilder( - valueListenable: _controller, - builder: (context, VideoPlayerValue value, child) { - if (!value.hasError && value.initialized) { - return Row( - children: [ - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.position.inMinutes.toString() + - ':' + - (value.position.inSeconds - value.position.inMinutes * 60) - .toString()), - ), - Container( - height: 20, - width: videoWidth - 92, - child: VideoProgressIndicator( - _controller, - allowScrubbing: true, - colors: VideoProgressColors( - playedColor: Color(0xFF22A3D2), - backgroundColor: Color(0x5515162B), - bufferedColor: Color(0x5583D8F7), - ), - padding: EdgeInsets.only(top: 8.0, bottom: 8.0), - ), - ), - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.duration.inMinutes.toString() + - ':' + - (value.duration.inSeconds - value.duration.inMinutes * 60) - .toString()), - ), - ], - ); - } else { - return Container(); - } - }, - ); - } - @override void dispose() { - _controller.dispose(); + _chewieController.dispose(); + _videoPlayerController.dispose(); + initFuture = null; super.dispose(); } } diff --git a/example/pubspec.lock b/example/pubspec.lock index ce010d6..aa48073 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,69 +1,76 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: + async: dependency: transitive description: - name: archive + name: async url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" - args: + version: "2.5.0-nullsafety.1" + boolean_selector: dependency: transitive description: - name: args + name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" - async: + version: "2.1.0-nullsafety.1" + characters: dependency: transitive description: - name: async + name: characters url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" - boolean_selector: + version: "1.1.0-nullsafety.3" + charcode: dependency: transitive description: - name: boolean_selector + name: charcode url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" - charcode: - dependency: transitive + version: "1.2.0-nullsafety.1" + chewie: + dependency: "direct main" description: - name: charcode + name: chewie url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" - collection: + version: "0.12.2" + clock: dependency: transitive description: - name: collection + name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" - convert: + version: "1.1.0-nullsafety.1" + collection: dependency: transitive description: - name: convert + name: collection url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" - crypto: + version: "1.15.0-nullsafety.3" + csslib: dependency: transitive description: - name: crypto + name: csslib url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "0.16.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0-nullsafety.1" flutter: dependency: "direct main" description: flutter @@ -79,6 +86,13 @@ packages: description: flutter source: sdk version: "0.0.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+4" http: dependency: transitive description: @@ -93,34 +107,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" - image: + import_js_library: + dependency: transitive + description: + name: import_js_library + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + js: dependency: transitive description: - name: image + name: js url: "https://pub.dartlang.org" source: hosted - version: "2.1.12" + version: "0.6.2" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10-nullsafety.1" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.8.0-nullsafety.1" pedantic: dependency: transitive description: @@ -128,20 +149,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.0" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" sky_engine: dependency: transitive description: flutter @@ -153,63 +160,63 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety.2" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0-nullsafety.1" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.1" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.2.19-nullsafety.2" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.3" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.3" video_player: dependency: transitive description: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "0.11.1+2" + version: "1.0.1" video_player_platform_interface: dependency: transitive description: @@ -230,14 +237,28 @@ packages: name: vimeoplayer url: "https://pub.dartlang.org" source: hosted - version: "0.1.7" - xml: + version: "0.0.2" + wakelock: + dependency: transitive + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1+1" + wakelock_platform_interface: + dependency: transitive + description: + name: wakelock_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" + wakelock_web: dependency: transitive description: - name: xml + name: wakelock_web url: "https://pub.dartlang.org" source: hosted - version: "3.6.1" + version: "0.1.0+3" sdks: - dart: ">=2.8.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + dart: ">=2.10.0-110 <2.11.0" + flutter: ">=1.22.0 <2.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 92340a1..0ce2924 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -24,11 +24,12 @@ dependencies: flutter: sdk: flutter vimeoplayer: ^0.0.1 + chewie: ^0.12.2 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 + cupertino_icons: ^1.0.0 dev_dependencies: flutter_test: diff --git a/lib/src/fullscreen_player.dart b/lib/src/fullscreen_player.dart deleted file mode 100644 index b674f18..0000000 --- a/lib/src/fullscreen_player.dart +++ /dev/null @@ -1,432 +0,0 @@ -library vimeoplayer; - -import 'package:flutter/material.dart'; -import 'package:video_player/video_player.dart'; -import 'package:flutter/services.dart'; -import 'quality_links.dart'; -import 'dart:async'; - -//Класс видео плеера во весь экран -class FullscreenPlayer extends StatefulWidget { - final String id; - final bool autoPlay; - final bool looping; - final VideoPlayerController controller; - final position; - final Future initFuture; - final String qualityValue; - - FullscreenPlayer({ - @required this.id, - this.autoPlay, - this.looping, - this.controller, - this.position, - this.initFuture, - this.qualityValue, - Key key, - }) : super(key: key); - - @override - _FullscreenPlayerState createState() => _FullscreenPlayerState( - id, autoPlay, looping, controller, position, initFuture, qualityValue); -} - -class _FullscreenPlayerState extends State { - String _id; - bool autoPlay = false; - bool looping = false; - bool _overlay = true; - bool fullScreen = true; - - VideoPlayerController controller; - VideoPlayerController _controller; - - int position; - - Future initFuture; - var qualityValue; - - _FullscreenPlayerState(this._id, this.autoPlay, this.looping, this.controller, - this.position, this.initFuture, this.qualityValue); - - // Quality Class - QualityLinks _quality; - Map _qualityValues; - - //Переменная перемотки - bool _seek = true; - - //Переменные видео - double videoHeight; - double videoWidth; - double videoMargin; - - //Переменные под зоны дабл-тапа - double doubleTapRMarginFS = 36; - double doubleTapRWidthFS = 700; - double doubleTapRHeightFS = 300; - double doubleTapLMarginFS = 10; - double doubleTapLWidthFS = 700; - double doubleTapLHeightFS = 400; - - @override - void initState() { - //Инициализация контроллеров видео при получении данных из Vimeo - _controller = controller; - if (autoPlay) _controller.play(); - - // Подгрузка списка качеств видео - _quality = QualityLinks(_id); //Create class - _quality.getQualitiesSync().then((value) { - _qualityValues = value; - }); - - setState(() { - SystemChrome.setPreferredOrientations( - [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); - SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]); - }); - - super.initState(); - } - - //Ослеживаем пользовательского нажатие назад и переводим - // на экран с плеером не в режиме фуллскрин, возвращаем ориентацию - Future _onWillPop() { - setState(() { - _controller.pause(); - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); - SystemChrome.setEnabledSystemUIOverlays( - [SystemUiOverlay.top, SystemUiOverlay.bottom]); - }); - Navigator.pop(context, _controller.value.position.inSeconds); - return Future.value(true); - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: _onWillPop, - child: Scaffold( - body: Center( - child: Stack( - alignment: AlignmentDirectional.center, - children: [ - GestureDetector( - child: FutureBuilder( - future: initFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - //Управление шириной и высотой видео - double delta = MediaQuery.of(context).size.width - - MediaQuery.of(context).size.height * - _controller.value.aspectRatio; - if (MediaQuery.of(context).orientation == - Orientation.portrait || - delta < 0) { - videoHeight = MediaQuery.of(context).size.width / - _controller.value.aspectRatio; - videoWidth = MediaQuery.of(context).size.width; - videoMargin = 0; - } else { - videoHeight = MediaQuery.of(context).size.height; - videoWidth = - videoHeight * _controller.value.aspectRatio; - videoMargin = - (MediaQuery.of(context).size.width - videoWidth) / - 2; - } - //Переменные дабл тапа, зависимые от размеров видео - doubleTapRWidthFS = videoWidth; - doubleTapRHeightFS = videoHeight - 36; - doubleTapLWidthFS = videoWidth; - doubleTapLHeightFS = videoHeight; - - //Сразу при входе в режим фуллскрин перематываем - // на нужное место - if (_seek && fullScreen) { - _controller.seekTo(Duration(seconds: position)); - _seek = false; - } - - //Переходи на нужное место при смене качества - if (_seek && _controller.value.duration.inSeconds > 2) { - _controller.seekTo(Duration(seconds: position)); - _seek = false; - } - SystemChrome.setEnabledSystemUIOverlays( - [SystemUiOverlay.bottom]); - - //Отрисовка элементов плеера - return Stack( - children: [ - Container( - height: videoHeight, - width: videoWidth, - margin: EdgeInsets.only(left: videoMargin), - child: VideoPlayer(_controller), - ), - _videoOverlay(), - ], - ); - } else { - return Center( - heightFactor: 6, - child: CircularProgressIndicator( - strokeWidth: 4, - valueColor: AlwaysStoppedAnimation( - Color(0xFF22A3D2)), - )); - } - }), - //Редактируем размер области дабл тапа при показе оверлея. - // Сделано для открытия кнопок "Во весь экран" и "Качество" - onTap: () { - setState(() { - _overlay = !_overlay; - if (_overlay) { - doubleTapRHeightFS = videoHeight - 36; - doubleTapLHeightFS = videoHeight - 10; - doubleTapRMarginFS = 36; - doubleTapLMarginFS = 10; - } else if (!_overlay) { - doubleTapRHeightFS = videoHeight + 36; - doubleTapLHeightFS = videoHeight; - doubleTapRMarginFS = 0; - doubleTapLMarginFS = 0; - } - }); - }, - ), - GestureDetector( - child: Container( - width: doubleTapLWidthFS / 2 - 30, - height: doubleTapLHeightFS - 44, - margin: - EdgeInsets.fromLTRB(0, 0, doubleTapLWidthFS / 2 + 30, 40), - decoration: BoxDecoration( - //color: Colors.red, - ), - ), - //Редактируем размер области дабл тапа при показе оверлея. - // Сделано для открытия кнопок "Во весь экран" и "Качество" - onTap: () { - setState(() { - _overlay = !_overlay; - if (_overlay) { - doubleTapRHeightFS = videoHeight - 36; - doubleTapLHeightFS = videoHeight - 10; - doubleTapRMarginFS = 36; - doubleTapLMarginFS = 10; - } else if (!_overlay) { - doubleTapRHeightFS = videoHeight + 36; - doubleTapLHeightFS = videoHeight; - doubleTapRMarginFS = 0; - doubleTapLMarginFS = 0; - } - }); - }, - onDoubleTap: () { - setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds - 10)); - }); - }), - GestureDetector( - child: Container( - width: doubleTapRWidthFS / 2 - 45, - height: doubleTapRHeightFS - 80, - margin: EdgeInsets.fromLTRB(doubleTapRWidthFS / 2 + 45, 0, 0, - doubleTapLMarginFS + 20), - decoration: BoxDecoration( - //color: Colors.red, - ), - ), - //Редактируем размер области дабл тапа при показе оверлея. - // Сделано для открытия кнопок "Во весь экран" и "Качество" - onTap: () { - setState(() { - _overlay = !_overlay; - if (_overlay) { - doubleTapRHeightFS = videoHeight - 36; - doubleTapLHeightFS = videoHeight - 10; - doubleTapRMarginFS = 36; - doubleTapLMarginFS = 10; - } else if (!_overlay) { - doubleTapRHeightFS = videoHeight + 36; - doubleTapLHeightFS = videoHeight; - doubleTapRMarginFS = 0; - doubleTapLMarginFS = 0; - } - }); - }, - onDoubleTap: () { - setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds + 10)); - }); - }), - ], - )))); - } - - //================================ Quality ================================// - void _settingModalBottomSheet(context) { - showModalBottomSheet( - context: context, - builder: (BuildContext bc) { - final children = []; - _qualityValues.forEach((elem, value) => (children.add(new ListTile( - title: new Text(" ${elem.toString()} fps"), - onTap: () => { - //Обновление состояние приложения и перерисовка - setState(() { - _controller.pause(); - _controller = VideoPlayerController.network(value); - _controller.setLooping(true); - _seek = true; - initFuture = _controller.initialize(); - _controller.play(); - }), - })))); - - return Container( - height: videoHeight, - child: ListView( - children: children, - ), - ); - }); - } - - //================================ OVERLAY ================================// - Widget _videoOverlay() { - return _overlay - ? Stack( - children: [ - GestureDetector( - child: Center( - child: Container( - width: videoWidth, - height: videoHeight, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerRight, - end: Alignment.centerLeft, - colors: [ - const Color(0x662F2C47), - const Color(0x662F2C47) - ], - ), - ), - ), - ), - ), - Center( - child: IconButton( - padding: EdgeInsets.only( - top: videoHeight / 2 - 50, - bottom: videoHeight / 2 - 30, - ), - icon: _controller.value.isPlaying - ? Icon(Icons.pause, size: 60.0) - : Icon(Icons.play_arrow, size: 60.0), - onPressed: () { - setState(() { - _controller.value.isPlaying - ? _controller.pause() - : _controller.play(); - }); - }), - ), - Container( - margin: EdgeInsets.only( - top: videoHeight - 80, left: videoWidth + videoMargin - 50), - child: IconButton( - alignment: AlignmentDirectional.center, - icon: Icon(Icons.fullscreen, size: 30.0), - onPressed: () { - setState(() { - _controller.pause(); - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitDown, - DeviceOrientation.portraitUp - ]); - SystemChrome.setEnabledSystemUIOverlays( - [SystemUiOverlay.top, SystemUiOverlay.bottom]); - }); - Navigator.pop( - context, _controller.value.position.inSeconds); - }), - ), - Container( - margin: EdgeInsets.only(left: videoWidth + videoMargin - 48), - child: IconButton( - icon: Icon(Icons.settings, size: 26.0), - onPressed: () { - position = _controller.value.position.inSeconds; - _seek = true; - _settingModalBottomSheet(context); - setState(() {}); - }), - ), - Container( - //===== Ползунок =====// - margin: EdgeInsets.only( - top: videoHeight - 40, left: videoMargin), //CHECK IT - child: _videoOverlaySlider(), - ) - ], - ) - : Center(); - } - - //=================== ПОЛЗУНОК ===================// - Widget _videoOverlaySlider() { - return ValueListenableBuilder( - valueListenable: _controller, - builder: (context, VideoPlayerValue value, child) { - if (!value.hasError && value.initialized) { - return Row( - children: [ - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.position.inMinutes.toString() + - ':' + - (value.position.inSeconds - value.position.inMinutes * 60) - .toString()), - ), - Container( - height: 20, - width: videoWidth - 92, - child: VideoProgressIndicator( - _controller, - allowScrubbing: true, - colors: VideoProgressColors( - playedColor: Color(0xFF22A3D2), - backgroundColor: Color(0x5515162B), - bufferedColor: Color(0x5583D8F7), - ), - padding: EdgeInsets.only(top: 8.0, bottom: 8.0), - ), - ), - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.duration.inMinutes.toString() + - ':' + - (value.duration.inSeconds - value.duration.inMinutes * 60) - .toString()), - ), - ], - ); - } else { - return Container(); - } - }, - ); - } -} diff --git a/lib/vimeoplayer.dart b/lib/vimeoplayer.dart index 6c6d506..0b664e4 100644 --- a/lib/vimeoplayer.dart +++ b/lib/vimeoplayer.dart @@ -1,30 +1,41 @@ library vimeoplayer; +import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:flutter/services.dart'; import 'src/quality_links.dart'; import 'dart:async'; -import 'src/fullscreen_player.dart'; -//Класс видео плеера +//Video player class class VimeoPlayer extends StatefulWidget { final String id; final bool autoPlay; final bool looping; final int position; + final bool allowFullScreen; + final bool allowPlaybackSpeedChanging; VimeoPlayer({ @required this.id, this.autoPlay, this.looping, this.position, + @required this.allowFullScreen, + this.allowPlaybackSpeedChanging = false, Key key, - }) : super(key: key); + }) : assert(id != null && allowFullScreen != null), + super(key: key); @override - _VimeoPlayerState createState() => - _VimeoPlayerState(id, autoPlay, looping, position); + _VimeoPlayerState createState() => _VimeoPlayerState( + id, + autoPlay, + looping, + position, + allowFullScreen, + allowPlaybackSpeedChanging, + ); } class _VimeoPlayerState extends State { @@ -33,28 +44,38 @@ class _VimeoPlayerState extends State { bool looping = false; bool _overlay = true; bool fullScreen = false; + bool allowFullScreen = false; + bool allowPlaybackSpeedChanging = false; int position; - _VimeoPlayerState(this._id, this.autoPlay, this.looping, this.position); + _VimeoPlayerState( + this._id, + this.autoPlay, + this.looping, + this.position, + this.allowFullScreen, + this.allowPlaybackSpeedChanging, + ); //Custom controller VideoPlayerController _controller; + ChewieController _chewieController; + Future initFuture; //Quality Class QualityLinks _quality; - Map _qualityValues; var _qualityValue; - //Переменная перемотки + //Variable rewind bool _seek = false; - //Переменные видео + //Video variables double videoHeight; double videoWidth; double videoMargin; - //Переменные под зоны дабл-тапа + //Variables for double-tap zones double doubleTapRMargin = 36; double doubleTapRWidth = 400; double doubleTapRHeight = 160; @@ -64,34 +85,53 @@ class _VimeoPlayerState extends State { @override void initState() { + fullScreen = allowFullScreen; + //Create class _quality = QualityLinks(_id); - //Инициализация контроллеров видео при получении данных из Vimeo + //Initializing video controllers when receiving data from Vimeo _quality.getQualitiesSync().then((value) { - _qualityValues = value; _qualityValue = value[value.lastKey()]; _controller = VideoPlayerController.network(_qualityValue); _controller.setLooping(looping); if (autoPlay) _controller.play(); - initFuture = _controller.initialize(); + initFuture = _controller.initialize().then((value) { + _chewieController = ChewieController( + videoPlayerController: _controller, + // Prepare the video to be played and display the first frame + autoInitialize: true, + allowFullScreen: fullScreen, + deviceOrientationsOnEnterFullScreen: [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight], + systemOverlaysOnEnterFullScreen: [SystemUiOverlay.bottom], + deviceOrientationsAfterFullScreen: [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp], + systemOverlaysAfterFullScreen: [SystemUiOverlay.top, SystemUiOverlay.bottom], + aspectRatio: _controller.value.aspectRatio, + looping: looping, + autoPlay: autoPlay, + allowPlaybackSpeedChanging: allowPlaybackSpeedChanging, + // Errors can occur for example when trying to play a video + // from a non-existent URL + errorBuilder: (context, errorMessage) { + return Center(child: Text(errorMessage, style: TextStyle(color: Colors.white))); + }, + ); + }); - //Обновление состояние приложения и перерисовка + //Update orientation and rebuilding page setState(() { - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); }); }); - //На странице видео преимущество за портретной ориентацией - SystemChrome.setPreferredOrientations( - [DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); + //The video page takes precedence over portrait orientation + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]); SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); super.initState(); } - //Отрисовываем элементы плеера + //Build player element @override Widget build(BuildContext context) { return Center( @@ -103,58 +143,48 @@ class _VimeoPlayerState extends State { future: initFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - //Управление шириной и высотой видео + //Controlling width and height double delta = MediaQuery.of(context).size.width - - MediaQuery.of(context).size.height * - _controller.value.aspectRatio; + MediaQuery.of(context).size.height * _controller.value.aspectRatio; - //Рассчет ширины и высоты видео плеера относительно сторон - // и ориентации устройства - if (MediaQuery.of(context).orientation == - Orientation.portrait || - delta < 0) { - videoHeight = MediaQuery.of(context).size.width / - _controller.value.aspectRatio; + //Calculating the width and height of the video player relative to the sides + // and orientation of the device + if (MediaQuery.of(context).orientation == Orientation.portrait || delta < 0) { + videoHeight = MediaQuery.of(context).size.width / _controller.value.aspectRatio; videoWidth = MediaQuery.of(context).size.width; videoMargin = 0; } else { videoHeight = MediaQuery.of(context).size.height; videoWidth = videoHeight * _controller.value.aspectRatio; - videoMargin = - (MediaQuery.of(context).size.width - videoWidth) / 2; + videoMargin = (MediaQuery.of(context).size.width - videoWidth) / 2; } - //Начинаем с того же места, где и остановились при смене качества + //We start from the same place where we left off when changing quality if (_seek && _controller.value.duration.inSeconds > 2) { _controller.seekTo(Duration(seconds: position)); _seek = false; } - //Отрисовка элементов плеера - return Stack( - children: [ - Container( - height: videoHeight, - width: videoWidth, - margin: EdgeInsets.only(left: videoMargin), - child: VideoPlayer(_controller), - ), - _videoOverlay(), - ], - ); + //Prevent exception if it failes when initialising the vimeo video player + if(_chewieController != null) { + return Container( + margin: EdgeInsets.only(left: videoMargin), + child: Chewie(controller: _chewieController), + ); + } + return Container(); } else { return Center( heightFactor: 6, child: CircularProgressIndicator( strokeWidth: 4, - valueColor: - AlwaysStoppedAnimation(Color(0xFF22A3D2)), + valueColor: AlwaysStoppedAnimation(Color(0xFF22A3D2)), )); } }), onTap: () { - //Редактируем размер области дабл тапа при показе оверлея. - // Сделано для открытия кнопок "Во весь экран" и "Качество" + //Editing the size of the double tap area when showing the overlay. + // Made to open the "Full Screen" and "Quality" buttons setState(() { _overlay = !_overlay; if (_overlay) { @@ -172,19 +202,18 @@ class _VimeoPlayerState extends State { }, ), GestureDetector( - //======= Перемотка назад =======// + //======= Rewind =======// child: Container( width: doubleTapLWidth / 2 - 30, height: doubleTapLHeight - 46, - margin: EdgeInsets.fromLTRB( - 0, 10, doubleTapLWidth / 2 + 30, doubleTapLMargin + 20), + margin: EdgeInsets.fromLTRB(0, 10, doubleTapLWidth / 2 + 30, doubleTapLMargin + 20), decoration: BoxDecoration( - //color: Colors.red, - ), + //color: Colors.red, + ), ), - // Изменение размера блоков дабл тапа. Нужно для открытия кнопок - // "Во весь экран" и "Качество" при включенном overlay + // Resize double tap blocks. Needed to open the + // "Full Screen" and "Quality" buttons when overlay is enabled onTap: () { setState(() { _overlay = !_overlay; @@ -203,23 +232,21 @@ class _VimeoPlayerState extends State { }, onDoubleTap: () { setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds - 10)); + _controller.seekTo(Duration(seconds: _controller.value.position.inSeconds - 10)); }); }), GestureDetector( child: Container( - //======= Перемотка вперед =======// + //======= Flash forward =======// width: doubleTapRWidth / 2 - 45, height: doubleTapRHeight - 60, - margin: EdgeInsets.fromLTRB(doubleTapRWidth / 2 + 45, - doubleTapRMargin, 0, doubleTapRMargin + 20), + margin: EdgeInsets.fromLTRB(doubleTapRWidth / 2 + 45, doubleTapRMargin, 0, doubleTapRMargin + 20), decoration: BoxDecoration( - //color: Colors.red, - ), + //color: Colors.red, + ), ), - // Изменение размера блоков дабл тапа. Нужно для открытия кнопок - // "Во весь экран" и "Качество" при включенном overlay + // Resize double tap blocks. Needed to open the + // "Full Screen" and "Quality" buttons when overlay is enabled onTap: () { setState(() { _overlay = !_overlay; @@ -238,215 +265,17 @@ class _VimeoPlayerState extends State { }, onDoubleTap: () { setState(() { - _controller.seekTo(Duration( - seconds: _controller.value.position.inSeconds + 10)); + _controller.seekTo(Duration(seconds: _controller.value.position.inSeconds + 10)); }); }), ], )); } - //================================ Quality ================================// - void _settingModalBottomSheet(context) { - showModalBottomSheet( - context: context, - builder: (BuildContext bc) { - //Формирования списка качества - final children = []; - _qualityValues.forEach((elem, value) => (children.add(new ListTile( - title: new Text(" ${elem.toString()} fps"), - onTap: () => { - //Обновление состояние приложения и перерисовка - setState(() { - _controller.pause(); - _qualityValue = value; - _controller = - VideoPlayerController.network(_qualityValue); - _controller.setLooping(true); - _seek = true; - initFuture = _controller.initialize(); - _controller.play(); - }), - })))); - //Вывод элементов качество списком - return Container( - child: Wrap( - children: children, - ), - ); - }); - } - - //================================ OVERLAY ================================// - Widget _videoOverlay() { - return _overlay - ? Stack( - children: [ - GestureDetector( - child: Center( - child: Container( - width: videoWidth, - height: videoHeight, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerRight, - end: Alignment.centerLeft, - colors: [ - const Color(0x662F2C47), - const Color(0x662F2C47) - ], - ), - ), - ), - ), - ), - Center( - child: IconButton( - padding: EdgeInsets.only( - top: videoHeight / 2 - 30, - bottom: videoHeight / 2 - 30), - icon: _controller.value.isPlaying - ? Icon(Icons.pause, size: 60.0) - : Icon(Icons.play_arrow, size: 60.0), - onPressed: () { - setState(() { - _controller.value.isPlaying - ? _controller.pause() - : _controller.play(); - }); - }), - ), - Container( - margin: EdgeInsets.only( - top: videoHeight - 70, left: videoWidth + videoMargin - 50), - child: IconButton( - alignment: AlignmentDirectional.center, - icon: Icon(Icons.fullscreen, size: 30.0), - onPressed: () async { - setState(() { - _controller.pause(); - }); - //Создание новой страницы с плеером во весь экран, - // предача данных в плеер и возвращение позиции при - // возвращении обратно. Пока что мы не вернулись из - // фуллскрина - программа в ожидании - position = await Navigator.push( - context, - PageRouteBuilder( - opaque: false, - pageBuilder: (BuildContext context, _, __) => - FullscreenPlayer( - id: _id, - autoPlay: true, - controller: _controller, - position: - _controller.value.position.inSeconds, - initFuture: initFuture, - qualityValue: _qualityValue), - transitionsBuilder: (___, - Animation animation, - ____, - Widget child) { - return FadeTransition( - opacity: animation, - child: ScaleTransition( - scale: animation, child: child), - ); - })); - setState(() { - _controller.play(); - _seek = true; - }); - }), - ), - Container( - margin: EdgeInsets.only(left: videoWidth + videoMargin - 48), - child: IconButton( - icon: Icon(Icons.settings, size: 26.0), - onPressed: () { - position = _controller.value.position.inSeconds; - _seek = true; - _settingModalBottomSheet(context); - setState(() {}); - }), - ), - Container( - //===== Ползунок =====// - margin: EdgeInsets.only( - top: videoHeight - 26, left: videoMargin), //CHECK IT - child: _videoOverlaySlider(), - ) - ], - ) - : Center( - child: Container( - height: 5, - width: videoWidth, - margin: EdgeInsets.only(top: videoHeight - 5), - child: VideoProgressIndicator( - _controller, - allowScrubbing: true, - colors: VideoProgressColors( - playedColor: Color(0xFF22A3D2), - backgroundColor: Color(0x5515162B), - bufferedColor: Color(0x5583D8F7), - ), - padding: EdgeInsets.only(top: 2), - ), - ), - ); - } - - //=================== ПОЛЗУНОК ===================// - Widget _videoOverlaySlider() { - return ValueListenableBuilder( - valueListenable: _controller, - builder: (context, VideoPlayerValue value, child) { - if (!value.hasError && value.initialized) { - return Row( - children: [ - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.position.inMinutes.toString() + - ':' + - (value.position.inSeconds - value.position.inMinutes * 60) - .toString()), - ), - Container( - height: 20, - width: videoWidth - 92, - child: VideoProgressIndicator( - _controller, - allowScrubbing: true, - colors: VideoProgressColors( - playedColor: Color(0xFF22A3D2), - backgroundColor: Color(0x5515162B), - bufferedColor: Color(0x5583D8F7), - ), - padding: EdgeInsets.only(top: 8.0, bottom: 8.0), - ), - ), - Container( - width: 46, - alignment: Alignment(0, 0), - child: Text(value.duration.inMinutes.toString() + - ':' + - (value.duration.inSeconds - value.duration.inMinutes * 60) - .toString()), - ), - ], - ); - } else { - return Container(); - } - }, - ); - } - @override void dispose() { _controller.dispose(); + initFuture = null; super.dispose(); } } diff --git a/pubspec.lock b/pubspec.lock index 09a6a4d..03f5977 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,62 +1,76 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: + async: dependency: transitive description: - name: archive + name: async url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" - args: + version: "2.5.0-nullsafety.1" + boolean_selector: dependency: transitive description: - name: args + name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" - async: + version: "2.1.0-nullsafety.1" + characters: dependency: transitive description: - name: async + name: characters url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" - boolean_selector: + version: "1.1.0-nullsafety.3" + charcode: dependency: transitive description: - name: boolean_selector + name: charcode url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" - charcode: + version: "1.2.0-nullsafety.1" + chewie: + dependency: "direct main" + description: + name: chewie + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + clock: dependency: transitive description: - name: charcode + name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.1.0-nullsafety.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" - convert: + version: "1.15.0-nullsafety.3" + csslib: dependency: transitive description: - name: convert + name: csslib url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" - crypto: + version: "0.16.2" + cupertino_icons: dependency: transitive description: - name: crypto + name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "1.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0-nullsafety.1" flutter: dependency: "direct main" description: flutter @@ -72,6 +86,13 @@ packages: description: flutter source: sdk version: "0.0.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+4" http: dependency: "direct main" description: @@ -86,34 +107,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" - image: + import_js_library: + dependency: transitive + description: + name: import_js_library + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + js: dependency: transitive description: - name: image + name: js url: "https://pub.dartlang.org" source: hosted - version: "2.1.12" + version: "0.6.2" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10-nullsafety.1" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.8.0-nullsafety.1" pedantic: dependency: transitive description: @@ -121,20 +149,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.0" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" sky_engine: dependency: transitive description: flutter @@ -146,63 +160,63 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety.2" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0-nullsafety.1" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.1" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.2.19-nullsafety.2" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.3" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.3" video_player: dependency: "direct main" description: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "0.11.1+2" + version: "1.0.1" video_player_platform_interface: dependency: transitive description: @@ -217,13 +231,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.4" - xml: + wakelock: + dependency: transitive + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1+1" + wakelock_platform_interface: + dependency: transitive + description: + name: wakelock_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" + wakelock_web: dependency: transitive description: - name: xml + name: wakelock_web url: "https://pub.dartlang.org" source: hosted - version: "3.6.1" + version: "0.1.0+3" sdks: - dart: ">=2.8.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + dart: ">=2.10.0-110 <2.11.0" + flutter: ">=1.22.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 9922afe..9c4b103 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: sdk: flutter video_player: '>=0.10.0 <2.0.0' http: ^0.12.1 + chewie: ^0.12.2 dev_dependencies: flutter_test: