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
|
}
|