import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutterappfuyou/code/base/YSNetWorking.dart'; import 'package:flutterappfuyou/code/base/YSTools.dart'; import 'package:flutterappfuyou/code/live/view/YSLiveUserView.dart'; import 'package:live_flutter_plugin/v2_tx_live_def.dart'; import 'package:live_flutter_plugin/v2_tx_live_pusher.dart'; import 'package:live_flutter_plugin/v2_tx_live_pusher_observer.dart'; import 'package:live_flutter_plugin/widget/v2_tx_live_video_widget.dart'; import 'package:wakelock/wakelock.dart'; class YSLiveAnchor extends StatefulWidget { final int liveId; final isSelect; const YSLiveAnchor({Key key, this.liveId, this.isSelect = false}) : super(key: key); @override _YSLiveAnchorState createState() => _YSLiveAnchorState(); } class _YSLiveAnchorState extends State { String _pushUrl = ''; WebSocket _socket; List _userArray = []; int _userCount = 0; bool _isMute = false; bool _isPause = false; bool _isFont = true; @override void initState() { if(widget.isSelect){ Future.delayed(Duration(seconds: 0)).then((value) { _getStartData(); }); } super.initState(); } _getStartData() async{ Map dict = await ysRequestHttp(context, requestType.get, 'train/live2/start', {'live_id':widget.liveId}); if(dict!=null){ Map data = dict['data']; _pushUrl = data['push_url']; User().castAvatar = data['cast_avatar']; User().castName = data['cast_name']; User().stream = data['live_stream']??''; User.instance.isAnchor = true; setState(() {}); } } @override Widget build(BuildContext context) { return NotificationListener( onNotification: (notification){ Map data = notification.value; String type = data['type']; if(type=='headcount'){ _userArray = data['value']['users']??[]; _userCount = data['value']['total']??0; setState(() {}); }else if(type=='mute'){ _isMute = !_isMute; setState(() {}); }else if(type=='pause'||type=='resume'){ if(type=='pause'){ liveKey.currentState._pauseLive(); }else{ liveKey.currentState._resumeLive(); } _isPause = type=='pause'; setState(() {}); }else if(type=='staff'){ List users = data['value']['users']??[]; ysShowBottomAlertView(context, YSUsersAlertView(users: users,),isBarr: true); } return true; }, child: WillPopScope( onWillPop: () async{ return false; }, child: Scaffold( backgroundColor: Colors.black, body: Container( width: ysWidth(context), height: ysHeight(context), child: Stack( children: [ _pushUrl.isNotEmpty?YSAnchorLiveView( key: liveKey,pushStr: _pushUrl, ):Image.asset('lib/images/图.png',fit: BoxFit.fill,width: ysWidth(context),height: ysHeight(context),), Container( width: ysWidth(context), height: ysHeight(context), padding: EdgeInsets.only(top: ysTOP(context)+15,left: 10,right: 10,bottom: 10), child: Column( children: [ Container( height: 40, child: Row( children: [ Container( width: (ysWidth(context)-20)*0.3, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.all(Radius.circular(50)) ), child: User().castAvatar!=null?Row( children: [ Container( height: 40, width: 40, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(50)), image: DecorationImage(image: NetworkImage(User().castAvatar)) ), ), Container( width: (ysWidth(context)-20)*0.3-40, padding: EdgeInsets.only(left: 5,right: 5), child: Text(User().castName,style: TextStyle(fontSize: 10,color: Colors.white,fontWeight: FontWeight.bold,), maxLines: 1,overflow: TextOverflow.ellipsis,), ) ], ):Container(), ), GestureDetector( onTap: (){ if(_socket==null)return; _socket.add(jsonEncode({'type':'staff'})); // Navigator.of(context).push( // CupertinoPageRoute(builder: (context){ // return YSLiveUser(); // }) // ); }, behavior: HitTestBehavior.opaque, child: Row( children: [ Container( width: (ysWidth(context)-20)*0.55, padding: EdgeInsets.only(top: 5,bottom: 5,left: 10), child: ListView.builder( itemBuilder: (context,index){ Map item = _userArray[index]; return Container( height: 30, width: 30, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(50)), color: Colors.white, image: DecorationImage(image: NetworkImage(item['avatar']),fit: BoxFit.cover) ), ); }, itemCount: _userArray.length, scrollDirection: Axis.horizontal, ), ), Container( width: (ysWidth(context)-20)*0.15, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.all(Radius.circular(50)) ), alignment: Alignment.center, child: Text('$_userCount',style: TextStyle(fontSize: 12,color: Colors.white),), ) ], ), ) ], ), ), Container( height: ysHeight(context)-ysTOP(context)-125, alignment: Alignment.bottomRight, child: _pushUrl.isEmpty&&widget.isSelect==false?Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('直播尚未开始,您是否需要开始直播?',style: TextStyle(fontSize: 18,color: Colors.white,fontWeight: FontWeight.bold),), GestureDetector( onTap: () { _getStartData(); }, child: Container( height: 44, width: 150, decoration: BoxDecoration( color: Color(0xFFE36085), borderRadius: BorderRadius.all(Radius.circular(5)) ), margin: EdgeInsets.only(top: 30,bottom: 50), alignment: Alignment.center, child: Text('立即直播',style: TextStyle(fontSize: 18,color: Colors.white),), ), ) ], ):Container(), ), _pushUrl.isEmpty?Container( height: 60, alignment: Alignment.centerRight, child: GestureDetector( onTap: (){ Navigator.of(context).pop(''); Navigator.of(context).pop(''); Navigator.of(context).pop(''); }, child: Image.asset('lib/images/退出.png',height: 20,width: 20,), ) ):Container( alignment: Alignment.centerRight, height: 60, child: Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( child: Image.asset('lib/images/${_isMute?'取消禁言':'禁言'}.png',height: 20,width: 20,color: Colors.white,), onTap: (){ if(_isMute){ _socket.add(jsonEncode({'type':'mute','mute_time':1})); }else{ _socket.add(jsonEncode({'type':'mute','mute_time':0})); } }, ), Container( margin: EdgeInsets.only(left: 20,right: 20), child: GestureDetector( onTap: (){ if(_isPause){ _socket.add(jsonEncode({'type':'resume'})); }else{ _socket.add(jsonEncode({'type':'pause'})); } }, child: Image.asset('lib/images/${_isPause?'取消暂停':'暂停'}.png',height: 20,width: 20,color: Colors.white,), ) ), Container( margin: EdgeInsets.only(right: 20), child: GestureDetector( onTap: () async{ _isFont = !_isFont; liveKey.currentState._changeCameraLive(_isFont); setState(() {}); }, child: Icon(_isFont?Icons.camera_front:Icons.video_camera_back,color: Colors.white,) ), ), GestureDetector( onTap: () { ysShowCenterAlertView(context, YSTipsAlertView( tipsStr: '是否退出直播?', valueSetter: (value) async{ if(value){ Map dict = await ysRequestHttp(context, requestType.get, 'train/live2/close', {'live_id':widget.liveId}); if(dict!=null){ if(_socket!=null){ _socket.add(jsonEncode({'type':'leave'})); } liveKey.currentState._stopPush(); Navigator.of(context).pop(''); Navigator.of(context).pop(''); Navigator.of(context).pop(''); } } }, )); }, child: Image.asset('lib/images/退出.png',height: 20,width: 20,color: Colors.white,), ) ], ), ), ], ), ), if(_pushUrl.isNotEmpty)Positioned( bottom: 70, left: 10, child:YSTalkView( postSocket: (socket){ _socket = socket; if(_pushUrl.isNotEmpty){ Future.delayed(Duration(seconds: 2)).then((value) { _socket.add(jsonEncode({'type':'enter'})); }); } }, ) ) ], ), ), ), ), ); } } class YSTalkView extends StatefulWidget { final ValueSetter postSocket; const YSTalkView({Key key, this.postSocket}) : super(key: key); @override _YSTalkViewState createState() => _YSTalkViewState(); } class _YSTalkViewState extends State { List _dataArray = []; static WebSocket _socket; Timer _timer; ScrollController _scrollController = ScrollController(); @override void initState() { _getSocket(); super.initState(); } //wss://v2fy.niwoshenghuo.com/websocket //ws://101.43.97.222:868 //ws://114.115.176.164:8686 _getSocket() async{ WebSocket.connect('wss://v2fy.niwoshenghuo.com/websocket').then((socket) { _socket = socket; widget.postSocket(_socket); socket.listen(_onData, cancelOnError: false); _timer =Timer.periodic(Duration(seconds: 10), (timer) { _socket.add('heartbeat'); }); }).catchError((e){ LogUtil.d("Unable to connect: $e"); _getSocket(); // 连接超时,重新建立连接 }); } _onData(event) async{ Map dict = jsonDecode(event); LogUtil.d("---onData---$dict"); if(dict['type']=='connection'){ Map data = dict['data']; _dataArray.add({'type':0,'content':data['content']}); setState(() {}); Map message = {}; message['type'] = 'bind'; message['live_stream'] = User().stream; message['uid'] = data['uid']; message['user'] = {'username':User().castName??User().name,'avatar':User().castAvatar??User().avatar,'is_owner':User().isAnchor}; _socket.add(jsonEncode(message)); }else if(dict['type']=='message'){ var data = dict['data']; if(data is List){ _dataArray.add({'type':1,'content':User().content,'name':User().castName??User().name,'avatar':User().castAvatar??User().name}); }else{ Map user = data['user']; _dataArray.add({'type':1,'content':data['content'],'name':user['username'],'avatar':user['avatar']}); } setState(() {}); _scrollController.jumpTo(_scrollController.position.maxScrollExtent+50); }else if(dict['type']=='headcount'||dict['type']=='staff'||dict['type']=='mute'||dict['type']=='pause'||dict['type']=='resume'||dict['type']=='enter'||dict['type']=='staff'){ var data = dict['data']; CustomerValueNotification({'type':dict['type'],'value':data}).dispatch(context); }else if(dict['type']=='leave'){ CustomerValueNotification({'type':dict['type'],'value':{}}).dispatch(context); }else if(dict['type']=='bind'){ _socket.add(jsonEncode({'type':'headcount'})); }else if(dict['type']=='heartbeat'){ var data = dict['data']; CustomerValueNotification({'type':'headcount','value':data}).dispatch(context); } } @override void dispose() { _socket.close(); if(_timer!=null){ if(_timer.isActive)_timer.cancel(); } super.dispose(); } @override Widget build(BuildContext context) { return Container( height: ysHeight(context)*0.4, width: ysWidth(context)*0.75, child: ListView.separated( controller: _scrollController, padding: EdgeInsets.only(top: 10,bottom: 10), itemBuilder: (context,index){ Map item = _dataArray[index]; int type = item['type']; return Container( padding: EdgeInsets.only(left: 10,right: 10,top: 5,bottom: 5), decoration: BoxDecoration( color: Colors.black12, borderRadius: BorderRadius.all(Radius.circular(3)) ), child: type==0?Text('${item['content']}',style: TextStyle(fontSize: 13,color: Colors.orange),):Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( height: 20, width: 20, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(50)), image: DecorationImage(image: NetworkImage(item['avatar']),fit: BoxFit.cover) ), ), Container( padding: EdgeInsets.only(left: 5), constraints: BoxConstraints(maxWidth: ysWidth(context)*0.75-40), child: RichText( text: TextSpan( text: '${item['name']}:', style: TextStyle(fontSize: 13,color: Color(0xFF89DCFF)), children: [ TextSpan( text: '${item['content']}', style: TextStyle(fontSize: 13,color: Colors.white) ) ] ), ), ) ], ), ); }, separatorBuilder: (context,index){ return Container(height: 5,); }, itemCount: _dataArray.length ), ); } } GlobalKey<_YSAnchorLiveViewState> liveKey = GlobalKey(); class YSAnchorLiveView extends StatefulWidget { final String pushStr; const YSAnchorLiveView({Key key, this.pushStr = ''}) : super(key: key); @override _YSAnchorLiveViewState createState() => _YSAnchorLiveViewState(); } class _YSAnchorLiveViewState extends State { V2TXLivePusher _livePusher; int _localViewId; bool _isFont = true; _initPusher() async{ _livePusher = V2TXLivePusher(V2TXLiveMode.v2TXLiveModeRTMP); _livePusher.addListener(_onPusherObserver); } _onPusherObserver(V2TXLivePusherListenerType type, param) { debugPrint("==pusher listener type= ${type.toString()}"); debugPrint("==pusher listener param= $param"); } @override void initState() { Wakelock.enable(); _initPusher(); super.initState(); } @override void dispose() async{ Wakelock.disable(); _stopPush(); super.dispose(); } @override Widget build(BuildContext context) { return V2TXLiveVideoWidget( onViewCreated: (viewId) async{ _localViewId = viewId; _livePusher.setRenderViewID(_localViewId); _startPush(); } ); } _stopPush() async { await _livePusher?.stopMicrophone(); await _livePusher?.stopCamera(); await _livePusher?.stopPush(); _livePusher.destroy(); } _pauseLive(){ _livePusher.stopCamera(); _livePusher.stopMicrophone(); } _resumeLive(){ _livePusher.startCamera(_isFont); _livePusher.startMicrophone(); } _changeCameraLive(bool isFont){ _isFont = isFont; _livePusher.getDeviceManager().switchCamera(isFont); } _startPush() async { bool bool1 = await permissionHandler('microphone'); bool bool2 = await permissionHandler('camera'); if(bool1&&bool2){ _livePusher.getBeautyManager().setBeautyLevel(5); _livePusher.startCamera(_isFont); _livePusher.startMicrophone(); int result = await _livePusher.startPush(widget.pushStr); LogUtil.d('result=========$result'); } } }