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

561 lines
19 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 = <String>[]; // 状态文字信息列表
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, '最新插入的item-${widget.userName}-${widget.channelName}-${widget.role}');
if (widget.role == ClientRole.Broadcaster) {
// 判断若是主播,进入就初始化引擎并连接上麦
initialize();
_infoStrings.insert(0, '您身份为主播,已经上麦状态');
} else if (widget.role == ClientRole.Audience) {
_infoStrings.insert(0, '您身份为观众,点击麦克风图标上麦'); // 判断若非主播,进入提示连麦
}
}
@override
void dispose() {
super.dispose();
_users.clear();
_engine.leaveChannel();
_engine.destroy();
}
Future<void> initialize() async {
// 判断appid是否存在
if (APP_ID.isEmpty) {
setState(() {
_infoStrings.insert(0, 'APP ID缺失请在settings.dart中提供您的APP ID');
_infoStrings.insert(0, '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);
await _engine.setClientRole(widget.role!);
_addAgoraEventHandlers(); // 调用状态文字信息列表方法
_engine.joinChannel(Token, widget.channelName, null, 0); // 加入频道
}
void _addAgoraEventHandlers() {
_engine.setEventHandler(RtcEngineEventHandler(
error: (code) {
// 错误
setState(() {
final info = '错误 $code';
_infoStrings.insert(0, info);
});
},
joinChannelSuccess: (channel, uid, elapsed) {
// 加入频道成功
setState(() {
final info = '加入频道: $channel, uid: $uid';
_infoStrings.insert(0, info);
});
},
leaveChannel: (stats) {
// 离开频道
setState(() {
_infoStrings.insert(0, '离开频道');
_users.clear();
});
},
userJoined: (uid, elapsed) {
// 用户加入
setState(() {
final info = '用户加入: $uid';
_infoStrings.insert(0, info);
_users.add(uid);
});
},
userOffline: (uid, reason) {
// 用户离线
setState(() {
final info = '用户离线: $uid , reason: $reason';
_infoStrings.insert(0, info);
_users.remove(uid);
});
},
firstRemoteVideoFrame: (uid, width, height, elapsed) {
// 远程视频第一帧
setState(() {
final info = 'First Remote Video Frame: $uid';
_infoStrings.insert(0, info);
});
},
));
}
Widget _head() {
return Container(
child: Column(
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: 35,
crossAxisCount: 4,
childAspectRatio: 1 / 1.2,
),
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.role == ClientRole.Audience) {
// 观众身份的时候点击上麦克图标
_modelBottomSheet(index); // 调用底部弹框方法
} else {
_infoStrings.insert(0, '您身份为主播,已经上麦状态');
}
});
},
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() {
return Visibility(
visible: viewPanel,
child: Container(
padding: EdgeInsets.symmetric(vertical: 48),
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
// decoration: BoxDecoration(
// color: Colors.black,
// ),
// padding: EdgeInsets.symmetric(vertical: 48),
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: Text(
_infoStrings[index],
style: TextStyle(
color: Color.fromRGBO(155, 154, 170, 1),
),
),
),
),
],
),
);
},
),
),
),
),
);
}
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, 1, 6, 1),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Color.fromRGBO(47, 47, 59, 1),
),
child: Row(
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(),
],
),
);
}
// 底部弹框方法
_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';
}
}
});
initialize();
_engine.joinChannel(
Token, widget.channelName, null, 0); // 加入频道
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.leaveChannel();
print("离开下麦:${list[index]['content']}");
Navigator.pop(context);
},
),
ListTile(
title: Text(
"取消",
textAlign: TextAlign.center,
),
onTap: () {
Navigator.pop(context);
}),
],
),
);
},
);
}
}