import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutterappfuyou/code/live/view/YSLiveUserView.dart'; import 'package:live_flutter_plugin/v2_tx_live_player.dart'; import 'package:live_flutter_plugin/v2_tx_live_player_observer.dart'; import 'package:live_flutter_plugin/widget/v2_tx_live_video_widget.dart'; import 'package:wakelock/wakelock.dart'; import '../base/YSNetWorking.dart'; import '../base/YSTools.dart'; import 'YSLiveAnchor.dart'; import 'YSVideoDetail.dart'; class YSLiveDetail extends StatefulWidget { final liveId; final String passWord; const YSLiveDetail({Key key, this.liveId, this.passWord = ''}) : super(key: key); @override _YSLiveDetailState createState() => _YSLiveDetailState(); } class _YSLiveDetailState extends State with SingleTickerProviderStateMixin{ V2TXLivePlayer _livePlayer; Map _infoDict = {}; List _userArray = []; int _userCount = 0; bool _isMute = false; bool _isPause = false; WebSocket _socket; String _content = ''; @override void initState() { Wakelock.enable(); _createPlayer(); Future.delayed(Duration(seconds: 0)).then((value) { _getVideoDetailData(); }); super.initState(); } @override void dispose() { Wakelock.disable(); _livePlayer.stopPlay(); _livePlayer.destroy(); super.dispose(); } _createPlayer() async{ _livePlayer = await V2TXLivePlayer.create(); _livePlayer.addListener(onPlayerObserver); } /// Player observer onPlayerObserver(V2TXLivePlayerListenerType type, param) { debugPrint("==player listener type= ${type.toString()}"); debugPrint("==player listener type= $param"); } @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 = data['value']['MuteTime']==0; LogUtil.d('train/live2/info$_isMute'); setState(() {}); }else if(type=='pause'||type=='resume'){ _isPause = type=='pause'; setState(() {}); }else if(type=='leave'){ ysFlutterToast(context, '直播已结束'); Navigator.of(context).pop(''); }else if(type=='enter'){ _livePlayer.startLivePlay(_infoDict['pull_url']); }else if(type=='staff'){ List users = data['value']['users']??[]; ysShowBottomAlertView(context, YSUsersAlertView(users: users,),isBarr: true); } return true; }, child: AnnotatedRegion( value: SystemUiOverlayStyle.light, child: Scaffold( backgroundColor: Colors.black, body: SingleChildScrollView( child: Container( width: ysWidth(context), height: ysHeight(context), child: Stack( children: [ Container( padding: EdgeInsets.only(top: ysTOP(context)+10), child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(10)), child: Container( width: ysWidth(context), height: ysHeight(context)-ysTOP(context)-70, child: Stack( children: [ _infoDict.isNotEmpty?V2TXLiveVideoWidget( onViewCreated: (viewId) async { _livePlayer.setRenderViewID(viewId); }, ):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: 10,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: Row( children: [ if(_infoDict.isNotEmpty)Container( height: 40, width: 40, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(50)), image: DecorationImage(image: NetworkImage(_infoDict['cast_avatar'])) ), ), Container( width: (ysWidth(context)-20)*0.3-40, padding: EdgeInsets.only(left: 5,right: 5), child: Text(_infoDict['cast_name']??'',style: TextStyle(fontSize: 10,color: Colors.white,fontWeight: FontWeight.bold,), maxLines: 1,overflow: TextOverflow.ellipsis,), ) ], ), ), 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),), ) ], ), ) ], ), ), ], ), ), if(_isPause)Center( child: Icon(Icons.play_circle_fill,size: 100,color: Colors.white,), ), if(_infoDict.isNotEmpty)Positioned( bottom: 20, left: 10, child:YSTalkView( postSocket: (socket){ _socket = socket; }, ) ), if(_isPause)Center( child: Icon(Icons.play_circle_fill,size: 100,color: Colors.white,), ), ], ), ), ), ), Positioned( bottom: 10, left: 10, right: 10, child: Container( margin: EdgeInsets.only(top: 10,bottom: 10), height: 30, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: (){ if(_isMute){ ysFlutterToast(context, '直播间禁止发言'); return; } ysShowBottomAlertView2(context, YSInputView(valueSetter: (value) async{ User.instance.content = value; _socket.add(jsonEncode({'type':'message','content':value})); },)); }, child: Container( width: (ysWidth(context)-20)*0.6, height: 30, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(50)), border: Border.all(color: Colors.white,width: 1) ), child: Text('说点什么',style: TextStyle(fontSize: 13,color: Colors.white),), alignment: Alignment.centerLeft, padding: EdgeInsets.only(left: 15,right: 15), ), ), GestureDetector( onTap: (){ Navigator.pop(context); }, child: Icon(Icons.close,size: 30,color: Colors.white,) ) ], ), ) ), ], ), ), ), ), ), ); } _getVideoDetailData() async{ Map request = {'live_id':widget.liveId}; if(widget.passWord.isNotEmpty)request['password'] = widget.passWord; Map dict = await ysRequestHttp(context, requestType.get, 'train/live2/info', request); if(dict!=null){ _infoDict = dict['data']; _livePlayer.startLivePlay(_infoDict['pull_url']); User().stream = _infoDict['live_stream']??''; setState(() {}); } } } class YSLiveInfoView extends StatefulWidget { const YSLiveInfoView({Key key}) : super(key: key); @override _YSLiveInfoViewState createState() => _YSLiveInfoViewState(); } class _YSLiveInfoViewState extends State { @override Widget build(BuildContext context) { return Align( alignment: Alignment.topCenter, child: Container( width: MediaQuery.of(context).size.width-20, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5)) ), margin: EdgeInsets.only(top: 20,left: 10,right: 10), padding: EdgeInsets.only(top: 20,left: 10,right: 10,bottom: 20), child: Text('简介',style: TextStyle(fontSize: 14,color: Colors.black),), ), ); } } class YSCommentView2 extends StatefulWidget { final double height; const YSCommentView2({Key key, this.height = 0}) : super(key: key); @override _YSCommentView2State createState() => _YSCommentView2State(); } class _YSCommentView2State extends State { List _dataArray = []; static WebSocket _socket; Timer _timer; String _content = ''; bool _isMute = false; @override void initState() { User().isAnchor = false; _getSocket(); super.initState(); } @override void dispose() { _socket.close(); if(_timer!=null){ if(_timer.isActive)_timer.cancel(); } super.dispose(); } _getSocket() async{ WebSocket.connect('wss://v2fy.niwoshenghuo.com/websocket').then((socket) { LogUtil.d('WebSocket connect'); _socket = socket; socket.listen(_onData, cancelOnError: false); _timer =Timer.periodic(Duration(seconds: 50), (timer) { _socket.add('heartbeat'); }); _socket.add(jsonEncode({'type':'headcount'})); }).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':3,'content':data['content']}); setState(() {}); Map message = {}; message['type'] = 'bind'; message['live_stream'] = User().stream; message['uid'] = data['uid']; message['user'] = {'username':User().name,'avatar':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':2,'content':_content,'name':User().name,'avatar':User().avatar}); }else{ Map user = data['user']; _dataArray.add({'type':1,'content':data['content'],'name':user['username'],'avatar':user['avatar']}); } setState(() {}); }else if(dict['type']=='headcount'||dict['type']=='staff'||dict['type']=='mute'||dict['type']=='pause'||dict['type']=='resume'){ var data = dict['data']; if(dict['type']=='mute'){ _isMute = data['MuteTime']==0; LogUtil.d('MuteTime========$_isMute'); setState(() {}); } } } @override Widget build(BuildContext context) { return Column( children: [ Container( height: widget.height, child: ListView.separated( padding: EdgeInsets.only(top: 20,bottom: 20,left: 20,right: 20), itemBuilder: (context,index){ Map item = _dataArray[index]; return LayoutBuilder(builder: (context,conSize){ return item['type']==3?Container( child: Text(item['content'],style: TextStyle(fontSize: 13,color: Colors.orange),), ):item['type']==1?Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( height: 35, width: 35, decoration: BoxDecoration( color: Colors.white, image: DecorationImage(image: NetworkImage(item['avatar'])), borderRadius: BorderRadius.all(Radius.circular(50)) ), ), Container( width: conSize.maxWidth-35, padding: EdgeInsets.only(left: 8), child: Text(item['name']??'',style: TextStyle(fontSize: 13,color: Color(0xFF575757)),), ) ], ), Container( margin: EdgeInsets.only(left: 45,top: 5), padding: EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5)) ), child: Text(item['content']??'',style: TextStyle(fontSize: 13,color: Color(0xFF151515)),), ) ], ):Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Row( children: [ Container( width: conSize.maxWidth-35, padding: EdgeInsets.only(right: 8), child: Text(item['name']??'',style: TextStyle(fontSize: 13,color: Color(0xFF575757)),), alignment: Alignment.centerRight, ), Container( height: 35, width: 35, decoration: BoxDecoration( color: Colors.white, image: DecorationImage(image: NetworkImage(item['avatar'])), borderRadius: BorderRadius.all(Radius.circular(50)) ), ), ], ), Container( margin: EdgeInsets.only(right: 45,top: 5), padding: EdgeInsets.all(10), decoration: BoxDecoration( color: Color(0xFFE7688C), borderRadius: BorderRadius.all(Radius.circular(5)) ), child: Text(item['content']??'',style: TextStyle(fontSize: 13,color: Colors.white),), ) ], ); }); }, separatorBuilder: (context,index){ return Container(height: 10,); }, itemCount: _dataArray.length ), ), Container( height: 50, color: Colors.white, width: MediaQuery.of(context).size.width, padding: EdgeInsets.only(left: 20,right: 20,top: 10,bottom: 10), child: GestureDetector( onTap: (){ if(_isMute){ ysFlutterToast(context, '直播间禁止发言'); return; } ysShowBottomAlertView2(context, YSInputView(valueSetter: (value) async{ _content = value; _socket.add(jsonEncode({'type':'message','content':value})); },)); }, child: Container( alignment: Alignment.centerLeft, padding: EdgeInsets.only(left: 20,right: 20), child: Text('请输入您要发送的内容',style: TextStyle(fontSize: 15,color: Color(0xFF707070)),), decoration: BoxDecoration( color: Color(0xFFF5F3F0), borderRadius: BorderRadius.all(Radius.circular(5)) ), ), ), ) ], ); } } class YSLiveDetail2 extends StatefulWidget { final liveId; final String passWord; const YSLiveDetail2({Key key, this.liveId, this.passWord}) : super(key: key); @override _YSLiveDetail2State createState() => _YSLiveDetail2State(); } class _YSLiveDetail2State extends State with SingleTickerProviderStateMixin{ V2TXLivePlayer _livePlayer; Map _infoDict = {}; List _titles = ['简介','讨论']; TabController _tabController; int _index = 0; StateSetter _tabSet; @override void initState() { Wakelock.enable(); _tabController = TabController( vsync: this, length: _titles.length )..addListener(() { _index = _tabController.index; _tabSet(() {}); }); _createPlayer(); Future.delayed(Duration(seconds: 0)).then((value) { _getVideoDetailData(); }); super.initState(); } _createPlayer() async{ _livePlayer = await V2TXLivePlayer.create(); _livePlayer.addListener(onPlayerObserver); } /// Player observer onPlayerObserver(V2TXLivePlayerListenerType type, param) { } @override void dispose() { Wakelock.disable(); super.dispose(); } @override void deactivate() { _livePlayer?.removeListener(onPlayerObserver); _livePlayer?.stopPlay(); _livePlayer?.destroy(); super.deactivate(); } @override Widget build(BuildContext context) { return AnnotatedRegion( value: SystemUiOverlayStyle.light, child: Scaffold( body: SingleChildScrollView( child: Column( children: [ Container( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, color: Color(0xFFF5F3F0), child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Column( children: [ Container( height: constraints.maxHeight*0.4, color: Colors.black, child: V2TXLiveVideoWidget( onViewCreated: (viewId) async { _livePlayer.setRenderViewID(viewId); }, ), ), Container( height: constraints.maxHeight*0.6, child: DefaultTabController( length: _titles.length, child: Column( children: [ Container( width: ysWidth(context), height: 50, child: StatefulBuilder( builder: (context,setState){ _tabSet = setState; return TabBar( controller: _tabController, indicatorColor: Color(0xFFFA4444), labelColor: Color(0xFFFA4444), indicatorWeight: 2, indicatorPadding: EdgeInsets.all(0), labelPadding: EdgeInsets.all(0), indicatorSize: TabBarIndicatorSize.label, labelStyle: TextStyle(fontSize: 15), unselectedLabelColor: Color(0xFF5D6978), tabs: _titles.map((f) { return Container( color: _titles[_index]==f?Colors.transparent:Colors.white, alignment: Alignment.center, child: Tab(text: '$f'), width: ysWidth(context)/2, ); }).toList(), ); }, ), ), Container( height: constraints.maxHeight*0.6-50, child: TabBarView( controller: _tabController, children: _titles.map((f) { return f=='简介'?YSLiveInfoView():_infoDict.isNotEmpty?YSCommentView2( height: constraints.maxHeight*0.6-100 ):Container(); }).toList(), ) ), ], ), ), ) ], ); }, ) ), ], ), ), ), ); } _getVideoDetailData() async{ Map request = {'live_id':widget.liveId}; if(widget.passWord.isNotEmpty)request['password'] = widget.passWord; Map dict = await ysRequestHttp(context, requestType.get, 'train/live2/info', request); if(dict!=null){ _infoDict = dict['data']; _livePlayer.startLivePlay(_infoDict['pull_url']); User().stream = _infoDict['live_stream']??''; setState(() {}); } } }