From 119ba920eb7b4b400e6d9141cbedeb26a2a692d1 Mon Sep 17 00:00:00 2001
From: lcy <2503978335@qq.com>
Date: Mon, 19 Jun 2023 17:08:34 +0800
Subject: [PATCH] feat:视频播放器
---
android/app/src/main/AndroidManifest.xml | 1 +
ios/Runner/Info.plist | 5 +++++
lib/common/extension/string_extension.dart | 2 +-
lib/home/home_page.dart | 3 ++-
lib/practice/chosetopic/topicword/topic_word_page.dart | 2 +-
lib/route/route.dart | 4 ++++
lib/video/lookvideo/bloc/look_video_bloc.dart | 17 +++++++++++++++++
lib/video/lookvideo/bloc/look_video_event.dart | 4 ++++
lib/video/lookvideo/bloc/look_video_state.dart | 8 ++++++++
lib/video/lookvideo/look_video_page.dart | 33 +++++++++++++++++++++++++++++++++
lib/video/lookvideo/widgets/video_opera_widget.dart | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
lib/video/lookvideo/widgets/video_widget.dart | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
pubspec.yaml | 4 ++++
13 files changed, 327 insertions(+), 3 deletions(-)
create mode 100644 lib/video/lookvideo/bloc/look_video_bloc.dart
create mode 100644 lib/video/lookvideo/bloc/look_video_event.dart
create mode 100644 lib/video/lookvideo/bloc/look_video_state.dart
create mode 100644 lib/video/lookvideo/look_video_page.dart
create mode 100644 lib/video/lookvideo/widgets/video_opera_widget.dart
create mode 100644 lib/video/lookvideo/widgets/video_widget.dart
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 7ffad04..9d2a629 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -30,4 +30,5 @@
android:name="flutterEmbedding"
android:value="2" />
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 36d79e4..6cf0f9e 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -26,6 +26,11 @@
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
UIApplicationSupportsIndirectInputEvents
UILaunchStoryboardName
diff --git a/lib/common/extension/string_extension.dart b/lib/common/extension/string_extension.dart
index d42297a..2536904 100644
--- a/lib/common/extension/string_extension.dart
+++ b/lib/common/extension/string_extension.dart
@@ -6,4 +6,4 @@ extension AssetExtension on String {
String get assetImg => _assetImagePrefix + this;
String get assetPng => 'assets/images/$this.png';
String get assetGif => 'assets/images/$this.gif';
-}
\ No newline at end of file
+}
diff --git a/lib/home/home_page.dart b/lib/home/home_page.dart
index 1e12179..01d2516 100644
--- a/lib/home/home_page.dart
+++ b/lib/home/home_page.dart
@@ -34,7 +34,8 @@ class _HomePageView extends StatelessWidget {
Navigator.of(AppRouter.context).pushNamed(AppRouteName.shop);
} else {
// Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicPic);
- Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicWord);
+ // Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicWord);
+ Navigator.of(AppRouter.context).pushNamed(AppRouteName.lookVideo);
}
}
diff --git a/lib/practice/chosetopic/topicword/topic_word_page.dart b/lib/practice/chosetopic/topicword/topic_word_page.dart
index 90f6f97..b8f7c8c 100644
--- a/lib/practice/chosetopic/topicword/topic_word_page.dart
+++ b/lib/practice/chosetopic/topicword/topic_word_page.dart
@@ -11,7 +11,7 @@ class TopicWordPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => TopicWordBloc(PageController(), 3),
+ create: (context) => TopicWordBloc(PageController(), 4),
child: _TopicWordPage(),
);
}
diff --git a/lib/route/route.dart b/lib/route/route.dart
index 2b3fee6..a48edce 100644
--- a/lib/route/route.dart
+++ b/lib/route/route.dart
@@ -15,6 +15,7 @@ import 'package:wow_english/shop/exchane/exchange_lesson_page.dart';
import 'package:wow_english/shop/exchangelist/exchange_lesson_list_page.dart';
import 'package:wow_english/shop/home/shop_home_page.dart';
import 'package:wow_english/tab/tab_page.dart';
+import 'package:wow_english/video/lookvideo/look_video_page.dart';
class AppRouteName {
@@ -32,6 +33,7 @@ class AppRouteName {
static const String reAfter = 'reAfter';
static const String topicPic = 'topicPic';
static const String topicWord = 'topicWord';
+ static const String lookVideo = 'lookVideo';
static const String tab = '/';
}
@@ -70,6 +72,8 @@ class AppRouter {
return CupertinoPageRoute(builder: (_) => const TopicPicturePage());
case AppRouteName.topicWord:
return CupertinoPageRoute(builder: (_) => const TopicWordPage());
+ case AppRouteName.lookVideo:
+ return CupertinoPageRoute(builder: (_) => const LookVideoPage());
case AppRouteName.setPwd:
final phoneNum = (settings.arguments as Map)['phoneNumber'] as String;
return CupertinoPageRoute(builder: (_) => SetPassWordPage(phoneNum: phoneNum));
diff --git a/lib/video/lookvideo/bloc/look_video_bloc.dart b/lib/video/lookvideo/bloc/look_video_bloc.dart
new file mode 100644
index 0000000..9c10502
--- /dev/null
+++ b/lib/video/lookvideo/bloc/look_video_bloc.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:video_player/video_player.dart';
+
+part 'look_video_event.dart';
+part 'look_video_state.dart';
+
+class LookVideoBloc extends Bloc {
+
+ VideoPlayerController? _controller;
+
+ LookVideoBloc() : super(LookVideoInitial()) {
+ on((event, emit) {
+ // TODO: implement event handler
+ });
+ }
+}
diff --git a/lib/video/lookvideo/bloc/look_video_event.dart b/lib/video/lookvideo/bloc/look_video_event.dart
new file mode 100644
index 0000000..95bd813
--- /dev/null
+++ b/lib/video/lookvideo/bloc/look_video_event.dart
@@ -0,0 +1,4 @@
+part of 'look_video_bloc.dart';
+
+@immutable
+abstract class LookVideoEvent {}
diff --git a/lib/video/lookvideo/bloc/look_video_state.dart b/lib/video/lookvideo/bloc/look_video_state.dart
new file mode 100644
index 0000000..9d7102f
--- /dev/null
+++ b/lib/video/lookvideo/bloc/look_video_state.dart
@@ -0,0 +1,8 @@
+part of 'look_video_bloc.dart';
+
+@immutable
+abstract class LookVideoState {}
+
+class LookVideoInitial extends LookVideoState {}
+
+class VideoStarState extends LookVideoState {}
diff --git a/lib/video/lookvideo/look_video_page.dart b/lib/video/lookvideo/look_video_page.dart
new file mode 100644
index 0000000..b9c3a5c
--- /dev/null
+++ b/lib/video/lookvideo/look_video_page.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:wow_english/video/lookvideo/bloc/look_video_bloc.dart';
+import 'package:wow_english/video/lookvideo/widgets/video_widget.dart';
+
+class LookVideoPage extends StatelessWidget {
+ const LookVideoPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocProvider(
+ create: (context) => LookVideoBloc(),
+ child: _LookVideoPage(),
+ );
+ }
+}
+
+class _LookVideoPage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return BlocListener(
+ listener: (context,state){},
+ child: _lookVideoView(),
+ );
+ }
+
+ Widget _lookVideoView() => BlocBuilder(
+ builder: (context,state){
+ return const VideoWidget(
+ videoUrl: 'https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7194236f31b2e1e3da0fe06cfed4ba2b.mp4',
+ );
+ });
+}
\ No newline at end of file
diff --git a/lib/video/lookvideo/widgets/video_opera_widget.dart b/lib/video/lookvideo/widgets/video_opera_widget.dart
new file mode 100644
index 0000000..9633e33
--- /dev/null
+++ b/lib/video/lookvideo/widgets/video_opera_widget.dart
@@ -0,0 +1,93 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:wow_english/common/extension/string_extension.dart';
+
+class VideoOperaWidget extends StatefulWidget {
+ const VideoOperaWidget({super.key, this.currentTime = '00:00', this.totalTime = '00:00', this.degree = 0.0,});
+ //当前播放时间
+ final String currentTime;
+ //总时间
+ final String totalTime;
+ final double degree;
+
+ @override
+ State createState() {
+ return _VideoOperaWidgetState();
+ }
+}
+
+class _VideoOperaWidgetState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ child: SizedBox(
+ width: double.infinity,
+ height: double.infinity,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: EdgeInsets.only(left: 8.5.w,right: 8.5.w,top: 11.h),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ Image.asset(
+ 'back_around'.assetPng,
+ height: 40,
+ width: 40
+ ),
+ 18.horizontalSpace,
+ Container(
+ height: 40.h,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(6.r),
+ border: Border.all(
+ width: 1.5,
+ color: const Color(0xFF140C10)
+ )
+ ),
+ padding: EdgeInsets.symmetric(horizontal: 10.w),
+ child: Text(
+ 'song',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 20.sp,
+ color: const Color(0xFF333333),
+ ),
+ ),
+ )
+ ],
+ ),
+ Container(
+ height: 40.h,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(6.r),
+ border: Border.all(
+ width: 1.5,
+ color: const Color(0xFF140C10)
+ )
+ ),
+ padding: EdgeInsets.symmetric(horizontal: 10.w),
+ child: Text(
+ '中/英',
+ style: TextStyle(
+ fontSize: 20.sp,
+ color: const Color(0xFF333333),
+ ),
+ ),
+ )
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/video/lookvideo/widgets/video_widget.dart b/lib/video/lookvideo/widgets/video_widget.dart
new file mode 100644
index 0000000..7284f57
--- /dev/null
+++ b/lib/video/lookvideo/widgets/video_widget.dart
@@ -0,0 +1,154 @@
+import 'package:common_utils/common_utils.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:video_player/video_player.dart';
+import 'package:wow_english/video/lookvideo/widgets/video_opera_widget.dart';
+
+class VideoWidget extends StatefulWidget {
+ const VideoWidget({super.key, this.videoUrl = ''});
+
+ final String videoUrl;
+
+ @override
+ State createState() {
+ return _VideoWidgetState();
+ }
+}
+
+class _VideoWidgetState extends State {
+ 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');
+ 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.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);
+ _currentTime = formatDuration(_controller!.value.position);
+ _playDegree = currentSecond/totalSecond;
+ });
+ }
+ }
+ });
+ }
+
+ //开始倒计时
+ void startTimer() {
+ if(timerUtil == null) {
+ timerUtil = TimerUtil(mInterval: 1000,mTotalTime: 1000*10);
+ timerUtil!.setOnTimerTickCallback((int tick) {
+ double currentTick = tick / 1000;
+ if (kDebugMode) {
+ print(currentTick);
+ }
+ if (currentTick.toInt() == 0) {//倒计时结束
+ setState(() {
+ _hiddenTipView = true;
+ });
+ timerUtil!.cancel();
+ timerUtil = null;
+ }
+ });
+ timerUtil!.startCountDown();
+ }
+ }
+
+ //取消倒计时
+ void cancelTimer() {
+ timerUtil!.cancel();
+ timerUtil = null;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = VideoPlayerController.network(widget.videoUrl)
+ ..initialize().then((_){
+ startTimer();
+ setState(() {
+ _currentTime = formatDuration(_controller!.value.position);
+ _totalTime = formatDuration(_controller!.value.duration);
+ _controller!.setLooping(true);
+ _controller!.setVolume(100);
+ _controller!.play();
+ });
+ _addListener();
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ setState(() {
+ _hiddenTipView = !_hiddenTipView;
+ if(!_hiddenTipView) {
+ startTimer();
+ } else {
+ if (timerUtil!.isActive()) {
+ cancelTimer();
+ }
+ }
+ });
+ },
+ onDoubleTap: () {
+ if(_controller!.value.isInitialized) {
+ if (_controller!.value.isPlaying) {
+ _controller!.pause();
+ } else {
+ _controller!.play();
+ }
+ }
+ },
+ 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(
+ currentTime: _currentTime,
+ totalTime: _totalTime,
+ degree: _playDegree,
+ ),
+ )
+ ],
+ ): Container(
+ color: Colors.black,
+ ),
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _controller?.dispose();
+ _controller?.removeListener(() {});
+ if (timerUtil != null) {
+ timerUtil!.cancel();
+ timerUtil = null;
+ }
+ super.dispose();
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 841286f..91a4aa7 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -85,7 +85,11 @@ dependencies:
limiting_direction_csx: ^0.2.0
# 富文本插件 https://pub.dev/packages/extended_text
extended_text: ^11.0.1
+ # 视频播放 https://pub.dev/packages/video_player
+ video_player: ^2.6.1
+ # UI适配 https://pub.dev/packages/responsive_framework
responsive_framework: ^1.0.0
+ auto_orientation: ^2.3.1
dev_dependencies:
build_runner: ^2.4.4
--
libgit2 0.22.2