will-play/lib/views/palRoom/PalRoom.dart

607 lines
21 KiB
Dart
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 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../utils/settings.dart';
// 实现语音主要五个步骤逻辑:
// 初始化引擎
// 开启音频权限,启用音频模块
// 创建房间
// 设置事件监听(成功加入房间,是否有用户加入,用户是否离开,用户是否掉线)
// 布局实现
// 退出语音(根据需要销毁引擎,释放资源)
class PalRoomPage extends StatefulWidget {
final String userName;
final String channelName;
final ClientRole? role;
PalRoomPage({Key? key, 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 = [
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
];
@override
void initState() {
super.initState();
_infoStrings.insert(0, {
'type': 'system',
'content': '信息-${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);
if (widget.role == ClientRole.Broadcaster) {
await _engine.setClientRole(ClientRole.Broadcaster);
}
_engine.joinChannel(Token, widget.channelName, null, 0); // 加入频道
_addAgoraEventHandlers(); // 调用状态文字信息列表方法
}
void _addAgoraEventHandlers() {
_engine.setEventHandler(RtcEngineEventHandler(
error: (code) {
// 错误
setState(() {
final info = '错误 $code';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
joinChannelSuccess: (channel, uid, elapsed) {
// 加入频道成功
setState(() {
final info = '加入频道: $channel, uid: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
leaveChannel: (stats) {
// 离开频道
setState(() {
_infoStrings.insert(0, {'type': 'system', 'content': '离开频道'});
_users.clear();
});
},
userJoined: (uid, elapsed) {
// 用户加入
setState(() {
final info = '用户加入: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
_users.add(uid);
});
},
userOffline: (uid, reason) {
// 用户离线
setState(() {
final info = '用户离线: $uid , reason: $reason';
_infoStrings.insert(0, {'type': 'system', 'content': info});
_users.remove(uid);
});
},
firstRemoteVideoFrame: (uid, width, height, elapsed) {
// 远程视频第一帧
setState(() {
final info = 'First Remote Video Frame: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
clientRoleChanged: (oldRole, newRole) {
// 直播场景下用户角色切换成功回调
setState(() {
final info = '用户旧身份$oldRole,用户新身份$newRole';
_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]['content'] == false
? ListTile(
title: Text(
"连线上麦",
textAlign: TextAlign.center,
),
onTap: () {
setState(() {
for (var i = 0; i < list.length; i++) {
if (i == index) {
list[index]['content'] = true;
list[index]['name'] = '_users';
list[index]['image'] =
'images/palRoom/photo.png';
} else {
list[i]['content'] = false;
list[i]['name'] = '';
list[i]['image'] = 'images/palRoom/voice2.png';
}
}
});
// 申请连麦需要改变身份为主播!
_engine.setClientRole(ClientRole.Broadcaster);
Navigator.pop(context);
print("连线上麦:${list[index]['content']}");
},
)
: ListTile(
title: Text(
"下麦",
textAlign: TextAlign.center,
),
onTap: () {
setState(() {
list[index]['content'] = false;
list[index]['name'] = '';
list[index]['image'] = 'images/palRoom/voice2.png';
});
// 下麦再将身份改回为观众(下麦不代表离开频道)
_engine.setClientRole(ClientRole.Audience);
Navigator.pop(context);
print("下麦:${list[index]['content']}");
},
),
ListTile(
title: Text(
"取消",
textAlign: TextAlign.center,
),
onTap: () {
Navigator.pop(context);
}),
],
),
);
},
);
}
return Container(
child: ListView(
children: [
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)),
],
),
),
Container(
child: GridView.builder(
padding: EdgeInsets.all(15),
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]['content'] ? 55 : 14,
height: list[index]['content'] ? 55 : 21,
),
),
),
Text(
list[index]['name'],
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 12),
),
],
),
);
;
},
),
),
],
),
);
}
// 消息列表
Widget _planel() {
_filtering(type, content) {
switch (type) {
case 'system':
return Container(
child: Text(
'系统公告:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
case 'speak':
return Container(
child: Text(
'聊天消息:$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']),
),
),
],
),
);
},
),
),
),
),
);
}
// 底部输入框控制台
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(
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(
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(),
],
),
);
}
}