| 119ba920  liangchengyou
 
feat:视频播放器 | 1 |   import 'package:common_utils/common_utils.dart';
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 2 |   import 'package:flutter/material.dart';
 | 
| 46675a89  吴启风
 
feat:过渡页-视频环节 | 3 |   import 'package:flutter_bloc/flutter_bloc.dart';
 | 
| cb38bc90  liangchengyou
 
feat:视频跟读逻辑处理 | 4 |   import 'package:flutter_screenutil/flutter_screenutil.dart';
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 5 |   import 'package:video_player/video_player.dart';
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 6 |   import 'package:wow_english/common/extension/string_extension.dart';
 | 
| 46675a89  吴启风
 
feat:过渡页-视频环节 | 7 |   import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart';
 | 
| 934e2b47  liangchengyou
 
feat:权限调整+课程进度接口对接 | 8 |   import 'package:wow_english/route/route.dart';
 | 
| 4b358e22  liangchengyou
 
feat:调整文件结构 | 9 |   
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 10
11 |   import '../../../section/subsection/base_section/event.dart';
  import '../../../section/subsection/base_section/state.dart';
 | 
| 4b358e22  liangchengyou
 
feat:调整文件结构 | 12 |   import 'video_opera_widget.dart';
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 13
14 |   
  class VideoWidget extends StatefulWidget {
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 15
16
17
18
19 |     const VideoWidget({super.key,
      this.videoUrl = '',
      this.typeTitle,
      this.courseLessonId = '',
      this.isTopic = false});
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 20
21 |   
    final String videoUrl;
 | 
| 842b7132  liangchengyou
 
feat:磨耳朵/练习页面调整 | 22 |     final String? typeTitle;
 | 
| 934e2b47  liangchengyou
 
feat:权限调整+课程进度接口对接 | 23 |     final String courseLessonId;
 | 
| 46675a89  吴启风
 
feat:过渡页-视频环节 | 24 |     final bool isTopic;
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 |   
    @override
    State<StatefulWidget> createState() {
      return _VideoWidgetState();
    }
  }
  
  class _VideoWidgetState extends State<VideoWidget> {
    VideoPlayerController? _controller;
    String _currentTime = '00:00';
    String _totalTime = '00:00';
    double _playDegree = 0.0;
    bool _hiddenTipView = false;
    TimerUtil? timerUtil;
  
    String formatDuration(Duration duration) {
      String hours = duration.inHours.toString().padLeft(2, '0');
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 42
43
44
45 |       String minutes = duration.inMinutes.remainder(60).toString().padLeft(
          2, '0');
      String seconds = duration.inSeconds.remainder(60).toString().padLeft(
          2, '0');
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 46
47
48
49
50 |       return "$hours:$minutes:$seconds";
    }
  
    void _addListener() {
      _controller!.addListener(() {
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 51 |         if (_controller!.value.isInitialized) {
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 52
53 |           if (_controller!.value.isPlaying) {
            setState(() {
 | 
| 66a7e3e7  吴启风
 
feat:退出课堂和结束课堂优化 | 54 |               double currentSecond = getCurrentPositionSeconds().toDouble();
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 55
56
57 |               int totalSecond =
                  _controller!.value.duration.inMinutes.remainder(60) * 60 +
                      _controller!.value.duration.inSeconds.remainder(60);
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 58 |               _currentTime = formatDuration(_controller!.value.position);
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 59 |               _playDegree = currentSecond / totalSecond;
 | 
| 3840b7fe  liangchengyou
 
feat:更新设置页面 | 60
61
62 |               if (_playDegree > 1.0) {
                _playDegree = 1.0;
              }
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 63 |               if (_playDegree < 0) {
 | 
| 3840b7fe  liangchengyou
 
feat:更新设置页面 | 64
65 |                 _playDegree = 0.0;
              }
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 66 |             });
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 67
68
69
70
71
72
73
74 |           } 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>();
            lookVideoBloc.sectionComplete(() {
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 75
76 |               popPage(data: {
                'courseLessonId': widget.courseLessonId,
 | 
| 66a7e3e7  吴启风
 
feat:退出课堂和结束课堂优化 | 77
78 |                 'currentTime': getCurrentPositionSeconds(),
                'isCompleted': true,
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 79
80
81
82
83
84 |                 'nextSection': widget.isTopic
              });
            } as VoidCallback,
                againSectionTap: (() {
                  lookVideoBloc.add(SectionAgainEvent());
                }), context: context);
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 85
86
87
88
89
90 |           }
        }
      });
    }
  
    //开始倒计时
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 91
92
93 |     void startTimer() {
      if (timerUtil == null) {
        timerUtil = TimerUtil(mInterval: 1000, mTotalTime: 1000 * 10);
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 94
95 |         timerUtil!.setOnTimerTickCallback((int tick) {
          double currentTick = tick / 1000;
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 96
97 |           if (currentTick.toInt() == 0) {
            //倒计时结束
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 |             setState(() {
              _hiddenTipView = true;
            });
            timerUtil!.cancel();
            timerUtil = null;
          }
        });
        timerUtil!.startCountDown();
      }
    }
  
    //取消倒计时
    void cancelTimer() {
      timerUtil!.cancel();
      timerUtil = null;
    }
  
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 115
116 |     void actionType(OperationType type) async {
      if (type == OperationType.back) {
 | 
| 934e2b47  liangchengyou
 
feat:权限调整+课程进度接口对接 | 117
118
119
120
121
122
123 |         if (widget.courseLessonId.isEmpty) {
          popPage();
        } else {
          if (_controller == null) {
            popPage();
            return;
          }
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 124
125 |           popPage(data: {
            'courseLessonId': widget.courseLessonId,
 | 
| 66a7e3e7  吴启风
 
feat:退出课堂和结束课堂优化 | 126 |             'currentTime': getCurrentPositionSeconds(),
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 127 |           });
 | 
| 934e2b47  liangchengyou
 
feat:权限调整+课程进度接口对接 | 128 |         }
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 129
130
131
132
133
134 |       } else if (type == OperationType.playState) {
        if (_controller!.value.isPlaying) {
          _controller!.pause();
        } else {
          _controller!.play();
        }
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 135 |         setState(() {});
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 136
137
138 |       }
    }
  
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 139
140
141 |     @override
    void initState() {
      super.initState();
 | 
| 934e2b47  liangchengyou
 
feat:权限调整+课程进度接口对接 | 142
143 |       Uri uri = Uri.parse(widget.videoUrl);
      _controller = VideoPlayerController.networkUrl(uri)
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 144 |         ..initialize().then((_) {
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 145
146
147
148 |           startTimer();
          setState(() {
            _currentTime = formatDuration(_controller!.value.position);
            _totalTime = formatDuration(_controller!.value.duration);
 | 
| 46675a89  吴启风
 
feat:过渡页-视频环节 | 149 |             _controller!.setLooping(!widget.isTopic);
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 150
151
152
153
154
155
156
157
158 |             _controller!.setVolume(100);
            _controller!.play();
          });
          _addListener();
        });
    }
  
    @override
    Widget build(BuildContext context) {
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 159
160
161
162
163
164 |       return BlocListener<LookVideoBloc, BaseSectionState>(
        listener: (context, state) async {
          if (state is SectionAgainState) {
            await _controller?.seekTo(Duration.zero);
            _controller?.play();
          }
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 165 |         },
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 166
167 |         child: GestureDetector(
          onTap: () {
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 168 |             setState(() {
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 169
170
171
172
173
174
175
176 |               _hiddenTipView = !_hiddenTipView;
              if (!_hiddenTipView) {
                startTimer();
              } else {
                if (timerUtil!.isActive()) {
                  cancelTimer();
                }
              }
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 177 |             });
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200 |           },
          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!),
                  ),
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 201 |                 ),
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 |                 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));
                    },
 | 
