will-play/lib/views/palRoom/palroom2.txt

720 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:will_play/utils/auth.dart';
import '../../utils/settings.dart';
import '../../utils/http_util.dart';
import '../../utils/showToast.dart';
// 实现语音主要五个步骤逻辑:
// 初始化引擎
// 开启音频权限,启用音频模块
// 创建房间
// 设置事件监听(成功加入房间,是否有用户加入,用户是否离开,用户是否掉线)
// 布局实现
// 退出语音(根据需要销毁引擎,释放资源)
class PalRoomPage extends StatefulWidget {
final String agoraToken;
final String userName;
final String channelName;
final ClientRole? role;
PalRoomPage({
Key? key,
this.agoraToken = '',
this.userName = '',
this.channelName = '',
this.role,
}) : super(key: key);
@override
State<PalRoomPage> createState() => _PalRoomPageState();
}
class _PalRoomPageState extends State<PalRoomPage> {
// final _users = <int>[]; // 用户id数组
final _infoStrings = []; // 状态文字信息列表
bool viewPanel = true; // 是否显示状态文字信息列表
bool muted = false; // 是否开麦
bool volume = true; // 音量
late RtcEngine _engine; // 声网实例变量
// 房间座位
List _list = [
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
];
// 连麦用户列表数据
List newUserList = [];
@override
void initState() {
super.initState();
_infoStrings.insert(0, {
'type': 'system',
'content':
'|令牌:${widget.agoraToken}|用户名:${widget.userName}|房间:${widget.channelName}|身份:${widget.role}'
});
initialize();
if (widget.role == ClientRole.Broadcaster) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
} else if (widget.role == ClientRole.Audience) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为观众,点击麦克风图标上麦'});
}
}
@override
void dispose() {
super.dispose();
// _users.clear();
_engine.leaveChannel();
_engine.destroy();
}
Future<void> initialize() async {
// 判断appid是否存在
if (APP_ID.isEmpty) {
setState(() {
_infoStrings.insert(0, {
'type': 'system',
'content': 'APP ID缺失请在settings.dart中提供您的APP ID'
});
_infoStrings.insert(0, {'type': 'system', 'content': 'Agora引擎没有启动'});
});
return;
}
_engine = await RtcEngine.create(APP_ID); // 初始化引擎
await _engine.enableAudio(); // 启用音频模块
await _engine.setDefaultAudioRouteToSpeakerphone(
true); // 设置默认的音频路由:该方法设置接收到的音频从听筒或扬声器出声。如果用户不调用本方法,音频默认从听筒出声。
await _engine.setAudioProfile(AudioProfile.SpeechStandard,
AudioScenario.ChatRoomEntertainment); // 设置音频编码属性和音频场景
await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
_addAgoraEventHandlers(); // 调用状态文字信息列表方法
if (widget.role == ClientRole.Broadcaster) {
await _engine.setClientRole(ClientRole.Broadcaster);
}
String uid = await Storage.get('id');
await _engine.joinChannel(
widget.agoraToken, widget.channelName, null, int.parse(uid)); // 加入频道
_detail();
}
// 获取房间详情接口
void _detail() async {
var res =
await MyHttpUtil().get("/chat/api/room/detail/${widget.channelName}");
// print('${res}');
var _userList = res.data['userList'];
// 赋值image字段头像
_userList.forEach((item) {
item['image'] = 'images/palRoom/photo.png';
item['flag'] = true;
});
// 筛选出身份类型为主播的用户
newUserList =
_userList.where((item) => item['type'] != 'audience').toList();
print('-----------------筛选出身份类型为主播的用户:${newUserList}-----------------');
for (var i = 0; i < _list.length; i++) {
if (newUserList.length < (i + 1)) {
setState(() {
_list[i] = {
'nickname': '',
'flag': false,
'image': 'images/palRoom/voice2.png'
};
});
} else {
setState(() {
_list[i] = newUserList[i];
});
}
}
print('-----------------${_list}-----------------');
}
void _addAgoraEventHandlers() {
_engine.setEventHandler(RtcEngineEventHandler(
error: (code) {
// 错误
setState(() {
final info = '错误 $code';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
userJoined: (uid, elapsed) {
_detail();
// 用户加入
setState(() {
final info = '用户加入: uid $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.add(uid);
});
},
joinChannelSuccess: (channel, uid, elapsed) {
_detail();
// 加入频道成功
setState(() {
final info = '加入频道成功: $channel, uid: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
leaveChannel: (stats) {
_detail();
// 离开频道
setState(() {
_infoStrings.insert(0, {'type': 'system', 'content': '离开频道'});
// _users.clear();
});
},
userOffline: (uid, reason) {
_detail();
// 用户离开当前频道
setState(() {
final info = '用户离开当前频道: $uid , reason: $reason';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.remove(uid);
});
},
clientRoleChanged: (oldRole, newRole) {
_detail();
// 直播场景下用户角色切换成功回调
setState(() {
final info = '用户旧身份$oldRole,用户新身份$newRole';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
rejoinChannelSuccess: (channel, uid, elapsed) {
_detail();
// 成功重新加入频道回调
setState(() {
final info = '用户成功重新加入: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.add(uid);
});
},
warning: (warn) {
// 报告 RtcChannel 的警告码
print(warn);
},
tokenPrivilegeWillExpire: (token) {
// Token 服务将在30s内过期回调
// app 应重新获取 Token然后调用 _engine.renewToken(token) 将新的 Token 传给 SDK
print('即将服务失效的 Token:${token}');
},
requestToken: () {
// Token 已过期回调
setState(() {
final info = 'Token 已过期';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
));
}
// 头部
Widget _head() {
// 底部弹框组件
_modelBottomSheet(index) async {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 150.0,
child: Column(
children: [
_list[index]['flag'] == false
? ListTile(
title: Text(
"连线上麦",
textAlign: TextAlign.center,
),
onTap: () {
// setState(() {
// for (var i = 0; i < _list.length; i++) {
// if (i == index) {
// _list[index]['flag'] = true;
// _list[index]['nickname'] = '_users';
// _list[index]['image'] =
// 'images/palRoom/photo.png';
// } else {
// _list[i]['flag'] = false;
// _list[i]['name'] = '';
// _list[i]['image'] = 'images/palRoom/voice2.png';
// }
// }
// });
// 申请连麦需要改变身份为主播!
_engine.setClientRole(ClientRole.Broadcaster);
setState(() {
_list[index]['flag'] = true;
});
Navigator.pop(context);
},
)
: ListTile(
title: Text(
"下麦",
textAlign: TextAlign.center,
),
onTap: () {
setState(() {
_list[index]['flag'] = false;
_list[index]['nickname'] = '';
_list[index]['image'] = 'images/palRoom/voice2.png';
});
// 下麦再将身份改回为观众(下麦不代表离开频道)
_engine.setClientRole(ClientRole.Audience);
Navigator.pop(context);
},
),
ListTile(
title: Text(
"取消",
textAlign: TextAlign.center,
),
onTap: () {
Navigator.pop(context);
}),
],
),
);
},
);
}
return Container(
child: ListView(
children: [
// --房主 start --
Container(
padding: EdgeInsets.fromLTRB(0, 27, 0, 5),
alignment: Alignment.topCenter,
child: Image.asset(
'images/palRoom/photo.png',
width: 70,
height: 70,
),
),
Container(
margin: EdgeInsets.only(bottom: 30),
alignment: Alignment.topCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 18,
height: 18,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Color.fromRGBO(245, 173, 29, 1),
borderRadius: BorderRadius.circular(2),
),
child: Text(
'房',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
SizedBox(width: 5),
Text('以冬', style: TextStyle(color: Colors.white, fontSize: 14)),
],
),
),
// --房主 end --
// -- 网格 start --
Container(
// padding: EdgeInsets.only(left: 15, right: 15),
child: GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 40,
mainAxisSpacing: 30,
crossAxisCount: 4,
childAspectRatio: 1 / 1.25,
),
itemCount: _list.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.role == ClientRole.Audience) {
// 观众身份的时候点击上麦克图标
_modelBottomSheet(index); // 调用底部弹框方法
} else {
_infoStrings.insert(
0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
}
});
},
child: Column(
children: [
Container(
width: 55,
height: 55,
margin: EdgeInsets.only(bottom: 5),
decoration: BoxDecoration(
color: Color.fromRGBO(52, 51, 69, 1),
borderRadius: BorderRadius.circular(50),
),
child: Center(
child: Image.asset(
_list[index]['image'],
width: _list[index]['flag'] ? 55 : 14,
height: _list[index]['flag'] ? 55 : 21,
),
),
),
Text(
_list[index]['nickname'],
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 12),
),
],
),
);
},
),
),
// -- 网格 end --
],
),
);
}
// 消息列表
Widget _planel() {
_filtering(type, content, {name}) {
switch (type) {
case 'system':
return Container(
child: Text(
'系统公告:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
case 'speak':
return Container(
child: Text(
'${name}的聊天消息:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
case 'gift':
return Container(
child: Text(
'礼物消息:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
}
}
return Visibility(
visible: viewPanel,
child: Container(
padding: EdgeInsets.symmetric(vertical: 60),
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
// decoration: BoxDecoration(
// color: Colors.black,
// ),
child: ListView.builder(
reverse: true,
itemCount: _infoStrings.length,
itemBuilder: (BuildContext context, int index) {
if (_infoStrings.isEmpty) {
return Text('null');
}
return Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Container(
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 10,
),
decoration: BoxDecoration(
color: Color.fromRGBO(50, 51, 71, 1),
borderRadius: BorderRadius.circular(5),
),
child: _filtering(_infoStrings[index]['type'],
_infoStrings[index]['content'],
name: _infoStrings[index]['name']),
),
),
],
),
);
},
),
),
),
),
);
}
// 底部输入框控制台
Widget _footer() {
final _input = TextEditingController();
return Container(
width: double.infinity,
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(
vertical: 18,
horizontal: 15,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
GestureDetector(
// 音量图标
onTap: () {
setState(() {
volume = !volume;
});
_engine.adjustPlaybackSignalVolume(
volume ? 100 : 0); // 调节本地播放的所有远端用户信号音量
},
child: Icon(volume ? Icons.volume_up : Icons.volume_off,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
GestureDetector(
// 麦克风图标
onTap: () {
setState(() {
muted = !muted;
});
_engine.muteLocalAudioStream(muted); // 取消或恢复发布本地音频流(是否静音)
},
child: Icon(muted ? Icons.mic_off : Icons.mic,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
GestureDetector(
// 表情图标
child: Icon(Icons.tag_faces,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
Container(
// 输入框
width: 220,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Color.fromRGBO(52, 51, 69, 1),
borderRadius: BorderRadius.circular(30),
),
child: TextField(
onSubmitted: (value) async {
late String speak = value.trim();
if (speak.isEmpty) {
showToast("请输入聊天内容~");
return;
}
String userName = await Storage.get('userName'); // 用户名
setState(() {
_infoStrings.insert(0, {
'type': 'speak',
'content': '${speak}',
'name': '${userName}'
});
});
},
controller: _input,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: '我来说几句',
hintStyle: TextStyle(
fontSize: 12,
color: Color.fromRGBO(138, 138, 146, 1),
),
),
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
textInputAction: TextInputAction.send, // 键盘右下角图标
),
),
GestureDetector(
// 加号图标
child: Icon(Icons.add,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
print('离开');
_engine.setClientRole(ClientRole.Audience);
_engine.leaveChannel();
Navigator.of(context).pop();
}),
elevation: 0, // z轴阴影
titleSpacing: 0, // 标题与其他控件的间隔
backgroundColor: Color.fromRGBO(27, 28, 48, 1),
title: Container(
height: 60,
decoration: BoxDecoration(
color: Color.fromRGBO(27, 28, 48, 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
child: Padding(
padding: EdgeInsets.only(right: 5),
child: ClipOval(
child: Image.asset(
'images/palRoom/photo.png',
width: 46,
height: 46,
),
),
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
children: [
Text(
'以冬',
style: TextStyle(color: Colors.white, fontSize: 12),
),
SizedBox(width: 5),
Text(
'ID309060',
style: TextStyle(
color: Color.fromRGBO(242, 174, 30, 1),
fontSize: 10,
),
),
],
),
Row(
children: [
Text.rich(
TextSpan(
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
children: [
TextSpan(text: '交友'),
TextSpan(text: ' | '),
TextSpan(text: '房间名:成人避风港'),
]),
),
SizedBox(width: 5),
Container(
padding: EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Color.fromRGBO(47, 47, 59, 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'在线121',
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
),
SizedBox(width: 5),
Image.asset(
'images/palRoom/whiteRight.png',
width: 5,
height: 8,
),
],
),
)
],
),
],
),
)
],
),
),
actions: [
IconButton(
onPressed: () {
print('more图标被点击');
setState(() {
viewPanel = !viewPanel;
});
},
icon: Image.asset(
'images/palRoom/more.png',
fit: BoxFit.cover,
width: 20.0,
height: 5.0,
),
),
],
),
backgroundColor: Color.fromRGBO(27, 28, 48, 1),
body: Stack(
children: [
_head(),
_planel(),
_footer(),
],
),
);
}
}