Commit 119ba920eb7b4b400e6d9141cbedeb26a2a692d1

Authored by liangchengyou
1 parent 624214d0

feat:视频播放器

android/app/src/main/AndroidManifest.xml
... ... @@ -30,4 +30,5 @@
30 30 android:name="flutterEmbedding"
31 31 android:value="2" />
32 32 </application>
  33 + <uses-permission android:name="android.permission.INTERNET"/>
33 34 </manifest>
... ...
ios/Runner/Info.plist
... ... @@ -26,6 +26,11 @@
26 26 <string>$(FLUTTER_BUILD_NUMBER)</string>
27 27 <key>LSRequiresIPhoneOS</key>
28 28 <true/>
  29 + <key>NSAppTransportSecurity</key>
  30 + <dict>
  31 + <key>NSAllowsArbitraryLoads</key>
  32 + <true/>
  33 + </dict>
29 34 <key>UIApplicationSupportsIndirectInputEvents</key>
30 35 <true/>
31 36 <key>UILaunchStoryboardName</key>
... ...
lib/common/extension/string_extension.dart
... ... @@ -6,4 +6,4 @@ extension AssetExtension on String {
6 6 String get assetImg => _assetImagePrefix + this;
7 7 String get assetPng => 'assets/images/$this.png';
8 8 String get assetGif => 'assets/images/$this.gif';
9   -}
10 9 \ No newline at end of file
  10 +}
... ...
lib/home/home_page.dart
... ... @@ -34,7 +34,8 @@ class _HomePageView extends StatelessWidget {
34 34 Navigator.of(AppRouter.context).pushNamed(AppRouteName.shop);
35 35 } else {
36 36 // Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicPic);
37   - Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicWord);
  37 + // Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicWord);
  38 + Navigator.of(AppRouter.context).pushNamed(AppRouteName.lookVideo);