| 91fe517a  liangchengyou
 
feat:看视频功能开发 | 224 |                   ),
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 225 |                 ),
 | 
| aa0d2360  吴启风
 
feat:过渡页-视频环节(再来一次) | 226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242 |                 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,
 | 
| 66a7e3e7  吴启风
 
feat:退出课堂和结束课堂优化 | 243
244
245
246
247 |               child: const CircularProgressIndicator(),
              // Text(
              //   '视频加载中....',
              //   style: TextStyle(fontSize: 20.sp, color: Colors.black),
              // ),
 | 
| cb38bc90  liangchengyou
 
feat:视频跟读逻辑处理 | 248 |             ),
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 249
250
251
252
253
254
255
256
257
258
259
260
261
262
263 |           ),
        ),
      );
    }
  
    @override
    void dispose() {
      _controller?.dispose();
      _controller?.removeListener(() {});
      if (timerUtil != null) {
        timerUtil!.cancel();
        timerUtil = null;
      }
      super.dispose();
    }
 | 
| 66a7e3e7  吴启风
 
feat:退出课堂和结束课堂优化 | 264
265
266
267
268
269 |   
    ///获取当前进度秒数
    int getCurrentPositionSeconds() {
      return (_controller!.value.position.inMinutes.remainder(60) * 60 +
          _controller!.value.position.inSeconds.remainder(60));
    }
 | 
| 119ba920  liangchengyou
 
feat:视频播放器 | 270 |   }
 |