diff --git a/lib/pages/section/subsection/base_section/bloc.dart b/lib/pages/section/subsection/base_section/bloc.dart index f62c8ea..40bb4fe 100644 --- a/lib/pages/section/subsection/base_section/bloc.dart +++ b/lib/pages/section/subsection/base_section/bloc.dart @@ -12,8 +12,9 @@ abstract class BaseSectionBloc[ - // 左侧可点击的图片 Expanded( flex: 1, child: GestureDetector( onTap: () { - isCompleteDialogShow = false; - popPage(); add(SectionAgainEvent() as E); + popPage(); }, child: Image.asset('section_finish_again'.assetPng), ), ), - // 中间的图片 Expanded( flex: 2, child: Image.asset('section_finish_steve'.assetPng), ), - // 右侧可点击的图片 Expanded( flex: 1, child: GestureDetector( onTap: () { - // 处理右侧图片的点击事件 - isCompleteDialogShow = false; popPage(); nextSectionTap!(); }, @@ -68,6 +63,6 @@ abstract class BaseSectionBloc isCompleteDialogShow = false); } } diff --git a/lib/pages/section/subsection/base_section/state.dart b/lib/pages/section/subsection/base_section/state.dart index 17a32cf..4ef4a6e 100644 --- a/lib/pages/section/subsection/base_section/state.dart +++ b/lib/pages/section/subsection/base_section/state.dart @@ -1,3 +1,7 @@ abstract class BaseSectionState {} -class SectionCompleted extends BaseSectionState {} +///环节完成事件 +class SectionCompletedState extends BaseSectionState {} + +///环节再来一次事件 +class SectionAgainState extends BaseSectionState {} diff --git a/lib/pages/video/lookvideo/bloc/look_video_bloc.dart b/lib/pages/video/lookvideo/bloc/look_video_bloc.dart index f4ecd61..882c666 100644 --- a/lib/pages/video/lookvideo/bloc/look_video_bloc.dart +++ b/lib/pages/video/lookvideo/bloc/look_video_bloc.dart @@ -8,7 +8,7 @@ import 'package:wow_english/pages/section/subsection/base_section/state.dart'; part 'look_video_event.dart'; part 'look_video_state.dart'; -class LookVideoBloc extends BaseSectionBloc { +class LookVideoBloc extends BaseSectionBloc { VideoPlayerController? _controller; @@ -25,5 +25,8 @@ class LookVideoBloc extends BaseSectionBloc { on((event, emit) { // TODO: implement event handler }); + on((event, emit) { + emit(SectionAgainState()); + }); } } diff --git a/lib/pages/video/lookvideo/bloc/look_video_event.dart b/lib/pages/video/lookvideo/bloc/look_video_event.dart index ae5bd1e..d4759e2 100644 --- a/lib/pages/video/lookvideo/bloc/look_video_event.dart +++ b/lib/pages/video/lookvideo/bloc/look_video_event.dart @@ -2,3 +2,5 @@ part of 'look_video_bloc.dart'; @immutable abstract class LookVideoEvent extends BaseSectionEvent {} + +class RestartVideoEvent extends LookVideoEvent {} diff --git a/lib/pages/video/lookvideo/look_video_page.dart b/lib/pages/video/lookvideo/look_video_page.dart index de0a169..3575336 100644 --- a/lib/pages/video/lookvideo/look_video_page.dart +++ b/lib/pages/video/lookvideo/look_video_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:wow_english/pages/section/subsection/base_section/state.dart'; import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart'; import 'package:wow_english/pages/video/lookvideo/widgets/video_widget.dart'; @@ -22,7 +23,7 @@ class LookVideoPage extends StatelessWidget { } Widget _buildPage(BuildContext context) { - return BlocBuilder(builder: (context, state) { + return BlocBuilder(builder: (context, state) { final bloc = BlocProvider.of(context); return Container( color: Colors.white, diff --git a/lib/pages/video/lookvideo/widgets/video_widget.dart b/lib/pages/video/lookvideo/widgets/video_widget.dart index f61f6ae..b81567c 100644 --- a/lib/pages/video/lookvideo/widgets/video_widget.dart +++ b/lib/pages/video/lookvideo/widgets/video_widget.dart @@ -7,10 +7,16 @@ import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart'; import 'package:wow_english/route/route.dart'; +import '../../../section/subsection/base_section/event.dart'; +import '../../../section/subsection/base_section/state.dart'; import 'video_opera_widget.dart'; class VideoWidget extends StatefulWidget { - const VideoWidget({super.key, this.videoUrl = '',this.typeTitle, this.courseLessonId = '', this.isTopic = false}); + const VideoWidget({super.key, + this.videoUrl = '', + this.typeTitle, + this.courseLessonId = '', + this.isTopic = false}); final String videoUrl; final String? typeTitle; @@ -33,45 +39,68 @@ class _VideoWidgetState extends State { String formatDuration(Duration duration) { String hours = duration.inHours.toString().padLeft(2, '0'); - String minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0'); - String seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0'); + String minutes = duration.inMinutes.remainder(60).toString().padLeft( + 2, '0'); + String seconds = duration.inSeconds.remainder(60).toString().padLeft( + 2, '0'); return "$hours:$minutes:$seconds"; } void _addListener() { _controller!.addListener(() { - if(_controller!.value.isInitialized) { + if (_controller!.value.isInitialized) { if (_controller!.value.isPlaying) { setState(() { - double currentSecond = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toDouble(); - int totalSecond = _controller!.value.duration.inMinutes.remainder(60)*60+_controller!.value.duration.inSeconds.remainder(60); + double currentSecond = + (_controller!.value.position.inMinutes.remainder(60) * 60 + + _controller!.value.position.inSeconds.remainder(60)) + .toDouble(); + int totalSecond = + _controller!.value.duration.inMinutes.remainder(60) * 60 + + _controller!.value.duration.inSeconds.remainder(60); _currentTime = formatDuration(_controller!.value.position); - _playDegree = currentSecond/totalSecond; + _playDegree = currentSecond / totalSecond; if (_playDegree > 1.0) { _playDegree = 1.0; } - if(_playDegree < 0) { + if (_playDegree < 0) { _playDegree = 0.0; } }); - } else if (_controller!.value.isCompleted) { - context.read().completeSection((){ - String currentTime = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toString(); - popPage(data:{'courseLessonId':widget.courseLessonId,'currentTime':currentTime, - 'nextSection':widget.isTopic}); - } as VoidCallback); + } else if (widget.isTopic && + //受限于video_player库没有唯一的播放完成状态标识,发现isBuffering=false的时候暂时是安全唯一的 + _controller?.value.isBuffering == false && + _controller!.value.isCompleted && + _controller!.value.position.inSeconds == + _controller!.value.duration.inSeconds) { + final lookVideoBloc = context.read(); + lookVideoBloc.sectionComplete(() { + String currentTime = + (_controller!.value.position.inMinutes.remainder(60) * 60 + + _controller!.value.position.inSeconds.remainder(60)) + .toString(); + popPage(data: { + 'courseLessonId': widget.courseLessonId, + 'currentTime': currentTime, + 'nextSection': widget.isTopic + }); + } as VoidCallback, + againSectionTap: (() { + lookVideoBloc.add(SectionAgainEvent()); + }), context: context); } } }); } //开始倒计时 - void startTimer() { - if(timerUtil == null) { - timerUtil = TimerUtil(mInterval: 1000,mTotalTime: 1000*10); + void startTimer() { + if (timerUtil == null) { + timerUtil = TimerUtil(mInterval: 1000, mTotalTime: 1000 * 10); timerUtil!.setOnTimerTickCallback((int tick) { double currentTick = tick / 1000; - if (currentTick.toInt() == 0) {//倒计时结束 + if (currentTick.toInt() == 0) { + //倒计时结束 setState(() { _hiddenTipView = true; }); @@ -98,8 +127,14 @@ class _VideoWidgetState extends State { popPage(); return; } - String currentTime = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toString(); - popPage(data:{'courseLessonId':widget.courseLessonId,'currentTime':currentTime}); + String currentTime = + (_controller!.value.position.inMinutes.remainder(60) * 60 + + _controller!.value.position.inSeconds.remainder(60)) + .toString(); + popPage(data: { + 'courseLessonId': widget.courseLessonId, + 'currentTime': currentTime + }); } } else if (type == OperationType.playState) { if (_controller!.value.isPlaying) { @@ -107,9 +142,7 @@ class _VideoWidgetState extends State { } else { _controller!.play(); } - setState(() { - - }); + setState(() {}); } } @@ -118,7 +151,7 @@ class _VideoWidgetState extends State { super.initState(); Uri uri = Uri.parse(widget.videoUrl); _controller = VideoPlayerController.networkUrl(uri) - ..initialize().then((_){ + ..initialize().then((_) { startTimer(); setState(() { _currentTime = formatDuration(_controller!.value.position); @@ -133,82 +166,93 @@ class _VideoWidgetState extends State { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - setState(() { - _hiddenTipView = !_hiddenTipView; - if(!_hiddenTipView) { - startTimer(); - } else { - if (timerUtil!.isActive()) { - cancelTimer(); - } - } - }); + return BlocListener( + listener: (context, state) async { + if (state is SectionAgainState) { + await _controller?.seekTo(Duration.zero); + _controller?.play(); + } }, - onDoubleTap: () { - if(_controller!.value.isInitialized) { - if (_controller!.value.isPlaying) { - _controller!.pause(); - } else { - _controller!.play(); - } + child: GestureDetector( + onTap: () { setState(() { - + _hiddenTipView = !_hiddenTipView; + if (!_hiddenTipView) { + startTimer(); + } else { + if (timerUtil!.isActive()) { + cancelTimer(); + } + } }); - } - }, - child: Center( - child: _controller!.value.isInitialized ? Stack( - alignment: Alignment.center, - children: [ - SizedBox( - height: double.infinity, - width: double.infinity, - child: AspectRatio( - aspectRatio: _controller!.value.aspectRatio, - child: VideoPlayer(_controller!), - ), - ), - Offstage( - offstage: _hiddenTipView, - child: VideoOperaWidget( - title: widget.typeTitle??'song', - degree: _playDegree, - totalTime: _totalTime, - currentTime: _currentTime, - isPlay: _controller!.value.isPlaying, - actionEvent: (OperationType type) { - actionType(type); - }, - sliderChangeEvent: (double degree) { - int totalSecond = _controller!.value.duration.inMinutes.remainder(60)*60+_controller!.value.duration.inSeconds.remainder(60); - int positionSecond = (totalSecond * degree).toInt(); - _controller!.seekTo(Duration(seconds: positionSecond)); - }, + }, + onDoubleTap: () { + if (_controller!.value.isInitialized) { + if (_controller!.value.isPlaying) { + _controller!.pause(); + } else { + _controller!.play(); + } + setState(() {}); + } + }, + child: Center( + child: _controller!.value.isInitialized + ? Stack( + alignment: Alignment.center, + children: [ + SizedBox( + height: double.infinity, + width: double.infinity, + child: AspectRatio( + aspectRatio: _controller!.value.aspectRatio, + child: VideoPlayer(_controller!), + ), ), - ), - Offstage( - offstage: _controller!.value.isPlaying, - child: IconButton( - onPressed: () { - _controller!.play(); - }, - icon: Image.asset( - 'video_stop'.assetPng, - width: 70.w, - height: 70.h, + Offstage( + offstage: _hiddenTipView, + child: VideoOperaWidget( + title: widget.typeTitle ?? 'song', + degree: _playDegree, + totalTime: _totalTime, + currentTime: _currentTime, + isPlay: _controller!.value.isPlaying, + actionEvent: (OperationType type) { + actionType(type); + }, + sliderChangeEvent: (double degree) { + int totalSecond = _controller! + .value.duration.inMinutes + .remainder(60) * + 60 + + _controller!.value.duration.inSeconds + .remainder(60); + int positionSecond = (totalSecond * degree).toInt(); + _controller! + .seekTo(Duration(seconds: positionSecond)); + }, ), ), - ) - ], - ): Container( - color: Colors.white, - child: Text( - '视频加载中....', - style: TextStyle( - fontSize: 20.sp, - color: Colors.black + Offstage( + offstage: _controller!.value.isPlaying, + child: IconButton( + onPressed: () { + _controller!.play(); + }, + icon: Image.asset( + 'video_stop'.assetPng, + width: 70.w, + height: 70.h, + ), + ), + ) + ], + ) + : Container( + color: Colors.white, + child: Text( + '视频加载中....', + style: TextStyle(fontSize: 20.sp, color: Colors.black), ), ), ),