38 39 }
39 40 }
40 41  
... ...
lib/practice/chosetopic/topicword/topic_word_page.dart
... ... @@ -11,7 +11,7 @@ class TopicWordPage extends StatelessWidget {
11 11 @override
12 12 Widget build(BuildContext context) {
13 13 return BlocProvider(
14   - create: (context) => TopicWordBloc(PageController(), 3),
  14 + create: (context) => TopicWordBloc(PageController(), 4),
15 15 child: _TopicWordPage(),
16 16 );
17 17 }
... ...
lib/route/route.dart
... ... @@ -15,6 +15,7 @@ import &#39;package:wow_english/shop/exchane/exchange_lesson_page.dart&#39;;
15 15 import 'package:wow_english/shop/exchangelist/exchange_lesson_list_page.dart';
16 16 import 'package:wow_english/shop/home/shop_home_page.dart';
17 17 import 'package:wow_english/tab/tab_page.dart';
  18 +import 'package:wow_english/video/lookvideo/look_video_page.dart';
18 19  
19 20  
20 21 class AppRouteName {
... ... @@ -32,6 +33,7 @@ class AppRouteName {
32 33 static const String reAfter = 'reAfter';
33 34 static const String topicPic = 'topicPic';
34 35 static const String topicWord = 'topicWord';
  36 + static const String lookVideo = 'lookVideo';
35 37 static const String tab = '/';
36 38 }
37 39  
... ... @@ -70,6 +72,8 @@ class AppRouter {
70 72 return CupertinoPageRoute(builder: (_) => const TopicPicturePage());
71 73 case AppRouteName.topicWord:
72 74 return CupertinoPageRoute(builder: (_) => const TopicWordPage());
  75 + case AppRouteName.lookVideo:
  76 + return CupertinoPageRoute(builder: (_) => const LookVideoPage());
73 77 case AppRouteName.setPwd:
74 78 final phoneNum = (settings.arguments as Map)['phoneNumber'] as String;
75 79 return CupertinoPageRoute(builder: (_) => SetPassWordPage(phoneNum: phoneNum));
... ...
lib/video/lookvideo/bloc/look_video_bloc.dart 0 → 100644
  1 +import 'package:flutter/cupertino.dart';
  2 +import 'package:flutter_bloc/flutter_bloc.dart';
  3 +import 'package:video_player/video_player.dart';
  4 +
  5 +part 'look_video_event.dart';
  6 +part 'look_video_state.dart';
  7 +
  8 +class LookVideoBloc extends Bloc<LookVideoEvent, LookVideoState> {
  9 +
  10 + VideoPlayerController? _controller;
  11 +
  12 + LookVideoBloc() : super(LookVideoInitial()) {
  13 + on<LookVideoEvent>((event, emit) {
  14 + // TODO: implement event handler
  15 + });
  16 + }
  17 +}
... ...
lib/video/lookvideo/bloc/look_video_event.dart 0 → 100644
  1 +part of 'look_video_bloc.dart';
  2 +
  3 +@immutable
  4 +abstract class LookVideoEvent {}
... ...
lib/video/lookvideo/bloc/look_video_state.dart 0 → 100644
  1 +part of 'look_video_bloc.dart';
  2 +
  3 +@immutable
  4 +abstract class LookVideoState {}
  5 +
  6 +class LookVideoInitial extends LookVideoState {}
  7 +
  8 +class VideoStarState extends LookVideoState {}
... ...
lib/video/lookvideo/look_video_page.dart 0 → 100644
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter_bloc/flutter_bloc.dart';
  3 +import 'package:wow_english/video/lookvideo/bloc/look_video_bloc.dart';
  4 +import 'package:wow_english/video/lookvideo/widgets/video_widget.dart';
  5 +
  6 +class LookVideoPage extends StatelessWidget {
  7 + const LookVideoPage({super.key});
  8 +
  9 + @override
  10 + Widget build(BuildContext context) {
  11 + return BlocProvider(
  12 + create: (context) => LookVideoBloc(),
  13 + child: _LookVideoPage(),
  14 + );
  15 + }
  16 +}
  17 +
  18 +class _LookVideoPage extends StatelessWidget {
  19 + @override
  20 + Widget build(BuildContext context) {
  21 + return BlocListener<LookVideoBloc, LookVideoState>(
  22 + listener: (context,state){},
  23 + child: _lookVideoView(),
  24 + );
  25 + }
  26 +
  27 + Widget _lookVideoView() => BlocBuilder<LookVideoBloc, LookVideoState>(
  28 + builder: (context,state){
  29 + return const VideoWidget(
  30 + videoUrl: 'https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7194236f31b2e1e3da0fe06cfed4ba2b.mp4',
  31 + );
  32 + });
  33 +}
0 34 \ No newline at end of file
... ...
lib/video/lookvideo/widgets/video_opera_widget.dart 0 → 100644
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter_screenutil/flutter_screenutil.dart';
  3 +import 'package:wow_english/common/extension/string_extension.dart';
  4 +
  5 +class VideoOperaWidget extends StatefulWidget {
  6 + const VideoOperaWidget({super.key, this.currentTime = '00:00', this.totalTime = '00:00', this.degree = 0.0,});
  7 + //当前播放时间
  8 + final String currentTime;
  9 + //总时间
  10 + final String totalTime;
  11 + final double degree;
  12 +
  13 + @override
  14 + State<StatefulWidget> createState() {
  15 + return _VideoOperaWidgetState();
  16 + }
  17 +}
  18 +
  19 +class _VideoOperaWidgetState extends State<VideoOperaWidget> {
  20 + @override
  21 + Widget build(BuildContext context) {
  22 + return SafeArea(
  23 + child: SizedBox(
  24 + width: double.infinity,
  25 + height: double.infinity,
  26 + child: Column(
  27 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
  28 + children: [
  29 + Padding(
  30 + padding: EdgeInsets.only(left: 8.5.w,right: 8.5.w,top: 11.h),
  31 + child: Row(
  32 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
  33 + children: [
  34 + Row(
  35 + children: [
  36 + Image.asset(
  37 + 'back_around'.assetPng,
  38 + height: 40,
  39 + width: 40
  40 + ),
  41 + 18.horizontalSpace,
  42 + Container(
  43 + height: 40.h,
  44 + alignment: Alignment.center,
  45 + decoration: BoxDecoration(
  46 + color: Colors.white,
  47 + borderRadius: BorderRadius.circular(6.r),
  48 + border: Border.all(
  49 + width: 1.5,
  50 + color: const Color(0xFF140C10)
  51 + )
  52 + ),
  53 + padding: EdgeInsets.symmetric(horizontal: 10.w),
  54 + child: Text(
  55 + 'song',
  56 + textAlign: TextAlign.center,
  57 + style: TextStyle(
  58 + fontSize: 20.sp,
  59 + color: const Color(0xFF333333),
  60 + ),
  61 + ),
  62 + )
  63 + ],
  64 + ),
  65 + Container(
  66 + height: 40.h,
  67 + alignment: Alignment.center,
  68 + decoration: BoxDecoration(
  69 + color: Colors.white,
  70 + borderRadius: BorderRadius.circular(6.r),
  71 + border: Border.all(
  72 + width: 1.5,
  73 + color: const Color(0xFF140C10)
  74 + )
  75 + ),
  76 + padding: EdgeInsets.symmetric(horizontal: 10.w),
  77 + child: Text(
  78 + '中/英',
  79 + style: TextStyle(
  80 + fontSize: 20.sp,
  81 + color: const Color(0xFF333333),
  82 + ),
  83 + ),
  84 + )
  85 + ],
  86 + ),
  87 + )
  88 + ],
  89 + ),
  90 + ),
  91 + );
  92 + }
  93 +}
0 94 \ No newline at end of file
... ...
lib/video/lookvideo/widgets/video_widget.dart 0 → 100644
  1 +import 'package:common_utils/common_utils.dart';
  2 +import 'package:flutter/foundation.dart';
  3 +import 'package:flutter/material.dart';
  4 +import 'package:video_player/video_player.dart';
  5 +import 'package:wow_english/video/lookvideo/widgets/video_opera_widget.dart';
  6 +
  7 +class VideoWidget extends StatefulWidget {
  8 + const VideoWidget({super.key, this.videoUrl = ''});
  9 +
  10 + final String videoUrl;
  11 +
  12 + @override
  13 + State<StatefulWidget> createState() {
  14 + return _VideoWidgetState();
  15 + }
  16 +}
  17 +
  18 +class _VideoWidgetState extends State<VideoWidget> {
  19 + VideoPlayerController? _controller;
  20 + String _currentTime = '00:00';
  21 + String _totalTime = '00:00';
  22 + double _playDegree = 0.0;
  23 + bool _hiddenTipView = false;
  24 + TimerUtil? timerUtil;
  25 +
  26 + String formatDuration(Duration duration) {
  27 + String hours = duration.inHours.toString().padLeft(2, '0');
  28 + String minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
  29 + String seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
  30 + return "$hours:$minutes:$seconds";
  31 + }
  32 +
  33 + void _addListener() {
  34 + _controller!.addListener(() {
  35 + if(_controller!.value.isInitialized) {
  36 + if (_controller!.value.isPlaying) {
  37 + setState(() {
  38 + double currentSecond = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toDouble();
  39 + int totalSecond = _controller!.value.duration.inMinutes.remainder(60)*60+_controller!.value.duration.inSeconds.remainder(60);
  40 + _currentTime = formatDuration(_controller!.value.position);
  41 + _playDegree = currentSecond/totalSecond;
  42 + });
  43 + }
  44 + }
  45 + });
  46 + }
  47 +
  48 + //开始倒计时
  49 + void startTimer() {
  50 + if(timerUtil == null) {
  51 + timerUtil = TimerUtil(mInterval: 1000,mTotalTime: 1000*10);
  52 + timerUtil!.setOnTimerTickCallback((int tick) {
  53 + double currentTick = tick / 1000;
  54 + if (kDebugMode) {
  55 + print(currentTick);
  56 + }
  57 + if (currentTick.toInt() == 0) {//倒计时结束
  58 + setState(() {
  59 + _hiddenTipView = true;
  60 + });
  61 + timerUtil!.cancel();
  62 + timerUtil = null;
  63 + }
  64 + });
  65 + timerUtil!.startCountDown();
  66 + }
  67 + }
  68 +
  69 + //取消倒计时
  70 + void cancelTimer() {
  71 + timerUtil!.cancel();
  72 + timerUtil = null;
  73 + }
  74 +
  75 + @override
  76 + void initState() {
  77 + super.initState();
  78 + _controller = VideoPlayerController.network(widget.videoUrl)
  79 + ..initialize().then((_){
  80 + startTimer();
  81 + setState(() {
  82 + _currentTime = formatDuration(_controller!.value.position);
  83 + _totalTime = formatDuration(_controller!.value.duration);
  84 + _controller!.setLooping(true);
  85 + _controller!.setVolume(100);
  86 + _controller!.play();
  87 + });
  88 + _addListener();
  89 + });
  90 + }
  91 +
  92 + @override
  93 + Widget build(BuildContext context) {
  94 + return GestureDetector(
  95 + onTap: () {
  96 + setState(() {
  97 + _hiddenTipView = !_hiddenTipView;
  98 + if(!_hiddenTipView) {
  99 + startTimer();
  100 + } else {
  101 + if (timerUtil!.isActive()) {
  102 + cancelTimer();
  103 + }
  104 + }
  105 + });
  106 + },
  107 + onDoubleTap: () {
  108 + if(_controller!.value.isInitialized) {
  109 + if (_controller!.value.isPlaying) {
  110 + _controller!.pause();
  111 + } else {
  112 + _controller!.play();
  113 + }
  114 + }
  115 + },
  116 + child: Center(
  117 + child: _controller!.value.isInitialized ? Stack(
  118 + alignment: Alignment.center,
  119 + children: [
  120 + SizedBox(
  121 + height: double.infinity,
  122 + width: double.infinity,
  123 + child: AspectRatio(
  124 + aspectRatio: _controller!.value.aspectRatio,
  125 + child: VideoPlayer(_controller!),
  126 + ),
  127 + ),
  128 + Offstage(
  129 + offstage: _hiddenTipView,
  130 + child: VideoOperaWidget(
  131 + currentTime: _currentTime,
  132 + totalTime: _totalTime,
  133 + degree: _playDegree,
  134 + ),
  135 + )
  136 + ],
  137 + ): Container(
  138 + color: Colors.black,
  139 + ),
  140 + ),
  141 + );
  142 + }
  143 +
  144 + @override
  145 + void dispose() {
  146 + _controller?.dispose();
  147 + _controller?.removeListener(() {});
  148 + if (timerUtil != null) {
  149 + timerUtil!.cancel();
  150 + timerUtil = null;
  151 + }
  152 + super.dispose();
  153 + }
  154 +}
... ...
pubspec.yaml
... ... @@ -85,7 +85,11 @@ dependencies:
85 85 limiting_direction_csx: ^0.2.0
86 86 # 富文本插件 https://pub.dev/packages/extended_text
87 87 extended_text: ^11.0.1
  88 + # 视频播放 https://pub.dev/packages/video_player
  89 + video_player: ^2.6.1
  90 + # UI适配 https://pub.dev/packages/responsive_framework
88 91 responsive_framework: ^1.0.0
  92 + auto_orientation: ^2.3.1
89 93  
90 94 dev_dependencies:
91 95 build_runner: ^2.4.4
... ...