import 'dart:async'; import 'dart:core'; import 'dart:math'; import 'dart:ui'; import 'dart:typed_data'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'my_fijkvalue.dart'; import '../components/commonFun.dart'; import 'package:provider/provider.dart'; import '../provider/player_ratio.dart'; import 'my_slider.dart'; import '../my_extended_image/common/image_picker/_image_picker_io.dart'; import '../services/Storage.dart'; // class MyFijkPanelWidgetBuilder extends StatefulWidget { // const MyFijkPanelWidgetBuilder({Key key, this.path}) : super(key: key); // final path; // @override // State createState() { // return MyFijkPanelWidgetBuilderState(); // } // } // snapshot ImageProvider myImageProvider; //ImageProvider _imageProvider; //Timer _snapshotTimer; FijkPanelWidgetBuilder fijkPanel2Builder3( {Key key, final FijkPlayer player, final bool fill = false, final int duration = 4000, final bool doubleTap = true, final bool snapShot = false, final VoidCallback onBack}) { return (FijkPlayer player, FijkData data, BuildContext context, Size viewSize, Rect texturePos) { return MyFijkPanelWidgetBuilder( key: key, player: player, data: data, onBack: onBack, viewSize: viewSize, texPos: texturePos, fill: fill, doubleTap: doubleTap, snapShot: snapShot, hideDuration: duration, ); }; } class MyFijkPanelWidgetBuilder extends StatefulWidget { final FijkPlayer player; final FijkData data; final VoidCallback onBack; final Size viewSize; final Rect texPos; final bool fill; final bool doubleTap; final bool snapShot; final int hideDuration; const MyFijkPanelWidgetBuilder( {Key key, @required this.player, this.data, this.fill, this.onBack, this.viewSize, this.hideDuration, this.doubleTap, this.snapShot, this.texPos}) : assert(player != null), assert( hideDuration != null && hideDuration > 0 && hideDuration < 10000), super(key: key); // @override // FijkPanel2State createState() => FijkPanel2State(); // @override //State createState() { MyFijkPanelWidgetBuilderState createState() { return MyFijkPanelWidgetBuilderState(player); } } class MyFijkPanelWidgetBuilderState extends State with SingleTickerProviderStateMixin { FijkPlayer player; MyFijkPanelWidgetBuilderState(@required this.player) { _oldFullScreen = player.value.fullScreen; } PlayerRatioProvide playerRatioProvide; AnimationController _controller; Animation _animation; Offset _normalizedOffset; double _previousScale; double _kMinFlingVelocity = 600.0; Offset _offset = Offset.zero; double _scale = 1.0; Offset _focalPoint = Offset.zero; Offset _deltaPoint = Offset.zero; bool _oldFullScreen; bool _bScaleing = false; //FijkPlayer get player => widget.player; Timer _hideTimer; bool _hideStuff = true; Timer _statelessTimer; bool _prepared = false; bool _playing = false; //bool _dragLeft; int _nDrag; //0 Left, 1 Center, 2 Right double _volume; double _brightness; double _deltaX = 0; double _deltaY = 0; double _seekPos = -1.0; Duration _duration = Duration(); Duration _currentPos = Duration(); Duration _bufferPos = Duration(); StreamSubscription _currentPosSubs; StreamSubscription _bufferPosSubs; StreamController _valController; // snapshot //ImageProvider _imageProvider; Timer _snapshotTimer; // Is it needed to clear seek data in FijkData (widget.data) bool _needClearSeekData = true; static const MyFijkSliderColors sliderColors = MyFijkSliderColors(); @override void initState() { super.initState(); _controller = AnimationController(vsync: this); _controller.addListener(() { setState(() { _offset = _animation.value; }); }); _valController = StreamController.broadcast(); _prepared = player.state.index >= FijkState.prepared.index; _playing = player.state == FijkState.started; _duration = player.value.duration; _currentPos = player.currentPos; _bufferPos = player.bufferPos; _currentPosSubs = player.onCurrentPosUpdate.listen((v) { if (_hideStuff == false) { setState(() { _currentPos = v; }); } else { _currentPos = v; } if (_needClearSeekData) { widget.data.clearValue(MyFijkData.fijkViewPanelSeekto); } _needClearSeekData = false; }); if (widget.data.contains(MyFijkData.fijkViewPanelSeekto)) { var pos = widget.data.getValue(MyFijkData.fijkViewPanelSeekto) as double; _currentPos = Duration(milliseconds: pos.toInt()); } _bufferPosSubs = player.onBufferPosUpdate.listen((v) { if (_hideStuff == false) { setState(() { _bufferPos = v; }); } else { _bufferPos = v; } }); player.addListener(_playerValueChanged); } @override void dispose() { super.dispose(); _controller.dispose(); _valController?.close(); _hideTimer?.cancel(); _statelessTimer?.cancel(); _snapshotTimer?.cancel(); _currentPosSubs?.cancel(); _bufferPosSubs?.cancel(); player.removeListener(_playerValueChanged); } double dura2double(Duration d) { return d != null ? d.inMilliseconds.toDouble() : 0.0; } void _playerValueChanged() { FijkValue value = player.value; if (value.duration != _duration) { if (_hideStuff == false) { setState(() { _duration = value.duration; }); } else { _duration = value.duration; } } bool playing = (value.state == FijkState.started); bool prepared = value.prepared; if (playing != _playing || prepared != _prepared || value.state == FijkState.asyncPreparing) { setState(() { _playing = playing; _prepared = prepared; }); } } void _restartHideTimer() { _hideTimer?.cancel(); _hideTimer = Timer(Duration(milliseconds: widget.hideDuration), () { setState(() { _hideStuff = true; }); }); } void onTapFun() { if (_hideStuff == true) { _restartHideTimer(); } setState(() { _hideStuff = !_hideStuff; }); } void playOrPause() { if (player.isPlayable() || player.state == FijkState.asyncPreparing) { if (player.state == FijkState.started) { //bPlaying = false; playerRegionProvide.changePlayerState(false); Storage.setString('bPlaying', 'false'); player.pause(); } else { //bPlaying = true; playerRegionProvide.changePlayerState(true); Storage.setString('bPlaying', 'true'); player.start(); } //setState(() {}); } else { FijkLog.w("Invalid state ${player.state} ,can't perform play or pause"); } } void onVerticalDragStartFun(DragStartDetails d) { // if (_bScaleing) { // return; // } if (d.localPosition.dx > panelWidth() * 3 / 4) { // right, volume //_dragLeft = false; _nDrag = 2; //0 Left, 1 Center, 2 Right //https://fijkplayer.befovy.com/docs/zh/system-volume.html#gsc.tab=0 /// never show system volume changed UI. int neverShowUI = 2; FijkVolume.setUIMode(neverShowUI); FijkVolume.getVol().then((v) { if (widget.data != null && !widget.data.contains(MyFijkData.fijkViewPanelVolume)) { widget.data.setValue(MyFijkData.fijkViewPanelVolume, v); } setState(() { _volume = v; _valController.add(v); }); }); } else if (d.localPosition.dx < panelWidth() / 4) { // left, brightness //_dragLeft = true; _nDrag = 0; //0 Left, 1 Center, 2 Right FijkPlugin.screenBrightness().then((v) { if (widget.data != null && !widget.data.contains(MyFijkData.fijkViewPanelBrightness)) { widget.data.setValue(MyFijkData.fijkViewPanelBrightness, v); } setState(() { _brightness = v; _valController.add(v); }); }); } else { _nDrag = 1; //0 Left, 1 Center, 2 Right // setState(() { // if (_oldFullScreen != player.value.fullScreen) { // //全屏和窗口之间切换,便初始化相关变量 // _oldFullScreen = player.value.fullScreen; // // _offset = Offset.zero; // // _scale = 1.0; // //_focalPoint = Offset.zero; // //_deltaPoint = Offset.zero; // } // // _bScaleing = true; // // _previousScale = _scale; // // _normalizedOffset = (d.localPosition - _offset) / _scale; // // // 计算图片放大后的位置 // // _controller.stop(); // }); } _statelessTimer?.cancel(); _statelessTimer = Timer(const Duration(milliseconds: 2000), () { setState(() {}); }); } void onVerticalDragUpdateFun(DragUpdateDetails d) { // if (_bScaleing) { // return; // } double delta = d.primaryDelta / panelHeight(); print("d.primaryDelta = ${d.primaryDelta}, delta = {$delta}"); delta = -delta.clamp(-1.0, 1.0); //if (_dragLeft != null && _dragLeft == false) { if (_nDrag != null && _nDrag == 2) { if (_volume != null) { _volume += delta; _volume = _volume.clamp(0.0, 1.0); FijkVolume.setVol(_volume); setState(() { _valController.add(_volume); }); } //} else if (_dragLeft != null && _dragLeft == true) { } else if (_nDrag != null && _nDrag == 0) { if (_brightness != null) { _brightness += delta; _brightness = _brightness.clamp(0.0, 1.0); FijkPlugin.setScreenBrightness(_brightness); setState(() { _valController.add(_brightness); }); } } else { setState(() { //_scale = (_previousScale * details.scale).clamp(1.0, 10.0); // 限制放大倍数 1~10倍 // _offset = _clampOffset(d.localPosition - _normalizedOffset * _scale); // playerRatioProvide.changeOffset(_offset); _deltaY -= 2 * d.primaryDelta / panelHeight(); _deltaY = _deltaY.clamp(-1.0, 1.0); playerRatioProvide.changeDeltaY(_deltaY); // 更新当前位置 }); } } void onVerticalDragEndFun(DragEndDetails e) { _volume = null; _brightness = null; } Widget buildPlayButton(BuildContext context, double height) { Icon icon = (player.state == FijkState.started) ? Icon(Icons.pause) : Icon(Icons.play_arrow); bool fullScreen = player.value.fullScreen; return IconButton( padding: EdgeInsets.all(0), iconSize: fullScreen ? height : height * 0.8, color: Color(0xFFFFFFFF), icon: icon, onPressed: playOrPause, ); } Widget buildFullScreenButton(BuildContext context, double height) { Icon icon = player.value.fullScreen ? Icon(Icons.fullscreen_exit) : Icon(Icons.fullscreen); bool fullScreen = player.value.fullScreen; return IconButton( padding: EdgeInsets.all(0), iconSize: fullScreen ? height : height * 0.8, color: Color(0xFFFFFFFF), icon: icon, onPressed: () { player.value.fullScreen ? player.exitFullScreen() : player.enterFullScreen(); }, ); } Widget buildTimeText(BuildContext context, double height) { String text = "${mYduration2String(_currentPos)}" + "/${mYduration2String(_duration)}"; return Text(text, style: TextStyle(fontSize: 12, color: Color(0xFFFFFFFF))); } Widget buildSlider(BuildContext context) { double duration = dura2double(_duration); double currentValue = _seekPos > 0 ? _seekPos : dura2double(_currentPos); currentValue = currentValue.clamp(0.0, duration); double bufferPos = dura2double(_bufferPos); bufferPos = bufferPos.clamp(0.0, duration); return Padding( padding: EdgeInsets.only(left: 0), child: MyFijkSlider( colors: sliderColors, value: currentValue, cacheValue: bufferPos, min: 0.0, max: duration, onChanged: (v) { _restartHideTimer(); setState(() { _seekPos = v; }); }, onChangeEnd: (v) { setState(() { player.seekTo(v.toInt()); _currentPos = Duration(milliseconds: _seekPos.toInt()); widget.data.setValue(MyFijkData.fijkViewPanelSeekto, _seekPos); _needClearSeekData = true; _seekPos = -1.0; }); }, ), ); } Widget buildBottom(BuildContext context, double height) { if (_duration != null && _duration.inMilliseconds > 0) { return Row( children: [ buildPlayButton(context, height), buildTimeText(context, height), Expanded(child: buildSlider(context)), buildFullScreenButton(context, height), ], ); } else { return Row( children: [ buildPlayButton(context, height), Expanded(child: Container()), buildFullScreenButton(context, height), ], ); } } void takeSnapshot() { player.takeSnapShot().then((v) { var provider = MemoryImage(v); precacheImage(provider, context).then((_) { setState(() { myImageProvider = provider; }); }); FijkLog.d("get snapshot succeed"); //Uint8List fileData; String fileFath; ImageSaver.save('extended_image_cropped_image.jpg', v).then((value) { fileFath = value; // var fileFath = await ImagePickerSaver.saveFile(fileData: fileData); print('my save fileFath : $fileFath'); }); }).catchError((e) { FijkLog.d("get snapshot failed"); }); } Widget buildPanel(BuildContext context) { double height = panelHeight(); bool fullScreen = player.value.fullScreen; Widget centerWidget = Container( color: Color(0x00000000), ); Widget centerChild = Container( color: Color(0x00000000), ); if (fullScreen && widget.snapShot) { centerWidget = Row( children: [ Expanded(child: centerChild), Padding( padding: EdgeInsets.only(left: 10, right: 10, top: 8, bottom: 8), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ IconButton( padding: EdgeInsets.all(0), color: Color(0xFFFFFFFF), icon: Icon(Icons.camera_alt), onPressed: () { takeSnapshot(); }, ), ], ), ) ], ); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( height: height > 200 ? 80 : height / 5, decoration: BoxDecoration( gradient: LinearGradient( colors: [Color(0x88000000), Color(0x00000000)], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), Expanded( child: centerWidget, ), Container( height: height > 80 ? 80 : height / 2, decoration: BoxDecoration( gradient: LinearGradient( colors: [Color(0x88000000), Color(0x00000000)], end: Alignment.topCenter, begin: Alignment.bottomCenter, ), ), alignment: Alignment.bottomCenter, child: Container( height: height > 80 ? 45 : height / 2, padding: EdgeInsets.only(left: 8, right: 8, bottom: 5), child: buildBottom(context, height > 80 ? 40 : height / 2), // child: CustomFijkWidgetBottom( // player: player, // buildContext: context, // viewSize: // Size(widget.viewSize.width, widget.viewSize.height - 50), // texturePos: Rect.fromLTRB(widget.texPos.left, widget.texPos.top, // widget.texPos.width, widget.texPos.height - 50), //), ), ) ], ); } Rect panelRect() { Rect rect = player.value.fullScreen || (true == widget.fill) ? Rect.fromLTWH(0, 0, widget.viewSize.width, widget.viewSize.height) : Rect.fromLTRB( max(0.0, widget.texPos.left), max(0.0, widget.texPos.top), min(widget.viewSize.width, widget.texPos.right), min(widget.viewSize.height, widget.texPos.bottom)); return rect; } double panelHeight() { if (player.value.fullScreen || (true == widget.fill)) { return widget.viewSize.height; } else { return min(widget.viewSize.height, widget.texPos.bottom) - max(0.0, widget.texPos.top); } } double panelWidth() { if (player.value.fullScreen || (true == widget.fill)) { return widget.viewSize.width; } else { return min(widget.viewSize.width, widget.texPos.right) - max(0.0, widget.texPos.left); } } Widget buildBack(BuildContext context) { return IconButton( padding: EdgeInsets.only(left: 5), icon: Icon( Icons.arrow_back_ios, color: Color(0xDDFFFFFF), ), onPressed: widget.onBack, ); } Widget buildStateless() { if (_volume != null || _brightness != null) { Widget toast = _volume == null ? defaultFijkBrightnessToast(_brightness, _valController.stream) : defaultFijkVolumeToast(_volume, _valController.stream); return IgnorePointer( child: AnimatedOpacity( opacity: 1, duration: Duration(milliseconds: 500), child: toast, ), ); } else if (player.state == FijkState.asyncPreparing) { return Container( alignment: Alignment.center, child: SizedBox( width: 30, height: 30, child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.white)), ), ); } else if (player.state == FijkState.error) { return Container( alignment: Alignment.center, child: Icon( Icons.error, size: 30, color: Color(0x99FFFFFF), ), ); } else if (myImageProvider != null) { _snapshotTimer?.cancel(); _snapshotTimer = Timer(Duration(milliseconds: 1500), () { if (mounted) { setState(() { myImageProvider = null; }); } }); return Center( child: IgnorePointer( child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.yellowAccent, width: 3)), child: Image(height: 200, fit: BoxFit.contain, image: myImageProvider), ), ), ); } else { return Container(); } } GestureDetector buildGestureDetector(BuildContext context) { return GestureDetector( onScaleStart: _handleOnScaleStart, onScaleUpdate: _handleOnScaleUpdate, onScaleEnd: _handleOnScaleEnd, onDoubleTap: () { //自定义 FijkView 的双击响应 print('My onDoubleTap'); if (1.0 == playerRatioProvide.scale) { playOrPause(); } else { _handleScaleBack(); } }, onTap: onTapFun, //onDoubleTap: widget.doubleTap ? onDoubleTapFun : null, onVerticalDragUpdate: onVerticalDragUpdateFun, onVerticalDragStart: onVerticalDragStartFun, onVerticalDragEnd: onVerticalDragEndFun, //onHorizontalDragUpdate: (d) {}, child: AbsorbPointer( absorbing: _hideStuff, child: AnimatedOpacity( opacity: _hideStuff ? 0 : 1, duration: Duration(milliseconds: 300), child: buildPanel(context), ), ), ); } void _handleOnScaleStart(ScaleStartDetails details) { setState(() { // if (_oldFullScreen != player.value.fullScreen) { // //全屏和窗口之间切换,便初始化相关变量 // _oldFullScreen = player.value.fullScreen; // // _offset = Offset.zero; // // _scale = 1.0; // //_focalPoint = Offset.zero; // //_deltaPoint = Offset.zero; // } _bScaleing = true; _previousScale = _scale; _normalizedOffset = (details.focalPoint - _offset) / _scale; // 计算图片放大后的位置 _controller.stop(); _focalPoint = details.focalPoint; }); } void _handleScaleBack() { // 更新当前位置 playerRatioProvide.changeScale(1.0); playerRatioProvide.changeOffset(Offset(0, 0)); playerRatioProvide.changeDeltaX(0.0); playerRatioProvide.changeDeltaY(0.0); } void _handleOnScaleUpdate(ScaleUpdateDetails details) { setState(() { print('details.scale = ${details.scale}'); //print('_scale = $_scale, offset.dx = ${_offset.dx}, offset.dy = ${_offset.dy}'); //print('details.focalPoint = (${details.focalPoint.dx}, ${details.focalPoint.dy})'); //print('details.horizontalScale = ${details.horizontalScale}; details.verticalScale = ${details.verticalScale})'); if (1.0 != details.scale) { _scale = (_previousScale * details.scale).clamp(1.0, 10.0); playerRatioProvide.changeScale(_scale); // 限制放大倍数 1~10倍 _offset = _clampOffset(details.focalPoint - _normalizedOffset * _scale); } else { _deltaPoint = _clampdeltaPoint(details.focalPoint - _focalPoint); _focalPoint = details.focalPoint; print('_deltaPoint = (${_deltaPoint.dx}, ${_deltaPoint.dy})'); playerRatioProvide.changeDeltaX(_deltaPoint.dx); playerRatioProvide.changeDeltaY(_deltaPoint.dy); } // 更新当前位置 playerRatioProvide.changeOffset(_offset); }); } Offset _clampdeltaPoint(Offset delta) { final Size size = context.size; // widget的屏幕尺寸 double rate = 2.0; Offset deltaPoint = Offset((rate * delta.dx / size.width), (rate * delta.dy / size.height)); _deltaPoint -= deltaPoint; return Offset( _deltaPoint.dx.clamp(-1.0, 1.0), _deltaPoint.dy.clamp(-1.0, 1.0)); } Offset _clampOffset(Offset offset) { final Size size = context.size; // widget的屏幕宽度 final Offset minOffset = Offset(size.width, size.height) * (1.0 - _scale); // 限制他的最小尺寸 return Offset( offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0)); } void _handleOnScaleEnd(ScaleEndDetails details) { final double magnitude = details.velocity.pixelsPerSecond.distance; if (magnitude < _kMinFlingVelocity) return; final Offset direction = details.velocity.pixelsPerSecond / magnitude; // 计算当前的方向 final double distance = (Offset.zero & context.size).shortestSide; // 计算放大倍速,并相应的放大宽和高,比如原来是600*480的图片,放大后倍数为1.25倍时,宽和高是同时变化的 _animation = _controller.drive(Tween( begin: _offset, end: _clampOffset(_offset + direction * distance))); _controller ..value = 0.0 ..fling(velocity: magnitude / 1000.0); _bScaleing = false; } @override Widget build(BuildContext context) { playerRatioProvide = Provider.of(context); Rect rect = panelRect(); List ws = []; if (_statelessTimer != null && _statelessTimer.isActive) { ws.add(buildStateless()); } else if (player.state == FijkState.asyncPreparing) { ws.add(buildStateless()); } else if (player.state == FijkState.error) { ws.add(buildStateless()); } else if (myImageProvider != null) { ws.add(buildStateless()); } ws.add(buildGestureDetector(context)); if (widget.onBack != null) { ws.add(buildBack(context)); } return Positioned.fromRect( rect: rect, child: Stack(children: ws), ); } }