diff --git a/android/build.gradle b/android/build.gradle index b9c4f8b..9646c3a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -58,6 +58,8 @@ android { implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$exoPlayerVersion" implementation "com.google.android.exoplayer:exoplayer-ui:$exoPlayerVersion" implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerVersion" + + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-common:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" diff --git a/example/android/build.gradle b/example/android/build.gradle index dcb7372..81dd9b6 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -28,6 +28,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 8a0b06d..b2ab7c0 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -70,4 +70,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/example/lib/constants.dart b/example/lib/constants.dart index ec49d02..59f0584 100644 --- a/example/lib/constants.dart +++ b/example/lib/constants.dart @@ -8,8 +8,9 @@ class Constants { static const String networkTestVideoEncryptUrl = "https://github.com/tinusneethling/betterplayer/raw/ClearKeySupport/example/assets/testvideo_encrypt.mp4"; static const String fileExampleSubtitlesUrl = "example_subtitles.srt"; - static const String hlsTestStreamUrl = - "https://mtoczko.github.io/hls-test-streams/test-group/playlist.m3u8"; + static const String hlsTestStreamUrl ='https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8'; + // 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8'; + // "https://mtoczko.github.io/hls-test-streams/test-group/playlist.m3u8"; static const String hlsPlaylistUrl = "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8"; static const Map exampleResolutionsUrls = { diff --git a/example/lib/main.dart b/example/lib/main.dart index a9906dd..3f01a40 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -13,6 +13,7 @@ class MyApp extends StatelessWidget { LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), }, child: MaterialApp( + debugShowCheckedModeBanner: false, title: 'Better player demo', localizationsDelegates: [ GlobalMaterialLocalizations.delegate, diff --git a/example/lib/pages/basic_player_page.dart b/example/lib/pages/basic_player_page.dart index 33f8a7c..4c06acf 100644 --- a/example/lib/pages/basic_player_page.dart +++ b/example/lib/pages/basic_player_page.dart @@ -16,78 +16,71 @@ class _BasicPlayerPageState extends State { void initState() { _betterPlayerDataSource = BetterPlayerDataSource( BetterPlayerDataSourceType.network, - 'https://d357lqen3ahf81.cloudfront.net/transcoded/Af34Dm2h4M5/video.m3u8', + 'https://d357lqen3ahf81.cloudfront.net/transcoded/7EsHghnDzbx/video.m3u8', cacheConfiguration: const BetterPlayerCacheConfiguration(useCache: true), - bufferingConfiguration: const BetterPlayerBufferingConfiguration(maxBufferMs: 3000, minBufferMs: 1000), + bufferingConfiguration: const BetterPlayerBufferingConfiguration(), ); - _controller = BetterPlayerController(BetterPlayerConfiguration(autoPlay: true), + _controller = BetterPlayerController( + BetterPlayerConfiguration( + autoPlay: true, + ), betterPlayerDataSource: _betterPlayerDataSource); + super.initState(); } @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Basic player"), - ), - body: ListView( - children: [ - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - "Basic player created with the simplest factory method. Shows video from URL.", - style: TextStyle(fontSize: 16), + return MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + appBar: AppBar( + title: Text("Basic player"), + ), + body: ListView( + children: [ + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + "Basic player created with the simplest factory method. Shows video from URL.", + style: TextStyle(fontSize: 16), + ), ), - ), - // AspectRatio( - // aspectRatio: 16 / 9, - // child: BetterPlayer.network( - // Constants.forBiggerBlazesUrl, - // ), - // ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - "Next player shows video from file.", - style: TextStyle(fontSize: 16), + BetterPlayer(controller: _controller!), + const SizedBox(height: 8), + // FutureBuilder( + // future: Utils.getFileUrl(Constants.fileTestVideoUrl), + // builder: (BuildContext context, AsyncSnapshot snapshot) { + // if (snapshot.data != null) { + // return BetterPlayer( + // controller: _controller!, + // ); + // } else { + // return const SizedBox(); + // } + // }, + // ), + SizedBox( + height: 20, ), - ), - const SizedBox(height: 8), - FutureBuilder( - future: Utils.getFileUrl(Constants.fileTestVideoUrl), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data != null) { - return BetterPlayer( - controller: _controller!, - ); - } else { - return const SizedBox(); - } - }, - ), - - SizedBox( - height: 20, - ), - - AnimatedContainer( - width: double.infinity, - height: 350, - duration: const Duration(milliseconds: 300), - // color: _controller.value.isControlsVisible ? Colors.black.withAlpha(150) : Colors.transparent, - // color: Colors.black.withAlpha(120) - decoration: BoxDecoration( - gradient: LinearGradient(colors: [ - Color(0xff000000), - Color(0xff000000).withOpacity(0), - Color(0xff000000), - ], begin: Alignment.topCenter, end: Alignment.bottomCenter), + AnimatedContainer( + width: double.infinity, + height: 350, + duration: const Duration(milliseconds: 300), + // color: _controller.value.isControlsVisible ? Colors.black.withAlpha(150) : Colors.transparent, + // color: Colors.black.withAlpha(120) + decoration: BoxDecoration( + gradient: LinearGradient(colors: [ + Color(0xff000000), + Color(0xff000000).withOpacity(0), + Color(0xff000000), + ], begin: Alignment.topCenter, end: Alignment.bottomCenter), + ), + // color: Colors.red, ), - // color: Colors.red, - ), - ], + ], + ), ), ); } diff --git a/example/lib/pages/hls_tracks_page.dart b/example/lib/pages/hls_tracks_page.dart index 757b589..2ddcdfa 100644 --- a/example/lib/pages/hls_tracks_page.dart +++ b/example/lib/pages/hls_tracks_page.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:better_player/better_player.dart'; import 'package:better_player_example/constants.dart'; import 'package:flutter/material.dart'; @@ -24,9 +26,20 @@ class _HlsTracksPageState extends State { ); _betterPlayerController = BetterPlayerController(betterPlayerConfiguration); _betterPlayerController.setupDataSource(dataSource); + super.initState(); } + @override + void dispose() { + // TODO: implement dispose + + log('9048 crossed checkpoints ---> ${_betterPlayerController.getCheckPointsCount()}'); + _betterPlayerController.dispose(); + + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -44,9 +57,11 @@ class _HlsTracksPageState extends State { style: TextStyle(fontSize: 16), ), ), - AspectRatio( - aspectRatio: 16 / 9, - child: BetterPlayer(controller: _betterPlayerController), + Flexible( + child: AspectRatio( + aspectRatio: 16 / 9, + child: BetterPlayer(controller: _betterPlayerController), + ), ), ], ), diff --git a/lib/src/checkPoints/check_points.dart b/lib/src/checkPoints/check_points.dart new file mode 100644 index 0000000..36f727b --- /dev/null +++ b/lib/src/checkPoints/check_points.dart @@ -0,0 +1,21 @@ +class CheckPointData { + final double videoFraction; + final bool status; + + CheckPointData({required this.status, required this.videoFraction}); + + CheckPointData copyWith({ + double? value, + bool? status, + }) { + return CheckPointData( + videoFraction: value ?? this.videoFraction, + status: status ?? this.status, + ); + } + + @override + String toString() { + return 'CheckPointData(value: $videoFraction, status: $status)'; + } +} \ No newline at end of file diff --git a/lib/src/controls/better_player_controls_state.dart b/lib/src/controls/better_player_controls_state.dart index 26c3d2a..c1612de 100644 --- a/lib/src/controls/better_player_controls_state.dart +++ b/lib/src/controls/better_player_controls_state.dart @@ -8,7 +8,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; ///Base class for both material and cupertino controls -abstract class BetterPlayerControlsState extends State { +abstract class BetterPlayerControlsState + extends State { ///Min. time of buffered video to hide loading timer (in milliseconds) static const int _bufferingInterval = 20000; @@ -35,9 +36,12 @@ abstract class BetterPlayerControlsState extends State cancelAndRestartTimer(); final beginning = const Duration().inMilliseconds; final skip = (latestValue!.position - - Duration(milliseconds: betterPlayerControlsConfiguration.backwardSkipTimeInMilliseconds)) + Duration( + milliseconds: betterPlayerControlsConfiguration + .backwardSkipTimeInMilliseconds)) .inMilliseconds; - betterPlayerController!.seekTo(Duration(milliseconds: max(skip, beginning))); + betterPlayerController! + .seekTo(Duration(milliseconds: max(skip, beginning))); } } @@ -46,7 +50,9 @@ abstract class BetterPlayerControlsState extends State cancelAndRestartTimer(); final end = latestValue!.duration!.inMilliseconds; final skip = (latestValue!.position + - Duration(milliseconds: betterPlayerControlsConfiguration.forwardSkipTimeInMilliseconds)) + Duration( + milliseconds: betterPlayerControlsConfiguration + .forwardSkipTimeInMilliseconds)) .inMilliseconds; betterPlayerController!.seekTo(Duration(milliseconds: min(skip, end))); } @@ -60,9 +66,10 @@ abstract class BetterPlayerControlsState extends State // _showModalBottomSheet([_buildMoreOptionsList()]); _showSpeedChooserWidget(); } + void onQualityButtonClicked() { // _showModalBottomSheet([_buildMoreOptionsList()]); - _showQualitiesSelectionWidget(); + _showQualitiesSelectionWidget(); } Widget _buildMoreOptionsList() { @@ -74,31 +81,34 @@ abstract class BetterPlayerControlsState extends State children: [ if (betterPlayerControlsConfiguration.enablePlaybackSpeed) _buildMoreOptionsListRow( - betterPlayerControlsConfiguration.playbackSpeedIcon, translations.overflowMenuPlaybackSpeed, - () { + betterPlayerControlsConfiguration.playbackSpeedIcon, + translations.overflowMenuPlaybackSpeed, () { Navigator.of(context).pop(); _showSpeedChooserWidget(); }), if (betterPlayerControlsConfiguration.enableSubtitles) _buildMoreOptionsListRow( - betterPlayerControlsConfiguration.subtitlesIcon, translations.overflowMenuSubtitles, () { + betterPlayerControlsConfiguration.subtitlesIcon, + translations.overflowMenuSubtitles, () { Navigator.of(context).pop(); _showSubtitlesSelectionWidget(); }), if (betterPlayerControlsConfiguration.enableQualities) _buildMoreOptionsListRow( - betterPlayerControlsConfiguration.qualitiesIcon, translations.overflowMenuQuality, () { + betterPlayerControlsConfiguration.qualitiesIcon, + translations.overflowMenuQuality, () { Navigator.of(context).pop(); _showQualitiesSelectionWidget(); }), if (betterPlayerControlsConfiguration.enableAudioTracks) _buildMoreOptionsListRow( - betterPlayerControlsConfiguration.audioTracksIcon, translations.overflowMenuAudioTracks, - () { + betterPlayerControlsConfiguration.audioTracksIcon, + translations.overflowMenuAudioTracks, () { Navigator.of(context).pop(); _showAudioTracksSelectionWidget(); }), - if (betterPlayerControlsConfiguration.overflowMenuCustomItems.isNotEmpty) + if (betterPlayerControlsConfiguration + .overflowMenuCustomItems.isNotEmpty) ...betterPlayerControlsConfiguration.overflowMenuCustomItems.map( (customItem) => _buildMoreOptionsListRow( customItem.icon, @@ -115,7 +125,8 @@ abstract class BetterPlayerControlsState extends State ); } - Widget _buildMoreOptionsListRow(IconData icon, String name, void Function() onTap) { + Widget _buildMoreOptionsListRow( + IconData icon, String name, void Function() onTap) { return BetterPlayerMaterialClickableWidget( onTap: onTap, child: Padding( @@ -152,7 +163,8 @@ abstract class BetterPlayerControlsState extends State } Widget _buildSpeedRow(double value) { - final bool isSelected = betterPlayerController!.videoPlayerController!.value.speed == value; + final bool isSelected = + betterPlayerController!.videoPlayerController!.value.speed == value; return BetterPlayerMaterialClickableWidget( onTap: () { @@ -168,7 +180,8 @@ abstract class BetterPlayerControlsState extends State visible: isSelected, child: Icon( Icons.check_outlined, - color: betterPlayerControlsConfiguration.overflowModalTextColor, + color: + betterPlayerControlsConfiguration.overflowModalTextColor, )), const SizedBox(width: 16), Text( @@ -209,18 +222,23 @@ abstract class BetterPlayerControlsState extends State } void _showSubtitlesSelectionWidget() { - final subtitles = List.of(betterPlayerController!.betterPlayerSubtitlesSourceList); - final noneSubtitlesElementExists = - subtitles.firstWhereOrNull((source) => source.type == BetterPlayerSubtitlesSourceType.none) != null; + final subtitles = + List.of(betterPlayerController!.betterPlayerSubtitlesSourceList); + final noneSubtitlesElementExists = subtitles.firstWhereOrNull( + (source) => source.type == BetterPlayerSubtitlesSourceType.none) != + null; if (!noneSubtitlesElementExists) { - subtitles.add(BetterPlayerSubtitlesSource(type: BetterPlayerSubtitlesSourceType.none)); + subtitles.add(BetterPlayerSubtitlesSource( + type: BetterPlayerSubtitlesSourceType.none)); } - _showModalBottomSheet(subtitles.map((source) => _buildSubtitlesSourceRow(source)).toList()); + _showModalBottomSheet( + subtitles.map((source) => _buildSubtitlesSourceRow(source)).toList()); } Widget _buildSubtitlesSourceRow(BetterPlayerSubtitlesSource subtitlesSource) { - final selectedSourceType = betterPlayerController!.betterPlayerSubtitlesSource; + final selectedSourceType = + betterPlayerController!.betterPlayerSubtitlesSource; final bool isSelected = (subtitlesSource == selectedSourceType) || (subtitlesSource.type == BetterPlayerSubtitlesSourceType.none && subtitlesSource.type == selectedSourceType!.type); @@ -239,13 +257,15 @@ abstract class BetterPlayerControlsState extends State visible: isSelected, child: Icon( Icons.check_outlined, - color: betterPlayerControlsConfiguration.overflowModalTextColor, + color: + betterPlayerControlsConfiguration.overflowModalTextColor, )), const SizedBox(width: 16), Text( subtitlesSource.type == BetterPlayerSubtitlesSourceType.none ? betterPlayerController!.translations.generalNone - : subtitlesSource.name ?? betterPlayerController!.translations.generalDefault, + : subtitlesSource.name ?? + betterPlayerController!.translations.generalDefault, style: _getOverflowMenuElementTextStyle(isSelected), ), ], @@ -259,8 +279,10 @@ abstract class BetterPlayerControlsState extends State ///Resolution selection is used for normal videos void _showQualitiesSelectionWidget() { // HLS / DASH - final List asmsTrackNames = betterPlayerController!.betterPlayerDataSource!.asmsTrackNames ?? []; - final List asmsTracks = betterPlayerController!.betterPlayerAsmsTracks; + final List asmsTrackNames = + betterPlayerController!.betterPlayerDataSource!.asmsTrackNames ?? []; + final List asmsTracks = + betterPlayerController!.betterPlayerAsmsTracks; final List children = []; dev.log(asmsTracks.toString()); @@ -279,15 +301,16 @@ abstract class BetterPlayerControlsState extends State } // normal videos - final resolutions = betterPlayerController!.betterPlayerDataSource!.resolutions; + final resolutions = + betterPlayerController!.betterPlayerDataSource!.resolutions; resolutions?.forEach((key, value) { children.add(_buildResolutionSelectionRow(key, value)); }); if (children.isEmpty) { children.add( - _buildTrackRow( - BetterPlayerAsmsTrack.defaultTrack(), betterPlayerController!.translations.qualityAuto), + _buildTrackRow(BetterPlayerAsmsTrack.defaultTrack(), + betterPlayerController!.translations.qualityAuto), ); } @@ -299,10 +322,11 @@ abstract class BetterPlayerControlsState extends State final int height = track.height ?? 0; final int bitrate = track.bitrate ?? 0; final String mimeType = (track.mimeType ?? '').replaceAll('video/', ''); - final String trackName = - preferredName ?? "${width}x$height ${BetterPlayerUtils.formatBitrate(bitrate)} $mimeType"; + final String trackName = preferredName ?? + "${width}x$height ${BetterPlayerUtils.formatBitrate(bitrate)} $mimeType"; - final BetterPlayerAsmsTrack? selectedTrack = betterPlayerController!.betterPlayerAsmsTrack; + final BetterPlayerAsmsTrack? selectedTrack = + betterPlayerController!.betterPlayerAsmsTrack; final bool isSelected = selectedTrack != null && selectedTrack == track; return BetterPlayerMaterialClickableWidget( @@ -319,7 +343,8 @@ abstract class BetterPlayerControlsState extends State visible: isSelected, child: Icon( Icons.check_outlined, - color: betterPlayerControlsConfiguration.overflowModalTextColor, + color: + betterPlayerControlsConfiguration.overflowModalTextColor, )), const SizedBox(width: 16), Text( @@ -333,7 +358,8 @@ abstract class BetterPlayerControlsState extends State } Widget _buildResolutionSelectionRow(String name, String url) { - final bool isSelected = url == betterPlayerController!.betterPlayerDataSource!.url; + final bool isSelected = + url == betterPlayerController!.betterPlayerDataSource!.url; return BetterPlayerMaterialClickableWidget( onTap: () { Navigator.of(context).pop(); @@ -348,7 +374,8 @@ abstract class BetterPlayerControlsState extends State visible: isSelected, child: Icon( Icons.check_outlined, - color: betterPlayerControlsConfiguration.overflowModalTextColor, + color: + betterPlayerControlsConfiguration.overflowModalTextColor, )), const SizedBox(width: 16), Text( @@ -363,13 +390,15 @@ abstract class BetterPlayerControlsState extends State void _showAudioTracksSelectionWidget() { //HLS / DASH - final List? asmsTracks = betterPlayerController!.betterPlayerAsmsAudioTracks; + final List? asmsTracks = + betterPlayerController!.betterPlayerAsmsAudioTracks; final List children = []; final BetterPlayerAsmsAudioTrack? selectedAsmsAudioTrack = betterPlayerController!.betterPlayerAsmsAudioTrack; if (asmsTracks != null) { for (var index = 0; index < asmsTracks.length; index++) { - final bool isSelected = selectedAsmsAudioTrack != null && selectedAsmsAudioTrack == asmsTracks[index]; + final bool isSelected = selectedAsmsAudioTrack != null && + selectedAsmsAudioTrack == asmsTracks[index]; children.add(_buildAudioTrackRow(asmsTracks[index], isSelected)); } } @@ -388,7 +417,8 @@ abstract class BetterPlayerControlsState extends State _showModalBottomSheet(children); } - Widget _buildAudioTrackRow(BetterPlayerAsmsAudioTrack audioTrack, bool isSelected) { + Widget _buildAudioTrackRow( + BetterPlayerAsmsAudioTrack audioTrack, bool isSelected) { return BetterPlayerMaterialClickableWidget( onTap: () { Navigator.of(context).pop(); @@ -403,7 +433,8 @@ abstract class BetterPlayerControlsState extends State visible: isSelected, child: Icon( Icons.check_outlined, - color: betterPlayerControlsConfiguration.overflowModalTextColor, + color: + betterPlayerControlsConfiguration.overflowModalTextColor, )), const SizedBox(width: 16), Text( @@ -421,7 +452,8 @@ abstract class BetterPlayerControlsState extends State fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? betterPlayerControlsConfiguration.overflowModalTextColor - : betterPlayerControlsConfiguration.overflowModalTextColor.withOpacity(0.7), + : betterPlayerControlsConfiguration.overflowModalTextColor + .withOpacity(0.7), ); } @@ -434,23 +466,29 @@ abstract class BetterPlayerControlsState extends State void _showCupertinoModalBottomSheet(List children) { showCupertinoModalPopup( - barrierColor: Colors.transparent, + // barrierColor: Colors.transparent, context: context, - useRootNavigator: betterPlayerController?.betterPlayerConfiguration.useRootNavigator ?? false, + useRootNavigator: + betterPlayerController?.betterPlayerConfiguration.useRootNavigator ?? + false, builder: (context) { return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Container( + width: (betterPlayerController?.isFullScreen ?? false) ? 390 : null, padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), decoration: BoxDecoration( color: betterPlayerControlsConfiguration.overflowModalColor, /*shape: RoundedRectangleBorder(side: Bor,borderRadius: 24,)*/ - borderRadius: - const BorderRadius.only(topLeft: Radius.circular(24.0), topRight: Radius.circular(24.0)), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24.0), + topRight: Radius.circular(24.0)), ), child: SafeArea( top: false, + left: !(betterPlayerController?.isFullScreen ?? false), child: Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox( height: 7, @@ -458,8 +496,9 @@ abstract class BetterPlayerControlsState extends State Container( width: 44, height: 5, - decoration: - BoxDecoration(borderRadius: BorderRadius.circular(9), color: const Color(0xffB0B6CC)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(9), + color: const Color(0xffB0B6CC)), ), const SizedBox( height: 10, @@ -511,7 +550,8 @@ abstract class BetterPlayerControlsState extends State void changePlayerControlsNotVisible(bool notVisible) { setState(() { if (notVisible) { - betterPlayerController?.postEvent(BetterPlayerEvent(BetterPlayerEventType.controlsHiddenStart)); + betterPlayerController?.postEvent( + BetterPlayerEvent(BetterPlayerEventType.controlsHiddenStart)); } controlsNotVisible = notVisible; }); diff --git a/lib/src/controls/better_player_cupertino_controls.dart b/lib/src/controls/better_player_cupertino_controls.dart index 89aafb8..efa0e2f 100644 --- a/lib/src/controls/better_player_cupertino_controls.dart +++ b/lib/src/controls/better_player_cupertino_controls.dart @@ -31,7 +31,8 @@ class BetterPlayerCupertinoControls extends StatefulWidget { } } -class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState { +class _BetterPlayerCupertinoControlsState + extends BetterPlayerControlsState { final marginSize = 5.0; VideoPlayerValue? _latestValue; double? _latestVolume; @@ -44,7 +45,8 @@ class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState widget.controlsConfiguration; + BetterPlayerControlsConfiguration get _controlsConfiguration => + widget.controlsConfiguration; @override VideoPlayerValue? get latestValue => _latestValue; @@ -53,7 +55,8 @@ class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState _betterPlayerController; @override - BetterPlayerControlsConfiguration get betterPlayerControlsConfiguration => _controlsConfiguration; + BetterPlayerControlsConfiguration get betterPlayerControlsConfiguration => + _controlsConfiguration; @override Widget build(BuildContext context) { @@ -85,27 +88,21 @@ class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState[ _buildTopBar( @@ -114,14 +111,8 @@ class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState[ - const SizedBox(width: 8), - if (_controlsConfiguration.enablePlayPause) - _buildPlayPause( - _controller!, - ) - else - const SizedBox(), - const SizedBox(width: 8), - _buildLiveWidget(), - ], + child: _betterPlayerController!.isLiveStream() + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(width: 8), + if (_controlsConfiguration.enablePlayPause) + _buildPlayPause( + _controller!, ) - : Row( - crossAxisAlignment: CrossAxisAlignment.center, - // mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(width: 10), - if (_controlsConfiguration.enableProgressText) _buildPosition() else const SizedBox(), - if (_controlsConfiguration.enableProgressBar) _buildProgressBar() else const SizedBox(), - if (_controlsConfiguration.enableProgressText) _buildRemaining() else const SizedBox(), - _buildFullScreenToogle(), - SizedBox(width: 10), + else + const SizedBox(), + const SizedBox(width: 8), + _buildLiveWidget(), + ], + ) + : Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + // mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 10), + if (_controlsConfiguration.enableProgressText) + _buildPosition() + else + const SizedBox(), + if (_controlsConfiguration.enableProgressBar) + _buildProgressBar() + else + const SizedBox(), + if (_controlsConfiguration.enableProgressText) + _buildRemaining() + else + const SizedBox(), + _buildFullScreenToogle(), + SizedBox(width: 10), + ], + ), + Visibility( + // visible: (_betterPlayerController?.isFullScreen ?? false), + visible: true, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Visibility( + visible: + (_betterPlayerController?.isFullScreen ?? false), + child: _buildSpeedChangeButton( + _controller, + ), + ), + const SizedBox( + width: 20, + ), + Visibility( + visible: + (_betterPlayerController?.isFullScreen ?? false), + child: _buildQualityChangeButton( + _controller, + ), + ), ], ), - ), - ), - ), + ) + ], + ), ); } @@ -261,7 +316,9 @@ class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState 0) - // ? _controlsConfiguration.muteIcon - // : _controlsConfiguration.unMuteIcon, - // color: iconColor, - // size: iconSize, - // ), + child: Padding( + padding: EdgeInsets.fromLTRB(10, 10, 5, 10), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Row( + children: [ + SvgPicture.asset( + PlayerImages.speed, + width: 24, + height: 24, + fit: BoxFit.scaleDown, + ), + Text( + '${betterPlayerController!.videoPlayerController!.value.speed}x', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold), + ) + ], ), ), ), @@ -481,23 +514,36 @@ class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState(_controlsConfiguration.loadingColor), + valueColor: + AlwaysStoppedAnimation(_controlsConfiguration.loadingColor), ); } @@ -848,10 +912,12 @@ class _BetterPlayerCupertinoControlsState extends BetterPlayerControlsState BetterPlayerUtils.log(exception.toString()); } widget.controller.setupTranslations(locale); + + + } @override diff --git a/lib/src/core/better_player_controller.dart b/lib/src/core/better_player_controller.dart index 75ec3b0..8587fca 100644 --- a/lib/src/core/better_player_controller.dart +++ b/lib/src/core/better_player_controller.dart @@ -1,7 +1,9 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; import 'package:better_player/better_player.dart'; +import 'package:better_player/src/checkPoints/check_points.dart'; import 'package:better_player/src/configuration/better_player_controller_event.dart'; import 'package:better_player/src/core/better_player_utils.dart'; import 'package:better_player/src/subtitles/better_player_subtitle.dart'; @@ -10,6 +12,7 @@ import 'package:better_player/src/video_player/video_player.dart'; import 'package:better_player/src/video_player/video_player_platform_interface.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; ///Class used to control overall Better Player behavior. Main class to change @@ -554,6 +557,14 @@ class BetterPlayerController { return temp; } + Map _checkPoints = {}; + + /// Retrieves the crossed check points count + Map getCheckPointsCount() { + return _checkPoints.map((key,value) => MapEntry(key, value.status)); + + } + ///Initializes video based on configuration. Invoke actions which need to be ///run on player start. Future _initializeVideo() async { @@ -590,6 +601,35 @@ class BetterPlayerController { if (startAt != null) { seekTo(startAt); } + int totalDuration = (videoPlayerController?.value.duration?.inSeconds) ?? 0; + + int singleSegmentDuration = totalDuration > 0 ? totalDuration ~/ 10 : 0; + + int tolerance = (singleSegmentDuration * 5) ~/ 100; // 5 % single segment + _checkPoints.clear(); + for (int i = 0; i < 10; i++) { + _checkPoints[i] = CheckPointData( + status: false, videoFraction: singleSegmentDuration * (i + 1)); + } + + if (singleSegmentDuration > 0) { + videoPlayerController?.addListener(() async { + int position = videoPlayerController?.value.position.inSeconds ?? 0; + + int key = (position + tolerance) ~/ singleSegmentDuration; + + + double? currentFraction = _checkPoints[key]?.videoFraction; + + if (currentFraction != null && _checkPoints[key]?.status == false) { + if ((currentFraction - singleSegmentDuration) + tolerance >= + position) { + + _checkPoints[key] = _checkPoints[key]!.copyWith(status: true); + } + } + }); + } } ///Method which is invoked when full screen changes. @@ -1312,6 +1352,7 @@ class BetterPlayerController { _nextVideoTimeStreamController.close(); _controlsVisibilityStreamController.close(); _videoEventStreamSubscription?.cancel(); + // _checkPoints.clear(); _disposed = true; _controllerEventStreamController.close(); diff --git a/pubspec.yaml b/pubspec.yaml index 379d4d1..98650d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: better_player description: Advanced video player based on video_player and Chewie. It's solves many typical use cases and it's easy to run. -version: 0.1.1 +version: 0.1.3 # Disabled because of warning from analyzer # authors: # - Jakub Homlala