import 'dart:async'; import 'package:chat/data/constants.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_ijkplayer/flutter_ijkplayer.dart'; import 'package:flutter_ijkplayer/src/helper/full_screen_helper.dart'; import 'package:flutter_ijkplayer/src/helper/logutil.dart'; import 'package:flutter_ijkplayer/src/helper/time_helper.dart'; import 'package:flutter_ijkplayer/src/helper/ui_helper.dart'; import 'package:flutter_ijkplayer/src/route/fullscreen_route.dart'; import 'package:flutter_ijkplayer/src/widget/progress_bar.dart'; part 'full_screen.part.dart'; /// Using mediaController to Construct a Controller UI typedef Widget IJKControllerWidgetBuilder(IjkMediaController controller); /// default create IJK Controller UI Widget defaultBuildIjkControllerWidget(IjkMediaController controller) { return MyDefaultIJKControllerWidget( controller: controller, fullscreenControllerWidgetBuilder: (ctl) => buildFullscreenMediaController(ctl), ); } /// Default Controller Widget /// /// see [IjkPlayer] and [IJKControllerWidgetBuilder] class MyDefaultIJKControllerWidget extends StatefulWidget { final IjkMediaController controller; /// If [doubleTapPlay] is true, can double tap to play or pause media. final bool doubleTapPlay; /// If [verticalGesture] is false, vertical gesture will be ignored. final bool verticalGesture; /// If [horizontalGesture] is false, horizontal gesture will be ignored. final bool horizontalGesture; /// Controlling [verticalGesture] is controlling system volume or media volume. final VolumeType volumeType; final bool playWillPauseOther; /// Control whether there is a full-screen button. final bool showFullScreenButton; /// The current full-screen button style should not be changed by users. final bool currentFullScreenState; final IJKControllerWidgetBuilder fullscreenControllerWidgetBuilder; /// See [FullScreenType] final FullScreenType fullScreenType; ///自定义全屏布局 final Widget myFullScreenWidget; final bool hideBackButton; /// The UI of the controller. const MyDefaultIJKControllerWidget({ Key key, @required this.controller, this.doubleTapPlay = false, this.verticalGesture = true, this.horizontalGesture = true, this.volumeType = VolumeType.system, this.playWillPauseOther = true, this.currentFullScreenState = false, this.showFullScreenButton = true, this.fullscreenControllerWidgetBuilder, this.fullScreenType = FullScreenType.rotateBox, this.myFullScreenWidget, this.hideBackButton=false }) : super(key: key); @override _MyDefaultIJKControllerWidgetState createState() => _MyDefaultIJKControllerWidgetState(); MyDefaultIJKControllerWidget copyWith({ Key key, IjkMediaController controller, bool doubleTapPlay, bool verticalGesture, bool horizontalGesture, VolumeType volumeType, bool playWillPauseOther, bool currentFullScreenState, bool showFullScreenButton, IJKControllerWidgetBuilder fullscreenControllerWidgetBuilder, FullScreenType fullScreenType, }) { return MyDefaultIJKControllerWidget( controller: controller ?? this.controller, doubleTapPlay: doubleTapPlay ?? this.doubleTapPlay, fullscreenControllerWidgetBuilder: fullscreenControllerWidgetBuilder ?? this.fullscreenControllerWidgetBuilder, horizontalGesture: horizontalGesture ?? this.horizontalGesture, currentFullScreenState: currentFullScreenState ?? this.currentFullScreenState, key: key, volumeType: volumeType ?? this.volumeType, playWillPauseOther: playWillPauseOther ?? this.playWillPauseOther, showFullScreenButton: showFullScreenButton ?? this.showFullScreenButton, verticalGesture: verticalGesture ?? this.verticalGesture, fullScreenType: fullScreenType ?? this.fullScreenType, ); } } class _MyDefaultIJKControllerWidgetState extends State implements TooltipDelegate { IjkMediaController get controller => widget.controller; GlobalKey currentKey = GlobalKey(); bool _isShow = false; set isShow(bool value) { _isShow = value; setState(() {}); if (value == true) { controller.refreshVideoInfo(); } } bool get isShow => _isShow; Timer progressTimer; StreamSubscription controllerSubscription; @override void initState() { super.initState(); startTimer(); controllerSubscription = controller.textureIdStream.listen(_onTextureIdChange); } void _onTextureIdChange(int textureId) { LogUtils.debug("onTextureChange $textureId"); if (textureId != null) { startTimer(); } else { stopTimer(); } } @override void deactivate() { super.deactivate(); } @override void dispose() { controllerSubscription.cancel(); stopTimer(); IjkManager.resetBrightness(); super.dispose(); } void startTimer() { if (controller.textureId == null) { return; } progressTimer?.cancel(); progressTimer = Timer.periodic(Duration(milliseconds: 350), (timer) { LogUtils.verbose("timer will call refresh info"); controller.refreshVideoInfo(); }); } void stopTimer() { progressTimer?.cancel(); } @override Widget build(BuildContext context) { return GestureDetector( behavior: HitTestBehavior.opaque, child: buildContent(), onDoubleTap: onDoubleTap(), onHorizontalDragStart: wrapHorizontalGesture(_onHorizontalDragStart), onHorizontalDragUpdate: wrapHorizontalGesture(_onHorizontalDragUpdate), onHorizontalDragEnd: wrapHorizontalGesture(_onHorizontalDragEnd), onVerticalDragStart: wrapVerticalGesture(_onVerticalDragStart), onVerticalDragUpdate: wrapVerticalGesture(_onVerticalDragUpdate), onVerticalDragEnd: wrapVerticalGesture(_onVerticalDragEnd), onTap: onTap, key: currentKey, ); } Widget buildContent() { if (!isShow) { return Container(child: GestureDetector(onTap: (){ onTap(); },child: Container(child: widget.myFullScreenWidget,),),); } return StreamBuilder( stream: controller.videoInfoStream, builder: (context, snapshot) { var info = snapshot.data; if (info == null || !info.hasData) { return Container(); } return buildPortrait(info); }, ); } Widget _buildFullScreenButton() { if (widget.showFullScreenButton != true) { return Container(); } var isFull = widget.currentFullScreenState; IJKControllerWidgetBuilder fullscreenBuilder = widget.fullscreenControllerWidgetBuilder ?? (ctx) => widget.copyWith(currentFullScreenState: true); return IconButton( color: Colors.white, icon: Icon(isFull ? Icons.fullscreen_exit : Icons.fullscreen), onPressed: () { if (isFull) { Navigator.pop(context); } else { showFullScreenIJKPlayer( context, controller, fullscreenControllerWidgetBuilder: fullscreenBuilder, fullScreenType: widget.fullScreenType, ); } }, ); } int _overlayTurns = 0; Widget buildPortrait(VideoInfo info) { _overlayTurns = FullScreenHelper.getQuarterTurns(info, context); return PortraitController( controller: controller, myFullScreenWidget: widget.myFullScreenWidget, info: info, tooltipDelegate: this, playWillPauseOther: widget.playWillPauseOther, fullScreenWidget: _buildFullScreenButton(), hideBackButton: widget.hideBackButton, ); } OverlayEntry _tipOverlay; Widget createTooltipWidgetWrapper(Widget widget) { var typography = Typography(platform: TargetPlatform.android); var theme = typography.white; const style = const TextStyle( fontSize: 15.0, color: Colors.white, fontWeight: FontWeight.normal, ); var mergedTextStyle = theme.body2.merge(style); return Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(20.0), ), height: 100.0, width: 100.0, child: DefaultTextStyle( child: widget, style: mergedTextStyle, ), ); } void showTooltip(Widget widget) { hideTooltip(); _tipOverlay = OverlayEntry( builder: (BuildContext context) { Widget w = IgnorePointer( child: Center( child: widget, ), ); if (this.widget.fullScreenType == FullScreenType.rotateBox && this.widget.currentFullScreenState && _overlayTurns != 0) { w = RotatedBox( child: w, quarterTurns: _overlayTurns, ); } return w; }, ); Overlay.of(context).insert(_tipOverlay); } void hideTooltip() { _tipOverlay?.remove(); _tipOverlay = null; } _ProgressCalculator _calculator; onTap() => isShow = !isShow; Function onDoubleTap() { return widget.doubleTapPlay ? () { LogUtils.debug("ondouble tap"); controller.playOrPause(); } : null; } Function wrapHorizontalGesture(Function function) => widget.horizontalGesture == true ? function : null; Function wrapVerticalGesture(Function function) => widget.verticalGesture == true ? function : null; void _onHorizontalDragStart(DragStartDetails details) async { var videoInfo = await controller.getVideoInfo(); _calculator = _ProgressCalculator(details, videoInfo); } void _onHorizontalDragUpdate(DragUpdateDetails details) { if (_calculator == null || details == null) { return; } var updateText = _calculator.calcUpdate(details); var offsetPosition = _calculator.getOffsetPosition(); IconData iconData = offsetPosition > 0 ? Icons.fast_forward : Icons.fast_rewind; var w = Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( iconData, color: Colors.white, size: 40.0, ), Text( updateText, textAlign: TextAlign.center, ), ], ); showTooltip(createTooltipWidgetWrapper(w)); } void _onHorizontalDragEnd(DragEndDetails details) async { hideTooltip(); var targetSeek = _calculator?.getTargetSeek(details); _calculator = null; if (targetSeek == null) { return; } await controller.seekTo(targetSeek); var videoInfo = await controller.getVideoInfo(); if (targetSeek < videoInfo.duration) await controller.play(); } bool verticalDragging = false; bool leftVerticalDrag; void _onVerticalDragStart(DragStartDetails details) { verticalDragging = true; var width = UIHelper.findGlobalRect(currentKey).width; var dx = UIHelper.globalOffsetToLocal(currentKey, details.globalPosition).dx; leftVerticalDrag = dx / width <= 0.5; } void _onVerticalDragUpdate(DragUpdateDetails details) async { if (verticalDragging == false) return; String text = ""; IconData iconData = Icons.volume_up; if (leftVerticalDrag == false) { if (details.delta.dy > 0) { await volumeDown(); } else if (details.delta.dy < 0) { await volumeUp(); } var currentVolume = await getVolume(); if (currentVolume <= 0) { iconData = Icons.volume_mute; } else if (currentVolume < 50) { iconData = Icons.volume_down; } else { iconData = Icons.volume_up; } text = currentVolume.toString(); } else if (leftVerticalDrag == true) { var currentBright = await IjkManager.getSystemBrightness(); double target; if (details.delta.dy > 0) { target = currentBright - 0.03; } else { target = currentBright + 0.03; } if (target > 1) { target = 1; } else if (target < 0) { target = 0; } await IjkManager.setSystemBrightness(target); if (target >= 0.66) { iconData = Icons.brightness_high; } else if (target < 0.66 && target > 0.33) { iconData = Icons.brightness_medium; } else { iconData = Icons.brightness_low; } text = (target * 100).toStringAsFixed(0); } else { return; } var column = Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( iconData, color: Colors.white, size: 25.0, ), Padding( padding: const EdgeInsets.only(top: 10.0), child: Text(text), ), ], ); showTooltip(createTooltipWidgetWrapper(column)); } void _onVerticalDragEnd(DragEndDetails details) async { verticalDragging = false; leftVerticalDrag = null; hideTooltip(); Future.delayed(const Duration(milliseconds: 2000), () { hideTooltip(); }); } Future getVolume() async { switch (widget.volumeType) { case VolumeType.media: return controller.volume; case VolumeType.system: return controller.getSystemVolume(); } return 0; } Future volumeUp() async { var volume = await getVolume(); volume++; switch (widget.volumeType) { case VolumeType.media: controller.volume = volume; break; case VolumeType.system: await IjkManager.systemVolumeUp(); break; } } Future volumeDown() async { var volume = await getVolume(); volume--; switch (widget.volumeType) { case VolumeType.media: controller.volume = volume; break; case VolumeType.system: await IjkManager.systemVolumeDown(); break; } } } class _ProgressCalculator { DragStartDetails startDetails; VideoInfo info; double dx; _ProgressCalculator(this.startDetails, this.info); String calcUpdate(DragUpdateDetails details) { dx = details.globalPosition.dx - startDetails.globalPosition.dx; var f = dx > 0 ? "+" : "-"; var offset = getOffsetPosition().round().abs(); return "$f${offset}s"; } double getTargetSeek(DragEndDetails details) { var target = info.currentPosition + getOffsetPosition(); if (target < 0) { target = 0; } else if (target > info.duration) { target = info.duration; } return target; } double getOffsetPosition() { return dx / 10; } } class PortraitController extends StatelessWidget { final IjkMediaController controller; final VideoInfo info; final TooltipDelegate tooltipDelegate; final bool playWillPauseOther; final Widget fullScreenWidget; final Widget myFullScreenWidget; final bool hideBackButton; const PortraitController({ Key key, this.controller, this.info, this.tooltipDelegate, this.playWillPauseOther = true, this.fullScreenWidget, this.myFullScreenWidget, this.hideBackButton }) : super(key: key); bool get haveTime { return info.hasData && info.duration > 0; } @override Widget build(BuildContext context) { if (!info.hasData) { return Container(); } ///播放器定制页面--- Widget bottomBar = buildBottomBar(context); return Column(crossAxisAlignment: CrossAxisAlignment.start,children: [ Offstage(offstage: hideBackButton,child: InkWell( onTap: () { Navigator.pop(context); }, child: Container(padding: EdgeInsets.only(top: 10),width: 80,height: 80,child: Icon( IconData(0xe615, fontFamily: Constants.IconFontFamily), size: 35, color: Color(0xFFeeeeee), ),), ),), Expanded( child: Container(), ), bottomBar, ],); // return Stack(children: [ // // Positioned.fill(child: Container(child: myFullScreenWidget,)), // // Positioned.fill(child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [ // Container( // width: ScreenUtil().setWidth(80), // height: ScreenUtil().setWidth(80), // padding: EdgeInsets.all(ScreenUtil().setWidth(20)), // child: InkWell( // onTap: () { // Navigator.pop(context); // }, // child: Image.asset('assets/images/img_player_back.png'), // ), // ), // Expanded( // child: Container(), // ), // bottomBar, // ],)) // // // ],); } Widget buildBottomBar(BuildContext context) { var currentTime = buildCurrentText(); var maxTime = buildMaxTimeText(); var progress = buildProgress(info); var playButton = buildPlayButton(context); var fullScreenButton = buildFullScreenButton(); Widget widget = Row( children: [ playButton, Padding( padding: const EdgeInsets.all(8.0), child: currentTime, ), Expanded(child: progress), Padding( padding: const EdgeInsets.all(8.0), child: maxTime, ), fullScreenButton, ], ); widget = DefaultTextStyle( style: const TextStyle( color: Colors.white, ), child: widget, ); widget = Container( color: Colors.black.withOpacity(0.12), child: widget, ); return widget; } Widget buildProgress(VideoInfo info) { if (!info.hasData || info.duration == 0) { return Container(); } return Container( height: 22, child: ProgressBar( current: info.currentPosition, max: info.duration, changeProgressHandler: (progress) async { await controller.seekToProgress(progress); tooltipDelegate?.hideTooltip(); }, tapProgressHandler: (progress) { showProgressTooltip(info, progress); }, ), ); } buildCurrentText() { return haveTime ? Text( TimeHelper.getTimeText(info.currentPosition), ) : Container(); } buildMaxTimeText() { return haveTime ? Text( TimeHelper.getTimeText(info.duration), ) : Container(); } buildPlayButton(BuildContext context) { return IconButton( onPressed: () { controller.playOrPause(pauseOther: playWillPauseOther); }, color: Colors.white, icon: Icon(info.isPlaying ? Icons.pause : Icons.play_arrow), iconSize: 25.0, ); } void showProgressTooltip(VideoInfo info, double progress) { var target = info.duration * progress; var diff = info.currentPosition - target; String diffString; if (diff < 1 && diff > -1) { diffString = "0s"; } else if (diff < 0) { diffString = "+${TimeHelper.getTimeText(diff.abs())}"; } else if (diff > 0) { diffString = "-${TimeHelper.getTimeText(diff.abs())}"; } else { diffString = "0s"; } Widget text = Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( TimeHelper.getTimeText(target), style: TextStyle(fontSize: 20), ), Container( height: 10, ), Text(diffString), ], ), ); var tooltip = tooltipDelegate?.createTooltipWidgetWrapper(text); tooltipDelegate?.showTooltip(tooltip); } Widget buildFullScreenButton() { return fullScreenWidget ?? Container(); } } abstract class TooltipDelegate { void showTooltip(Widget widget); Widget createTooltipWidgetWrapper(Widget widget); void hideTooltip(); } enum VolumeType { system, media, }