Skip to content

Commit cd82981

Browse files
author
wangchangsheng
committedMar 17, 2022
commit-添加支持视频列表播放,支持滚动屏幕播放器状态监听
1 parent 93eec8f commit cd82981

File tree

12 files changed

+589
-72
lines changed

12 files changed

+589
-72
lines changed
 

‎assets/images/icon_pause.png

4.13 KB
Loading

‎assets/images/icon_play.png

4.82 KB
Loading

‎ios/Runner.xcodeproj/project.pbxproj

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 50;
6+
objectVersion = 51;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -76,7 +76,6 @@
7676
DAAC23E9F40DFE86A7A7CC42 /* Pods-Runner.release.xcconfig */,
7777
8AEF2CB413ADB124BF911F41 /* Pods-Runner.profile.xcconfig */,
7878
);
79-
name = Pods;
8079
path = Pods;
8180
sourceTree = "<group>";
8281
};
@@ -356,6 +355,7 @@
356355
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
357356
CLANG_ENABLE_MODULES = YES;
358357
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
358+
DEVELOPMENT_TEAM = 5NVG88VBQ8;
359359
ENABLE_BITCODE = NO;
360360
INFOPLIST_FILE = Runner/Info.plist;
361361
LD_RUNPATH_SEARCH_PATHS = (
@@ -484,6 +484,7 @@
484484
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
485485
CLANG_ENABLE_MODULES = YES;
486486
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
487+
DEVELOPMENT_TEAM = 5NVG88VBQ8;
487488
ENABLE_BITCODE = NO;
488489
INFOPLIST_FILE = Runner/Info.plist;
489490
LD_RUNPATH_SEARCH_PATHS = (
@@ -506,6 +507,7 @@
506507
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
507508
CLANG_ENABLE_MODULES = YES;
508509
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
510+
DEVELOPMENT_TEAM = 5NVG88VBQ8;
509511
ENABLE_BITCODE = NO;
510512
INFOPLIST_FILE = Runner/Info.plist;
511513
LD_RUNPATH_SEARCH_PATHS = (

‎lib/business/detail_page/detail_page.dart

+52-33
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ import 'package:open_eye/widget/fijkplayer_skin/video_config.dart';
1616
///视频详情页面
1717
// ignore: must_be_immutable
1818
class DetailPage extends BaseStatelessWidget<DetailController> {
19-
DetailPage({Key? key}) : super(key: key);
20-
21-
var videoPlayHeight = 450.w;
19+
const DetailPage({Key? key}) : super(key: key);
2220

2321
@override
2422
Widget buildContent(BuildContext context) {
@@ -28,38 +26,58 @@ class DetailPage extends BaseStatelessWidget<DetailController> {
2826
height: MediaQueryData.fromWindow(window).padding.top,
2927
color: ColorStyle.color_black,
3028
),
31-
FijkView(
32-
player: controller.player,
33-
height: videoPlayHeight,
34-
fit: FijkFit.cover,
35-
panelBuilder: (FijkPlayer player, FijkData data, BuildContext context,
36-
Size viewSize, Rect texturePos) {
37-
return Obx(() => CustomFijkPanel(
38-
player: player,
39-
viewSize: viewSize,
40-
texturePos: texturePos,
41-
playerTitle: controller.title.value,
42-
showConfig: controller.videoConfig,
43-
curPlayUrl: '',
44-
pageContent: context,
45-
));
46-
},
29+
Container(
30+
height: 450.w,
31+
color: ColorStyle.color_black,
32+
child: FijkView(
33+
player: controller.player,
34+
height: 450.w,
35+
color: ColorStyle.color_black,
36+
fit: FijkFit.cover,
37+
panelBuilder: (FijkPlayer player, FijkData data,
38+
BuildContext context, Size viewSize, Rect texturePos) {
39+
return Obx(() => CustomFijkPanel(
40+
player: player,
41+
viewSize: viewSize,
42+
texturePos: texturePos,
43+
playerTitle: controller.title.value,
44+
showConfig: controller.videoConfig,
45+
curPlayUrl: '',
46+
pageContent: context,
47+
));
48+
},
49+
),
4750
),
4851
Expanded(
49-
child: ListView.builder(
50-
itemBuilder: (BuildContext context, int index) {
51-
return GestureDetector(
52-
child: ItemVideoDetailWidget(controller.dataList[index]),
53-
onTap: () async {
54-
var playUrl = controller.dataList[index].data?.playUrl ?? "";
55-
var title = controller.dataList[index].data?.title ?? "";
56-
controller.title.value = title;
57-
controller.player.setDataSource(playUrl, autoPlay: true);
58-
},
59-
);
60-
},
61-
itemCount: controller.dataList.length,
62-
))
52+
child: MediaQuery.removePadding(
53+
removeTop: true,
54+
context: context,
55+
child: ListView.builder(
56+
shrinkWrap: true,
57+
itemBuilder: (BuildContext context, int index) {
58+
return Obx(() => GestureDetector(
59+
child: ItemVideoDetailWidget(
60+
controller.dataList[index],
61+
controller.selectIndex.value,
62+
index),
63+
onTap: () async {
64+
if (index == controller.selectIndex.value) {
65+
return;
66+
}
67+
var playUrl =
68+
controller.dataList[index].data?.playUrl ?? "";
69+
var title =
70+
controller.dataList[index].data?.title ?? "";
71+
controller.title.value = title;
72+
controller.selectIndex.value = index;
73+
await controller.player.reset();
74+
controller.player
75+
.setDataSource(playUrl, autoPlay: true);
76+
},
77+
));
78+
},
79+
itemCount: controller.dataList.length,
80+
)))
6381
],
6482
);
6583
}
@@ -78,6 +96,7 @@ class DetailController extends BaseController<ApiService> {
7896
RxString coverUrl = (Get.parameters["coverUrl"] ?? "").obs;
7997
RxString title = (Get.parameters["title"] ?? "").obs;
8098
RxList<FocusItemEntity> dataList = <FocusItemEntity>[].obs;
99+
RxInt selectIndex = (-1).obs;
81100

82101
@override
83102
void onReady() {

‎lib/business/detail_page/widget/item_video_detail_widget.dart

+20-6
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ import 'package:open_eye/business/find_page/model/Focus_Item_Entity.dart';
66
import 'package:open_eye/res/colors.dart';
77
import 'package:open_eye/res/style.dart';
88

9+
// ignore: must_be_immutable
910
class ItemVideoDetailWidget extends CommonStatelessWidget {
1011
FocusItemEntity itemData;
1112

12-
ItemVideoDetailWidget(this.itemData, {Key? key}) : super(key: key);
13+
int selectIndex;
14+
15+
int itemIndex;
16+
17+
ItemVideoDetailWidget(this.itemData, this.selectIndex, this.itemIndex,
18+
{Key? key})
19+
: super(key: key);
1320

1421
@override
1522
Widget build(BuildContext context) {
1623
return Container(
24+
color: ColorStyle.color_white,
1725
padding: EdgeInsets.only(left: 20.w, right: 20.w),
1826
height: 180.w,
1927
child: Row(
@@ -31,8 +39,9 @@ class ItemVideoDetailWidget extends CommonStatelessWidget {
3139
borderRadius: BorderRadius.all(Radius.circular(10.w)),
3240
image: DecorationImage(
3341
image: CachedNetworkImageProvider(
34-
itemData.data?.cover?.feed ?? "",
35-
),fit: BoxFit.cover)),
42+
itemData.data?.cover?.feed ?? "",
43+
),
44+
fit: BoxFit.cover)),
3645
),
3746
Box.wBox16,
3847
Expanded(
@@ -41,16 +50,21 @@ class ItemVideoDetailWidget extends CommonStatelessWidget {
4150
children: [
4251
Text(
4352
itemData.data?.title ?? "",
44-
style:
45-
TextStyle(color: ColorStyle.color_333333, fontSize: 28.w),
53+
style: TextStyle(
54+
color: selectIndex == itemIndex
55+
? ColorStyle.color_EA4C43
56+
: ColorStyle.color_333333,
57+
fontSize: 28.w),
4658
overflow: TextOverflow.ellipsis,
4759
maxLines: 2,
4860
),
4961
Box.hBox15,
5062
Text(
5163
itemData.data?.description ?? "",
5264
style:
53-
TextStyle(color: ColorStyle.color_999999, fontSize: 24.w),
65+
TextStyle(color: selectIndex == itemIndex
66+
? ColorStyle.color_EA4C43
67+
: ColorStyle.color_999999, fontSize: 24.w),
5468
overflow: TextOverflow.ellipsis,
5569
maxLines: 2,
5670
)

‎lib/business/topic_detail/topic_detail_page.dart

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:fijkplayer/fijkplayer.dart';
12
import 'package:flutter/cupertino.dart';
23
import 'package:flutter_screenutil/flutter_screenutil.dart';
34
import 'package:get/get_core/src/get_main.dart';
@@ -13,6 +14,7 @@ import 'package:open_eye/business/topic_detail/widget/item_topic_detail_widget.d
1314
import 'package:open_eye/http/apiservice/api_service.dart';
1415
import 'package:open_eye/res/colors.dart';
1516
import 'package:open_eye/res/style.dart';
17+
import 'package:open_eye/utils/log_utils.dart';
1618
import 'package:open_eye/widget/base_network_image.dart';
1719

1820
import 'model/Topic_ItemList.dart';
@@ -31,7 +33,14 @@ class TopicDetailPage extends BaseStatelessWidget<TopicDetailController> {
3133
physics: const NeverScrollableScrollPhysics(),
3234
shrinkWrap: true,
3335
itemBuilder: (context, index) {
34-
return ItemTopicDetailWidget(controller.dataList[index]);
36+
return Obx(() => ItemTopicDetailWidget(
37+
controller.dataList[index],
38+
controller.player,
39+
index,
40+
controller.playingIndex, (itemHeight) {
41+
var height = MediaQuery.of(context).size.height;
42+
LogD("Height>>>>>>>>>>>$itemHeight>>>>>>>>>>$height");
43+
}));
3544
},
3645
itemCount: controller.dataList.length)
3746
],
@@ -97,6 +106,8 @@ class TopicDetailPage extends BaseStatelessWidget<TopicDetailController> {
97106
}
98107

99108
class TopicDetailController extends BaseController<ApiService> {
109+
final FijkPlayer player = FijkPlayer();
110+
RxInt playingIndex = (-1).obs;
100111
String id = Get.parameters["id"] ?? "";
101112
RxString title = "".obs;
102113
RxString headImg = "".obs;
@@ -107,9 +118,20 @@ class TopicDetailController extends BaseController<ApiService> {
107118
@override
108119
void onReady() {
109120
super.onReady();
121+
player.addListener(_playerStateChanged);
110122
loadNet();
111123
}
112124

125+
void _playerStateChanged() {
126+
FijkValue value = player.value;
127+
// LogWTF("播放器状态>>>>>>>>>$value");
128+
bool hasStoped =
129+
(value.state == FijkState.completed || value.state == FijkState.error);
130+
if (hasStoped) {
131+
playingIndex.value = -1;
132+
}
133+
}
134+
113135
@override
114136
void loadNet() {
115137
queryTopicDetailData();
@@ -125,6 +147,13 @@ class TopicDetailController extends BaseController<ApiService> {
125147
title.value = value.brief ?? "";
126148
});
127149
}
150+
151+
@override
152+
void onClose() {
153+
super.onClose();
154+
player.removeListener(_playerStateChanged);
155+
player.release();
156+
}
128157
}
129158

130159
class TopicDetailBinding extends Bindings {
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,119 @@
1+
import 'package:cached_network_image/cached_network_image.dart';
2+
import 'package:fijkplayer/fijkplayer.dart';
13
import 'package:flutter/cupertino.dart';
24
import 'package:flutter_screenutil/flutter_screenutil.dart';
5+
import 'package:get/get.dart';
36
import 'package:open_eye/base/pageWidget/common_stateless_widget.dart';
47
import 'package:open_eye/business/topic_detail/model/Topic_ItemList.dart';
58
import 'package:open_eye/res/colors.dart';
69
import 'package:open_eye/res/style.dart';
10+
import 'package:open_eye/utils/log_utils.dart';
711
import 'package:open_eye/widget/base_network_image.dart';
12+
import 'package:open_eye/widget/fijkplayer_skin/simple_panel_skin.dart';
13+
import 'package:visibility_detector/visibility_detector.dart';
814

915
// ignore: must_be_immutable
1016
class ItemTopicDetailWidget extends CommonStatelessWidget {
1117
TopicItemList itemData;
18+
FijkPlayer player;
19+
int itemIndex; //条目Index
20+
RxInt playIndex; //正在播放的index
21+
ValueChanged callback;
1222

13-
ItemTopicDetailWidget(this.itemData, {Key? key}) : super(key: key);
23+
ItemTopicDetailWidget(
24+
this.itemData, this.player, this.itemIndex, this.playIndex, this.callback,
25+
{Key? key})
26+
: super(key: key);
1427

1528
@override
1629
Widget build(BuildContext context) {
17-
return GestureDetector(child: _createContent(), onTap: () {});
30+
return _createContent(context);
1831
}
1932

20-
Widget _createContent() {
21-
return Column(
22-
crossAxisAlignment: CrossAxisAlignment.center,
23-
children: [
24-
BaseNetworkImage(
25-
itemData.data?.content?.data?.cover?.feed ?? "",
26-
height: 400.w,
27-
width: double.infinity,
28-
fit: BoxFit.cover,
29-
),
30-
Box.hBox20,
31-
Text(
32-
itemData.data?.content?.data?.title ?? "",
33-
style: TextStyle(fontSize: 26.sp, color: ColorStyle.color_black),
34-
),
35-
Container(
36-
color: ColorStyle.color_white,
37-
padding: EdgeInsets.only(
38-
left: 32.w, right: 32.w, bottom: 15.w, top: 15.w),
39-
child: Text(
40-
itemData.data?.content?.data?.description ?? "",
41-
style:
42-
TextStyle(fontSize: 22.sp, color: ColorStyle.color_666666),
33+
Widget _createContent(BuildContext context) {
34+
return Obx(() => VisibilityDetector(
35+
child: Column(
36+
crossAxisAlignment: CrossAxisAlignment.center,
37+
children: [
38+
Container(
39+
child: playIndex.value == itemIndex
40+
? FijkView(
41+
color: ColorStyle.color_black,
42+
height: 400.w,
43+
player: player,
44+
fit: FijkFit.cover,
45+
panelBuilder: (FijkPlayer player,
46+
FijkData data,
47+
BuildContext context,
48+
Size viewSize,
49+
Rect texturePos) {
50+
return simpleFijkPanelBuilder(
51+
player, data, context, viewSize, texturePos);
52+
},
53+
cover: CachedNetworkImageProvider(
54+
itemData.data?.content?.data?.cover?.feed ?? "",
55+
),
56+
)
57+
: Stack(alignment: Alignment.center, children: [
58+
BaseNetworkImage(
59+
itemData.data?.content?.data?.cover?.feed ?? "",
60+
height: 400.w,
61+
width: double.infinity,
62+
fit: BoxFit.cover,
63+
),
64+
GestureDetector(
65+
child: Image.asset(
66+
"assets/images/icon_play.png",
67+
height: 100.w,
68+
width: 100.w,
69+
),
70+
onTap: () async {
71+
var height = context.size?.height ?? 0;
72+
var height2 = context.height;
73+
var top = MediaQuery.of(context).viewPadding.top;
74+
LogD("高度>>>>>>$height>>>>>>$height2>>>>>>>>$top");
75+
callback(height);
76+
playIndex.value = itemIndex;
77+
await player.reset();
78+
await player.setDataSource(
79+
itemData.data?.content?.data?.playUrl ?? "",
80+
autoPlay: true);
81+
},
82+
),
83+
]),
84+
),
85+
Box.hBox20,
86+
Text(
87+
itemData.data?.content?.data?.title ?? "",
88+
style:
89+
TextStyle(fontSize: 26.sp, color: ColorStyle.color_black),
90+
),
91+
Container(
92+
color: ColorStyle.color_white,
93+
padding: EdgeInsets.only(
94+
left: 32.w, right: 32.w, bottom: 15.w, top: 15.w),
95+
child: Text(
96+
itemData.data?.content?.data?.description ?? "",
97+
style: TextStyle(
98+
fontSize: 22.sp, color: ColorStyle.color_666666),
99+
),
100+
)
101+
],
43102
),
44-
)
45-
],
46-
);
103+
onVisibilityChanged: (visibilityInfo) {
104+
if (visibilityInfo.visibleFraction == 0) {
105+
var key = visibilityInfo.key as ValueKey;
106+
if (playIndex.value.toString() == key.value) {
107+
LogWTF("滚动监听>>>>>>>>>>${playIndex.value.toString()}");
108+
var isLandscape = context.isLandscape;
109+
if (!isLandscape) {
110+
player.reset();
111+
playIndex.value = -1;
112+
}
113+
}
114+
}
115+
},
116+
key: Key(itemIndex.toString()),
117+
));
47118
}
48119
}

‎lib/route/routes.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ abstract class AppRoutes {
107107
///视频详情
108108
GetPage(
109109
name: AppRoutes.detailPage,
110-
page: () => DetailPage(),
110+
page: () => const DetailPage(),
111111
binding: DetailBinding(),
112112
// transitionDuration: const Duration(milliseconds: 500),
113113
// transition: Transition.native

‎lib/widget/fijkplayer_skin/fijkplayer_skin.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -1145,7 +1145,7 @@ class BuildGestureDetectorState extends State<BuildGestureDetector> {
11451145
child: Text(
11461146
mapKey + " X",
11471147
style: TextStyle(
1148-
color: _speed == speedVals ? Colors.blue : Colors.white,
1148+
color: _speed == speedVals ? ColorStyle.color_EA4C43 : Colors.white,
11491149
fontSize: 16,
11501150
),
11511151
),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
//MIT License
2+
//
3+
//Copyright (c) [2019] [Befovy]
4+
//
5+
//Permission is hereby granted, free of charge, to any person obtaining a copy
6+
//of this software and associated documentation files (the "Software"), to deal
7+
//in the Software without restriction, including without limitation the rights
8+
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
//copies of the Software, and to permit persons to whom the Software is
10+
//furnished to do so, subject to the following conditions:
11+
//
12+
//The above copyright notice and this permission notice shall be included in all
13+
//copies or substantial portions of the Software.
14+
//
15+
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
//SOFTWARE.
22+
23+
import 'dart:async';
24+
import 'dart:math';
25+
26+
import 'package:fijkplayer/fijkplayer.dart';
27+
import 'package:flutter/material.dart';
28+
import 'package:flutter_screenutil/flutter_screenutil.dart';
29+
import 'package:open_eye/utils/log_utils.dart';
30+
31+
/// Default builder generate default [FijkPanel] UI
32+
Widget simpleFijkPanelBuilder(FijkPlayer player, FijkData data,
33+
BuildContext context, Size viewSize, Rect texturePos) {
34+
return _DefaultFijkPanel(
35+
player: player,
36+
buildContext: context,
37+
viewSize: viewSize,
38+
texturePos: texturePos);
39+
}
40+
41+
/// Default Panel Widget
42+
class _DefaultFijkPanel extends StatefulWidget {
43+
final FijkPlayer player;
44+
final BuildContext buildContext;
45+
final Size viewSize;
46+
final Rect texturePos;
47+
48+
const _DefaultFijkPanel({
49+
required this.player,
50+
required this.buildContext,
51+
required this.viewSize,
52+
required this.texturePos,
53+
});
54+
55+
@override
56+
_DefaultFijkPanelState createState() => _DefaultFijkPanelState();
57+
}
58+
59+
String _duration2String(Duration duration) {
60+
if (duration.inMilliseconds < 0) return "-: negtive";
61+
62+
String twoDigits(int n) {
63+
if (n >= 10) return "$n";
64+
return "0$n";
65+
}
66+
67+
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
68+
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
69+
int inHours = duration.inHours;
70+
return inHours > 0
71+
? "$inHours:$twoDigitMinutes:$twoDigitSeconds"
72+
: "$twoDigitMinutes:$twoDigitSeconds";
73+
}
74+
75+
class _DefaultFijkPanelState extends State<_DefaultFijkPanel> {
76+
FijkPlayer get player => widget.player;
77+
78+
Duration _duration = const Duration();
79+
Duration _currentPos = const Duration();
80+
Duration _bufferPos = const Duration();
81+
82+
bool _playing = false;
83+
bool _prepared = false;
84+
String? _exception;
85+
86+
// bool _buffering = false;
87+
88+
double _seekPos = -1.0;
89+
90+
StreamSubscription? _currentPosSubs;
91+
92+
StreamSubscription? _bufferPosSubs;
93+
94+
//StreamSubscription _bufferingSubs;
95+
96+
Timer? _hideTimer;
97+
bool _hideStuff = true;
98+
99+
double _volume = 1.0;
100+
101+
final barHeight = 40.0;
102+
103+
@override
104+
void initState() {
105+
super.initState();
106+
107+
_duration = player.value.duration;
108+
_currentPos = player.currentPos;
109+
_bufferPos = player.bufferPos;
110+
_prepared = player.state.index >= FijkState.prepared.index;
111+
_playing = player.state == FijkState.started;
112+
_exception = player.value.exception.message;
113+
// _buffering = player.isBuffering;
114+
115+
player.addListener(_playerValueChanged);
116+
117+
_currentPosSubs = player.onCurrentPosUpdate.listen((v) {
118+
setState(() {
119+
_currentPos = v;
120+
});
121+
});
122+
123+
_bufferPosSubs = player.onBufferPosUpdate.listen((v) {
124+
setState(() {
125+
_bufferPos = v;
126+
});
127+
});
128+
}
129+
130+
void _playerValueChanged() {
131+
FijkValue value = player.value;
132+
if (value.duration != _duration) {
133+
setState(() {
134+
_duration = value.duration;
135+
});
136+
}
137+
138+
bool playing = (value.state == FijkState.started);
139+
bool prepared = value.prepared;
140+
String? exception = value.exception.message;
141+
if (playing != _playing ||
142+
prepared != _prepared ||
143+
exception != _exception) {
144+
setState(() {
145+
_playing = playing;
146+
_prepared = prepared;
147+
_exception = exception;
148+
});
149+
}
150+
}
151+
152+
void _playOrPause() {
153+
if (_playing == true) {
154+
player.pause();
155+
} else {
156+
player.start();
157+
}
158+
}
159+
160+
@override
161+
void dispose() {
162+
super.dispose();
163+
_hideTimer?.cancel();
164+
165+
player.removeListener(_playerValueChanged);
166+
_currentPosSubs?.cancel();
167+
_bufferPosSubs?.cancel();
168+
}
169+
170+
void _startHideTimer() {
171+
_hideTimer?.cancel();
172+
_hideTimer = Timer(const Duration(seconds: 3), () {
173+
setState(() {
174+
_hideStuff = true;
175+
});
176+
});
177+
}
178+
179+
void _cancelAndRestartTimer() {
180+
if (_hideStuff == true) {
181+
_startHideTimer();
182+
}
183+
setState(() {
184+
_hideStuff = !_hideStuff;
185+
});
186+
}
187+
188+
Widget _buildVolumeButton() {
189+
IconData iconData;
190+
if (_volume <= 0) {
191+
iconData = Icons.volume_off;
192+
} else {
193+
iconData = Icons.volume_up;
194+
}
195+
return IconButton(
196+
icon: Icon(iconData),
197+
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
198+
onPressed: () {
199+
setState(() {
200+
_volume = _volume > 0 ? 0.0 : 1.0;
201+
player.setVolume(_volume);
202+
});
203+
},
204+
);
205+
}
206+
207+
AnimatedOpacity _buildBottomBar(BuildContext context) {
208+
double duration = _duration.inMilliseconds.toDouble();
209+
double currentValue =
210+
_seekPos > 0 ? _seekPos : _currentPos.inMilliseconds.toDouble();
211+
currentValue = min(currentValue, duration);
212+
currentValue = max(currentValue, 0);
213+
return AnimatedOpacity(
214+
opacity: _hideStuff ? 0.0 : 0.8,
215+
duration: const Duration(milliseconds: 400),
216+
child: Container(
217+
height: barHeight,
218+
color: Theme.of(context).dialogBackgroundColor,
219+
child: Row(
220+
children: <Widget>[
221+
_buildVolumeButton(),
222+
Padding(
223+
padding: const EdgeInsets.only(right: 5.0, left: 5),
224+
child: Text(
225+
_duration2String(_currentPos),
226+
style: const TextStyle(fontSize: 14.0),
227+
),
228+
),
229+
230+
_duration.inMilliseconds == 0
231+
? const Expanded(child: Center())
232+
: Expanded(
233+
child: Padding(
234+
padding: const EdgeInsets.only(right: 0, left: 0),
235+
child: FijkSlider(
236+
value: currentValue,
237+
cacheValue: _bufferPos.inMilliseconds.toDouble(),
238+
min: 0.0,
239+
max: duration,
240+
onChanged: (v) {
241+
_startHideTimer();
242+
setState(() {
243+
_seekPos = v;
244+
});
245+
},
246+
onChangeEnd: (v) {
247+
setState(() {
248+
player.seekTo(v.toInt());
249+
LogD("seek to $v");
250+
_currentPos =
251+
Duration(milliseconds: _seekPos.toInt());
252+
_seekPos = -1;
253+
});
254+
},
255+
),
256+
),
257+
),
258+
259+
// duration / position
260+
_duration.inMilliseconds == 0
261+
? const Text("LIVE")
262+
: Padding(
263+
padding: const EdgeInsets.only(right: 5.0, left: 5),
264+
child: Text(
265+
_duration2String(_duration),
266+
style: const TextStyle(
267+
fontSize: 14.0,
268+
),
269+
),
270+
),
271+
272+
IconButton(
273+
icon: Icon(
274+
widget.player.value.fullScreen
275+
? Icons.fullscreen_exit
276+
: Icons.fullscreen,
277+
size: 40.w,
278+
),
279+
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
280+
// color: Colors.transparent,
281+
onPressed: () {
282+
widget.player.value.fullScreen
283+
? player.exitFullScreen()
284+
: player.enterFullScreen();
285+
},
286+
)
287+
//
288+
],
289+
),
290+
),
291+
);
292+
}
293+
294+
@override
295+
Widget build(BuildContext context) {
296+
Rect rect = player.value.fullScreen
297+
? Rect.fromLTWH(0, 0, widget.viewSize.width, widget.viewSize.height)
298+
: Rect.fromLTRB(
299+
max(0.0, widget.texturePos.left),
300+
max(0.0, widget.texturePos.top),
301+
min(widget.viewSize.width, widget.texturePos.right),
302+
min(widget.viewSize.height, widget.texturePos.bottom));
303+
return Positioned.fromRect(
304+
rect: rect,
305+
child: GestureDetector(
306+
onTap: _cancelAndRestartTimer,
307+
child: AbsorbPointer(
308+
absorbing: _hideStuff,
309+
child: Column(
310+
children: <Widget>[
311+
Container(height: barHeight),
312+
Expanded(
313+
child: GestureDetector(
314+
onTap: () {
315+
_cancelAndRestartTimer();
316+
},
317+
child: Container(
318+
color: Colors.transparent,
319+
height: double.infinity,
320+
width: double.infinity,
321+
child: Center(
322+
child: _exception != null
323+
? Text(
324+
_exception!,
325+
style: const TextStyle(
326+
color: Colors.white,
327+
fontSize: 25,
328+
),
329+
)
330+
: (_prepared ||
331+
player.state == FijkState.initialized)
332+
? AnimatedOpacity(
333+
opacity: _hideStuff ? 0.0 : 0.7,
334+
duration: const Duration(milliseconds: 400),
335+
child: IconButton(
336+
iconSize: 80.w,
337+
icon: _playing
338+
? const ImageIcon(
339+
AssetImage(
340+
"assets/images/icon_pause.png"),
341+
color: Colors.white,
342+
)
343+
: const ImageIcon(
344+
AssetImage(
345+
"assets/images/icon_play.png"),
346+
color: Colors.white),
347+
// Icon(
348+
// _playing
349+
// ? Icons.pause
350+
// : Icons.play_arrow,
351+
// color: Colors.white),
352+
padding: const EdgeInsets.only(
353+
left: 10.0, right: 10.0),
354+
onPressed: _playOrPause))
355+
: SizedBox(
356+
width: 70.w,
357+
height: 70.w,
358+
child: const CircularProgressIndicator(
359+
valueColor: AlwaysStoppedAnimation(
360+
Colors.white)),
361+
)),
362+
),
363+
),
364+
),
365+
_buildBottomBar(context),
366+
],
367+
),
368+
),
369+
),
370+
);
371+
}
372+
}

‎pubspec.lock

+7
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,13 @@ packages:
11131113
url: "https://pub.flutter-io.cn"
11141114
source: hosted
11151115
version: "2.0.7"
1116+
visibility_detector:
1117+
dependency: "direct main"
1118+
description:
1119+
name: visibility_detector
1120+
url: "https://pub.flutter-io.cn"
1121+
source: hosted
1122+
version: "0.2.2"
11161123
vm_service:
11171124
dependency: transitive
11181125
description:

‎pubspec.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ dependencies:
9393
card_swiper: ^1.0.4
9494
# 播放器
9595
fijkplayer: ^0.10.1
96+
#Widget是否可见判断
97+
visibility_detector: ^0.2.2
98+
9699

97100
dev_dependencies:
98101
flutter_test:

0 commit comments

Comments
 (0)
Please sign in to comment.