Hibok
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

757 rader
20 KiB

  1. import 'dart:async';
  2. import 'package:chat/data/constants.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/services.dart';
  6. import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
  7. import 'package:flutter_ijkplayer/src/helper/full_screen_helper.dart';
  8. import 'package:flutter_ijkplayer/src/helper/logutil.dart';
  9. import 'package:flutter_ijkplayer/src/helper/time_helper.dart';
  10. import 'package:flutter_ijkplayer/src/helper/ui_helper.dart';
  11. import 'package:flutter_ijkplayer/src/route/fullscreen_route.dart';
  12. import 'package:flutter_ijkplayer/src/widget/progress_bar.dart';
  13. part 'full_screen.part.dart';
  14. /// Using mediaController to Construct a Controller UI
  15. typedef Widget IJKControllerWidgetBuilder(IjkMediaController controller);
  16. /// default create IJK Controller UI
  17. Widget defaultBuildIjkControllerWidget(IjkMediaController controller) {
  18. return MyDefaultIJKControllerWidget(
  19. controller: controller,
  20. fullscreenControllerWidgetBuilder: (ctl) =>
  21. buildFullscreenMediaController(ctl),
  22. );
  23. }
  24. /// Default Controller Widget
  25. ///
  26. /// see [IjkPlayer] and [IJKControllerWidgetBuilder]
  27. class MyDefaultIJKControllerWidget extends StatefulWidget {
  28. final IjkMediaController controller;
  29. /// If [doubleTapPlay] is true, can double tap to play or pause media.
  30. final bool doubleTapPlay;
  31. /// If [verticalGesture] is false, vertical gesture will be ignored.
  32. final bool verticalGesture;
  33. /// If [horizontalGesture] is false, horizontal gesture will be ignored.
  34. final bool horizontalGesture;
  35. /// Controlling [verticalGesture] is controlling system volume or media volume.
  36. final VolumeType volumeType;
  37. final bool playWillPauseOther;
  38. /// Control whether there is a full-screen button.
  39. final bool showFullScreenButton;
  40. /// The current full-screen button style should not be changed by users.
  41. final bool currentFullScreenState;
  42. final IJKControllerWidgetBuilder fullscreenControllerWidgetBuilder;
  43. /// See [FullScreenType]
  44. final FullScreenType fullScreenType;
  45. ///自定义全屏布局
  46. final Widget myFullScreenWidget;
  47. final bool hideBackButton;
  48. /// The UI of the controller.
  49. const MyDefaultIJKControllerWidget({
  50. Key key,
  51. @required this.controller,
  52. this.doubleTapPlay = false,
  53. this.verticalGesture = true,
  54. this.horizontalGesture = true,
  55. this.volumeType = VolumeType.system,
  56. this.playWillPauseOther = true,
  57. this.currentFullScreenState = false,
  58. this.showFullScreenButton = true,
  59. this.fullscreenControllerWidgetBuilder,
  60. this.fullScreenType = FullScreenType.rotateBox,
  61. this.myFullScreenWidget,
  62. this.hideBackButton=false
  63. }) : super(key: key);
  64. @override
  65. _MyDefaultIJKControllerWidgetState createState() =>
  66. _MyDefaultIJKControllerWidgetState();
  67. MyDefaultIJKControllerWidget copyWith({
  68. Key key,
  69. IjkMediaController controller,
  70. bool doubleTapPlay,
  71. bool verticalGesture,
  72. bool horizontalGesture,
  73. VolumeType volumeType,
  74. bool playWillPauseOther,
  75. bool currentFullScreenState,
  76. bool showFullScreenButton,
  77. IJKControllerWidgetBuilder fullscreenControllerWidgetBuilder,
  78. FullScreenType fullScreenType,
  79. }) {
  80. return MyDefaultIJKControllerWidget(
  81. controller: controller ?? this.controller,
  82. doubleTapPlay: doubleTapPlay ?? this.doubleTapPlay,
  83. fullscreenControllerWidgetBuilder: fullscreenControllerWidgetBuilder ??
  84. this.fullscreenControllerWidgetBuilder,
  85. horizontalGesture: horizontalGesture ?? this.horizontalGesture,
  86. currentFullScreenState:
  87. currentFullScreenState ?? this.currentFullScreenState,
  88. key: key,
  89. volumeType: volumeType ?? this.volumeType,
  90. playWillPauseOther: playWillPauseOther ?? this.playWillPauseOther,
  91. showFullScreenButton: showFullScreenButton ?? this.showFullScreenButton,
  92. verticalGesture: verticalGesture ?? this.verticalGesture,
  93. fullScreenType: fullScreenType ?? this.fullScreenType,
  94. );
  95. }
  96. }
  97. class _MyDefaultIJKControllerWidgetState
  98. extends State<MyDefaultIJKControllerWidget> implements TooltipDelegate {
  99. IjkMediaController get controller => widget.controller;
  100. GlobalKey currentKey = GlobalKey();
  101. bool _isShow = false;
  102. set isShow(bool value) {
  103. _isShow = value;
  104. setState(() {});
  105. if (value == true) {
  106. controller.refreshVideoInfo();
  107. }
  108. }
  109. bool get isShow => _isShow;
  110. Timer progressTimer;
  111. StreamSubscription controllerSubscription;
  112. @override
  113. void initState() {
  114. super.initState();
  115. startTimer();
  116. controllerSubscription =
  117. controller.textureIdStream.listen(_onTextureIdChange);
  118. }
  119. void _onTextureIdChange(int textureId) {
  120. LogUtils.debug("onTextureChange $textureId");
  121. if (textureId != null) {
  122. startTimer();
  123. } else {
  124. stopTimer();
  125. }
  126. }
  127. @override
  128. void deactivate() {
  129. super.deactivate();
  130. }
  131. @override
  132. void dispose() {
  133. controllerSubscription.cancel();
  134. stopTimer();
  135. IjkManager.resetBrightness();
  136. super.dispose();
  137. }
  138. void startTimer() {
  139. if (controller.textureId == null) {
  140. return;
  141. }
  142. progressTimer?.cancel();
  143. progressTimer = Timer.periodic(Duration(milliseconds: 350), (timer) {
  144. LogUtils.verbose("timer will call refresh info");
  145. controller.refreshVideoInfo();
  146. });
  147. }
  148. void stopTimer() {
  149. progressTimer?.cancel();
  150. }
  151. @override
  152. Widget build(BuildContext context) {
  153. return GestureDetector(
  154. behavior: HitTestBehavior.opaque,
  155. child: buildContent(),
  156. onDoubleTap: onDoubleTap(),
  157. onHorizontalDragStart: wrapHorizontalGesture(_onHorizontalDragStart),
  158. onHorizontalDragUpdate: wrapHorizontalGesture(_onHorizontalDragUpdate),
  159. onHorizontalDragEnd: wrapHorizontalGesture(_onHorizontalDragEnd),
  160. onVerticalDragStart: wrapVerticalGesture(_onVerticalDragStart),
  161. onVerticalDragUpdate: wrapVerticalGesture(_onVerticalDragUpdate),
  162. onVerticalDragEnd: wrapVerticalGesture(_onVerticalDragEnd),
  163. onTap: onTap,
  164. key: currentKey,
  165. );
  166. }
  167. Widget buildContent() {
  168. if (!isShow) {
  169. return Container(child: GestureDetector(onTap: (){
  170. onTap();
  171. },child: Container(child: widget.myFullScreenWidget,),),);
  172. }
  173. return StreamBuilder<VideoInfo>(
  174. stream: controller.videoInfoStream,
  175. builder: (context, snapshot) {
  176. var info = snapshot.data;
  177. if (info == null || !info.hasData) {
  178. return Container();
  179. }
  180. return buildPortrait(info);
  181. },
  182. );
  183. }
  184. Widget _buildFullScreenButton() {
  185. if (widget.showFullScreenButton != true) {
  186. return Container();
  187. }
  188. var isFull = widget.currentFullScreenState;
  189. IJKControllerWidgetBuilder fullscreenBuilder =
  190. widget.fullscreenControllerWidgetBuilder ??
  191. (ctx) => widget.copyWith(currentFullScreenState: true);
  192. return IconButton(
  193. color: Colors.white,
  194. icon: Icon(isFull ? Icons.fullscreen_exit : Icons.fullscreen),
  195. onPressed: () {
  196. if (isFull) {
  197. Navigator.pop(context);
  198. } else {
  199. showFullScreenIJKPlayer(
  200. context,
  201. controller,
  202. fullscreenControllerWidgetBuilder: fullscreenBuilder,
  203. fullScreenType: widget.fullScreenType,
  204. );
  205. }
  206. },
  207. );
  208. }
  209. int _overlayTurns = 0;
  210. Widget buildPortrait(VideoInfo info) {
  211. _overlayTurns = FullScreenHelper.getQuarterTurns(info, context);
  212. return PortraitController(
  213. controller: controller,
  214. myFullScreenWidget: widget.myFullScreenWidget,
  215. info: info,
  216. tooltipDelegate: this,
  217. playWillPauseOther: widget.playWillPauseOther,
  218. fullScreenWidget: _buildFullScreenButton(),
  219. hideBackButton: widget.hideBackButton,
  220. );
  221. }
  222. OverlayEntry _tipOverlay;
  223. Widget createTooltipWidgetWrapper(Widget widget) {
  224. var typography = Typography(platform: TargetPlatform.android);
  225. var theme = typography.white;
  226. const style = const TextStyle(
  227. fontSize: 15.0,
  228. color: Colors.white,
  229. fontWeight: FontWeight.normal,
  230. );
  231. var mergedTextStyle = theme.body2.merge(style);
  232. return Container(
  233. decoration: BoxDecoration(
  234. color: Colors.black.withOpacity(0.5),
  235. borderRadius: BorderRadius.circular(20.0),
  236. ),
  237. height: 100.0,
  238. width: 100.0,
  239. child: DefaultTextStyle(
  240. child: widget,
  241. style: mergedTextStyle,
  242. ),
  243. );
  244. }
  245. void showTooltip(Widget widget) {
  246. hideTooltip();
  247. _tipOverlay = OverlayEntry(
  248. builder: (BuildContext context) {
  249. Widget w = IgnorePointer(
  250. child: Center(
  251. child: widget,
  252. ),
  253. );
  254. if (this.widget.fullScreenType == FullScreenType.rotateBox &&
  255. this.widget.currentFullScreenState &&
  256. _overlayTurns != 0) {
  257. w = RotatedBox(
  258. child: w,
  259. quarterTurns: _overlayTurns,
  260. );
  261. }
  262. return w;
  263. },
  264. );
  265. Overlay.of(context).insert(_tipOverlay);
  266. }
  267. void hideTooltip() {
  268. _tipOverlay?.remove();
  269. _tipOverlay = null;
  270. }
  271. _ProgressCalculator _calculator;
  272. onTap() => isShow = !isShow;
  273. Function onDoubleTap() {
  274. return widget.doubleTapPlay
  275. ? () {
  276. LogUtils.debug("ondouble tap");
  277. controller.playOrPause();
  278. }
  279. : null;
  280. }
  281. Function wrapHorizontalGesture(Function function) =>
  282. widget.horizontalGesture == true ? function : null;
  283. Function wrapVerticalGesture(Function function) =>
  284. widget.verticalGesture == true ? function : null;
  285. void _onHorizontalDragStart(DragStartDetails details) async {
  286. var videoInfo = await controller.getVideoInfo();
  287. _calculator = _ProgressCalculator(details, videoInfo);
  288. }
  289. void _onHorizontalDragUpdate(DragUpdateDetails details) {
  290. if (_calculator == null || details == null) {
  291. return;
  292. }
  293. var updateText = _calculator.calcUpdate(details);
  294. var offsetPosition = _calculator.getOffsetPosition();
  295. IconData iconData =
  296. offsetPosition > 0 ? Icons.fast_forward : Icons.fast_rewind;
  297. var w = Column(
  298. mainAxisAlignment: MainAxisAlignment.center,
  299. children: <Widget>[
  300. Icon(
  301. iconData,
  302. color: Colors.white,
  303. size: 40.0,
  304. ),
  305. Text(
  306. updateText,
  307. textAlign: TextAlign.center,
  308. ),
  309. ],
  310. );
  311. showTooltip(createTooltipWidgetWrapper(w));
  312. }
  313. void _onHorizontalDragEnd(DragEndDetails details) async {
  314. hideTooltip();
  315. var targetSeek = _calculator?.getTargetSeek(details);
  316. _calculator = null;
  317. if (targetSeek == null) {
  318. return;
  319. }
  320. await controller.seekTo(targetSeek);
  321. var videoInfo = await controller.getVideoInfo();
  322. if (targetSeek < videoInfo.duration) await controller.play();
  323. }
  324. bool verticalDragging = false;
  325. bool leftVerticalDrag;
  326. void _onVerticalDragStart(DragStartDetails details) {
  327. verticalDragging = true;
  328. var width = UIHelper.findGlobalRect(currentKey).width;
  329. var dx =
  330. UIHelper.globalOffsetToLocal(currentKey, details.globalPosition).dx;
  331. leftVerticalDrag = dx / width <= 0.5;
  332. }
  333. void _onVerticalDragUpdate(DragUpdateDetails details) async {
  334. if (verticalDragging == false) return;
  335. String text = "";
  336. IconData iconData = Icons.volume_up;
  337. if (leftVerticalDrag == false) {
  338. if (details.delta.dy > 0) {
  339. await volumeDown();
  340. } else if (details.delta.dy < 0) {
  341. await volumeUp();
  342. }
  343. var currentVolume = await getVolume();
  344. if (currentVolume <= 0) {
  345. iconData = Icons.volume_mute;
  346. } else if (currentVolume < 50) {
  347. iconData = Icons.volume_down;
  348. } else {
  349. iconData = Icons.volume_up;
  350. }
  351. text = currentVolume.toString();
  352. } else if (leftVerticalDrag == true) {
  353. var currentBright = await IjkManager.getSystemBrightness();
  354. double target;
  355. if (details.delta.dy > 0) {
  356. target = currentBright - 0.03;
  357. } else {
  358. target = currentBright + 0.03;
  359. }
  360. if (target > 1) {
  361. target = 1;
  362. } else if (target < 0) {
  363. target = 0;
  364. }
  365. await IjkManager.setSystemBrightness(target);
  366. if (target >= 0.66) {
  367. iconData = Icons.brightness_high;
  368. } else if (target < 0.66 && target > 0.33) {
  369. iconData = Icons.brightness_medium;
  370. } else {
  371. iconData = Icons.brightness_low;
  372. }
  373. text = (target * 100).toStringAsFixed(0);
  374. } else {
  375. return;
  376. }
  377. var column = Column(
  378. mainAxisAlignment: MainAxisAlignment.center,
  379. children: <Widget>[
  380. Icon(
  381. iconData,
  382. color: Colors.white,
  383. size: 25.0,
  384. ),
  385. Padding(
  386. padding: const EdgeInsets.only(top: 10.0),
  387. child: Text(text),
  388. ),
  389. ],
  390. );
  391. showTooltip(createTooltipWidgetWrapper(column));
  392. }
  393. void _onVerticalDragEnd(DragEndDetails details) async {
  394. verticalDragging = false;
  395. leftVerticalDrag = null;
  396. hideTooltip();
  397. Future.delayed(const Duration(milliseconds: 2000), () {
  398. hideTooltip();
  399. });
  400. }
  401. Future<int> getVolume() async {
  402. switch (widget.volumeType) {
  403. case VolumeType.media:
  404. return controller.volume;
  405. case VolumeType.system:
  406. return controller.getSystemVolume();
  407. }
  408. return 0;
  409. }
  410. Future<void> volumeUp() async {
  411. var volume = await getVolume();
  412. volume++;
  413. switch (widget.volumeType) {
  414. case VolumeType.media:
  415. controller.volume = volume;
  416. break;
  417. case VolumeType.system:
  418. await IjkManager.systemVolumeUp();
  419. break;
  420. }
  421. }
  422. Future<void> volumeDown() async {
  423. var volume = await getVolume();
  424. volume--;
  425. switch (widget.volumeType) {
  426. case VolumeType.media:
  427. controller.volume = volume;
  428. break;
  429. case VolumeType.system:
  430. await IjkManager.systemVolumeDown();
  431. break;
  432. }
  433. }
  434. }
  435. class _ProgressCalculator {
  436. DragStartDetails startDetails;
  437. VideoInfo info;
  438. double dx;
  439. _ProgressCalculator(this.startDetails, this.info);
  440. String calcUpdate(DragUpdateDetails details) {
  441. dx = details.globalPosition.dx - startDetails.globalPosition.dx;
  442. var f = dx > 0 ? "+" : "-";
  443. var offset = getOffsetPosition().round().abs();
  444. return "$f${offset}s";
  445. }
  446. double getTargetSeek(DragEndDetails details) {
  447. var target = info.currentPosition + getOffsetPosition();
  448. if (target < 0) {
  449. target = 0;
  450. } else if (target > info.duration) {
  451. target = info.duration;
  452. }
  453. return target;
  454. }
  455. double getOffsetPosition() {
  456. return dx / 10;
  457. }
  458. }
  459. class PortraitController extends StatelessWidget {
  460. final IjkMediaController controller;
  461. final VideoInfo info;
  462. final TooltipDelegate tooltipDelegate;
  463. final bool playWillPauseOther;
  464. final Widget fullScreenWidget;
  465. final Widget myFullScreenWidget;
  466. final bool hideBackButton;
  467. const PortraitController({
  468. Key key,
  469. this.controller,
  470. this.info,
  471. this.tooltipDelegate,
  472. this.playWillPauseOther = true,
  473. this.fullScreenWidget,
  474. this.myFullScreenWidget,
  475. this.hideBackButton
  476. }) : super(key: key);
  477. bool get haveTime {
  478. return info.hasData && info.duration > 0;
  479. }
  480. @override
  481. Widget build(BuildContext context) {
  482. if (!info.hasData) {
  483. return Container();
  484. }
  485. ///播放器定制页面---
  486. Widget bottomBar = buildBottomBar(context);
  487. return Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[
  488. Offstage(offstage: hideBackButton,child: InkWell(
  489. onTap: () {
  490. Navigator.pop(context);
  491. },
  492. child: Container(padding: EdgeInsets.only(top: 10),width: 80,height: 80,child: Icon(
  493. IconData(0xe615, fontFamily: Constants.IconFontFamily),
  494. size: 35,
  495. color: Color(0xFFeeeeee),
  496. ),),
  497. ),),
  498. Expanded(
  499. child: Container(),
  500. ),
  501. bottomBar,
  502. ],);
  503. // return Stack(children: <Widget>[
  504. //
  505. // Positioned.fill(child: Container(child: myFullScreenWidget,)),
  506. //
  507. // Positioned.fill(child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[
  508. // Container(
  509. // width: ScreenUtil().setWidth(80),
  510. // height: ScreenUtil().setWidth(80),
  511. // padding: EdgeInsets.all(ScreenUtil().setWidth(20)),
  512. // child: InkWell(
  513. // onTap: () {
  514. // Navigator.pop(context);
  515. // },
  516. // child: Image.asset('assets/images/img_player_back.png'),
  517. // ),
  518. // ),
  519. // Expanded(
  520. // child: Container(),
  521. // ),
  522. // bottomBar,
  523. // ],))
  524. //
  525. //
  526. // ],);
  527. }
  528. Widget buildBottomBar(BuildContext context) {
  529. var currentTime = buildCurrentText();
  530. var maxTime = buildMaxTimeText();
  531. var progress = buildProgress(info);
  532. var playButton = buildPlayButton(context);
  533. var fullScreenButton = buildFullScreenButton();
  534. Widget widget = Row(
  535. children: <Widget>[
  536. playButton,
  537. Padding(
  538. padding: const EdgeInsets.all(8.0),
  539. child: currentTime,
  540. ),
  541. Expanded(child: progress),
  542. Padding(
  543. padding: const EdgeInsets.all(8.0),
  544. child: maxTime,
  545. ),
  546. fullScreenButton,
  547. ],
  548. );
  549. widget = DefaultTextStyle(
  550. style: const TextStyle(
  551. color: Colors.white,
  552. ),
  553. child: widget,
  554. );
  555. widget = Container(
  556. color: Colors.black.withOpacity(0.12),
  557. child: widget,
  558. );
  559. return widget;
  560. }
  561. Widget buildProgress(VideoInfo info) {
  562. if (!info.hasData || info.duration == 0) {
  563. return Container();
  564. }
  565. return Container(
  566. height: 22,
  567. child: ProgressBar(
  568. current: info.currentPosition,
  569. max: info.duration,
  570. changeProgressHandler: (progress) async {
  571. await controller.seekToProgress(progress);
  572. tooltipDelegate?.hideTooltip();
  573. },
  574. tapProgressHandler: (progress) {
  575. showProgressTooltip(info, progress);
  576. },
  577. ),
  578. );
  579. }
  580. buildCurrentText() {
  581. return haveTime
  582. ? Text(
  583. TimeHelper.getTimeText(info.currentPosition),
  584. )
  585. : Container();
  586. }
  587. buildMaxTimeText() {
  588. return haveTime
  589. ? Text(
  590. TimeHelper.getTimeText(info.duration),
  591. )
  592. : Container();
  593. }
  594. buildPlayButton(BuildContext context) {
  595. return IconButton(
  596. onPressed: () {
  597. controller.playOrPause(pauseOther: playWillPauseOther);
  598. },
  599. color: Colors.white,
  600. icon: Icon(info.isPlaying ? Icons.pause : Icons.play_arrow),
  601. iconSize: 25.0,
  602. );
  603. }
  604. void showProgressTooltip(VideoInfo info, double progress) {
  605. var target = info.duration * progress;
  606. var diff = info.currentPosition - target;
  607. String diffString;
  608. if (diff < 1 && diff > -1) {
  609. diffString = "0s";
  610. } else if (diff < 0) {
  611. diffString = "+${TimeHelper.getTimeText(diff.abs())}";
  612. } else if (diff > 0) {
  613. diffString = "-${TimeHelper.getTimeText(diff.abs())}";
  614. } else {
  615. diffString = "0s";
  616. }
  617. Widget text = Container(
  618. alignment: Alignment.center,
  619. child: Column(
  620. mainAxisAlignment: MainAxisAlignment.center,
  621. children: <Widget>[
  622. Text(
  623. TimeHelper.getTimeText(target),
  624. style: TextStyle(fontSize: 20),
  625. ),
  626. Container(
  627. height: 10,
  628. ),
  629. Text(diffString),
  630. ],
  631. ),
  632. );
  633. var tooltip = tooltipDelegate?.createTooltipWidgetWrapper(text);
  634. tooltipDelegate?.showTooltip(tooltip);
  635. }
  636. Widget buildFullScreenButton() {
  637. return fullScreenWidget ?? Container();
  638. }
  639. }
  640. abstract class TooltipDelegate {
  641. void showTooltip(Widget widget);
  642. Widget createTooltipWidgetWrapper(Widget widget);
  643. void hideTooltip();
  644. }
  645. enum VolumeType {
  646. system,
  647. media,
  648. }