meeting.dart 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:path_provider/path_provider.dart';
  7. import './setting.dart';
  8. import 'package:tencent_trtc_cloud/trtc_cloud_video_view.dart';
  9. import 'package:tencent_trtc_cloud/trtc_cloud.dart';
  10. import 'package:tencent_trtc_cloud/tx_beauty_manager.dart';
  11. import 'package:tencent_trtc_cloud/tx_device_manager.dart';
  12. import 'package:tencent_trtc_cloud/tx_audio_effect_manager.dart';
  13. import 'package:tencent_trtc_cloud/trtc_cloud_def.dart';
  14. import 'package:tencent_trtc_cloud/trtc_cloud_listener.dart';
  15. import 'package:trtc_demo/page/trtcmeetingdemo/index.dart';
  16. import 'package:trtc_demo/page/trtcmeetingdemo/tool.dart';
  17. import 'package:trtc_demo/models/meeting.dart';
  18. import 'package:trtc_demo/debug/GenerateTestUserSig.dart';
  19. import 'package:provider/provider.dart';
  20. import 'package:replay_kit_launcher/replay_kit_launcher.dart';
  21. const iosAppGroup = 'group.com.tencent.comm.trtc.demo';
  22. const iosExtensionName = 'TRTC Demo Screen';
  23. /// Meeting Page
  24. class MeetingPage extends StatefulWidget {
  25. @override
  26. State<StatefulWidget> createState() => MeetingPageState();
  27. }
  28. class MeetingPageState extends State<MeetingPage> with WidgetsBindingObserver {
  29. final _scaffoldKey = GlobalKey<ScaffoldState>();
  30. var meetModel;
  31. var userInfo = {}; //Multiplayer video user list
  32. bool isOpenMic = true; //whether turn on the microphone
  33. bool isOpenCamera = true; //whether turn on the video
  34. bool isFrontCamera = true; //front camera
  35. bool isSpeak = true;
  36. bool isDoubleTap = false;
  37. bool isShowingWindow = false;
  38. int? localViewId;
  39. bool isShowBeauty = true; //whether enable beauty settings
  40. String curBeauty = 'pitu';
  41. double curBeautyValue = 6; //The default beauty value is 6
  42. String doubleUserId = "";
  43. String doubleUserIdType = "";
  44. late TRTCCloud trtcCloud;
  45. late TXDeviceManager txDeviceManager;
  46. late TXBeautyManager txBeautyManager;
  47. late TXAudioEffectManager txAudioManager;
  48. List userList = [];
  49. List userListLast = [];
  50. List screenUserList = [];
  51. int? meetId;
  52. int quality = TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT;
  53. Offset _offset = Offset(0, 0);
  54. late ScrollController scrollControl;
  55. @override
  56. initState() {
  57. super.initState();
  58. WidgetsBinding.instance.addObserver(this);
  59. meetModel = context.read<MeetingModel>();
  60. var userSetting = meetModel.getUserSetting();
  61. meetId = userSetting["meetId"];
  62. userInfo['userId'] = userSetting["userId"];
  63. isOpenCamera = userSetting["enabledCamera"];
  64. isOpenMic = userSetting["enabledMicrophone"];
  65. iniRoom();
  66. initScrollListener();
  67. }
  68. iniRoom() async {
  69. // Create TRTCCloud singleton
  70. trtcCloud = (await TRTCCloud.sharedInstance())!;
  71. // Tencent Cloud Audio Effect Management Module
  72. txDeviceManager = trtcCloud.getDeviceManager();
  73. // Beauty filter and animated effect parameter management
  74. txBeautyManager = trtcCloud.getBeautyManager();
  75. // Tencent Cloud Audio Effect Management Module
  76. txAudioManager = trtcCloud.getAudioEffectManager();
  77. // Register event callback
  78. trtcCloud.registerListener(onRtcListener);
  79. // Enter the room
  80. enterRoom();
  81. initData();
  82. //Set beauty effect
  83. txBeautyManager.setBeautyStyle(TRTCCloudDef.TRTC_BEAUTY_STYLE_NATURE);
  84. txBeautyManager.setBeautyLevel(6);
  85. }
  86. @override
  87. void didChangeAppLifecycleState(AppLifecycleState state) {
  88. switch (state) {
  89. case AppLifecycleState.inactive:
  90. break;
  91. case AppLifecycleState
  92. .resumed: //Switch from the background to the foreground, and the interface is visible
  93. if (!kIsWeb && Platform.isAndroid) {
  94. userListLast = jsonDecode(jsonEncode(userList));
  95. userList = [];
  96. screenUserList = MeetingTool.getScreenList(userList);
  97. this.setState(() {});
  98. const timeout = const Duration(milliseconds: 100); //10ms
  99. Timer(timeout, () {
  100. userList = userListLast;
  101. screenUserList = MeetingTool.getScreenList(userList);
  102. this.setState(() {});
  103. });
  104. }
  105. break;
  106. case AppLifecycleState.paused: // Interface invisible, background
  107. break;
  108. case AppLifecycleState.detached:
  109. break;
  110. }
  111. }
  112. // Enter the trtc room
  113. enterRoom() async {
  114. try {
  115. userInfo['userSig'] =
  116. await GenerateTestUserSig.genTestSig(userInfo['userId']);
  117. meetModel.setUserInfo(userInfo);
  118. } catch (err) {
  119. userInfo['userSig'] = '';
  120. print(err);
  121. }
  122. await trtcCloud.enterRoom(
  123. TRTCParams(
  124. sdkAppId: GenerateTestUserSig.sdkAppId,
  125. userId: userInfo['userId'],
  126. userSig: userInfo['userSig'],
  127. role: TRTCCloudDef.TRTCRoleAnchor,
  128. roomId: meetId!),
  129. TRTCCloudDef.TRTC_APP_SCENE_LIVE);
  130. }
  131. initData() async {
  132. if (isOpenCamera) {
  133. userList.add({
  134. 'userId': userInfo['userId'],
  135. 'type': 'video',
  136. 'visible': true,
  137. 'size': {'width': 0, 'height': 0}
  138. });
  139. } else {
  140. userList.add({
  141. 'userId': userInfo['userId'],
  142. 'type': 'video',
  143. 'visible': false,
  144. 'size': {'width': 0, 'height': 0}
  145. });
  146. }
  147. if (isOpenMic) {
  148. if (kIsWeb) {
  149. Future.delayed(Duration(seconds: 2), () {
  150. trtcCloud.startLocalAudio(quality);
  151. });
  152. } else {
  153. await trtcCloud.startLocalAudio(quality);
  154. }
  155. }
  156. screenUserList = MeetingTool.getScreenList(userList);
  157. meetModel.setList(userList);
  158. this.setState(() {});
  159. }
  160. destoryRoom() async {
  161. trtcCloud.unRegisterListener(onRtcListener);
  162. await trtcCloud.exitRoom();
  163. await TRTCCloud.destroySharedInstance();
  164. }
  165. @override
  166. dispose() {
  167. WidgetsBinding.instance.removeObserver(this);
  168. destoryRoom();
  169. scrollControl.dispose();
  170. super.dispose();
  171. }
  172. /// Event callbacks
  173. onRtcListener(type, param) async {
  174. if (type == TRTCCloudListener.onError) {
  175. if (param['errCode'] == -1308) {
  176. MeetingTool.toast('Failed to start screen recording', context);
  177. await trtcCloud.stopScreenCapture();
  178. userList[0]['visible'] = true;
  179. isShowingWindow = false;
  180. this.setState(() {});
  181. trtcCloud.startLocalPreview(isFrontCamera, localViewId);
  182. } else {
  183. showErrordDialog(param['errMsg']);
  184. }
  185. }
  186. if (type == TRTCCloudListener.onEnterRoom) {
  187. if (param > 0) {
  188. MeetingTool.toast('Enter room success', context);
  189. }
  190. }
  191. if (type == TRTCCloudListener.onExitRoom) {
  192. if (param > 0) {
  193. MeetingTool.toast('Exit room success', context);
  194. }
  195. }
  196. // Remote user entry
  197. if (type == TRTCCloudListener.onRemoteUserEnterRoom) {
  198. userList.add({
  199. 'userId': param,
  200. 'type': 'video',
  201. 'visible': false,
  202. 'size': {'width': 0, 'height': 0}
  203. });
  204. screenUserList = MeetingTool.getScreenList(userList);
  205. this.setState(() {});
  206. meetModel.setList(userList);
  207. }
  208. // Remote user leaves room
  209. if (type == TRTCCloudListener.onRemoteUserLeaveRoom) {
  210. String userId = param['userId'];
  211. for (var i = 0; i < userList.length; i++) {
  212. if (userList[i]['userId'] == userId) {
  213. userList.removeAt(i);
  214. }
  215. }
  216. //The user who is amplifying the video exit room
  217. if (doubleUserId == userId) {
  218. isDoubleTap = false;
  219. }
  220. screenUserList = MeetingTool.getScreenList(userList);
  221. this.setState(() {});
  222. meetModel.setList(userList);
  223. }
  224. if (type == TRTCCloudListener.onUserVideoAvailable) {
  225. String userId = param['userId'];
  226. if (param['available']) {
  227. for (var i = 0; i < userList.length; i++) {
  228. if (userList[i]['userId'] == userId &&
  229. userList[i]['type'] == 'video') {
  230. userList[i]['visible'] = true;
  231. }
  232. }
  233. } else {
  234. for (var i = 0; i < userList.length; i++) {
  235. if (userList[i]['userId'] == userId &&
  236. userList[i]['type'] == 'video') {
  237. if (isDoubleTap &&
  238. doubleUserId == userList[i]['userId'] &&
  239. doubleUserIdType == userList[i]['type']) {
  240. doubleTap(userList[i]);
  241. }
  242. trtcCloud.stopRemoteView(
  243. userId, TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_BIG);
  244. userList[i]['visible'] = false;
  245. }
  246. }
  247. }
  248. screenUserList = MeetingTool.getScreenList(userList);
  249. this.setState(() {});
  250. meetModel.setList(userList);
  251. }
  252. if (type == TRTCCloudListener.onUserSubStreamAvailable) {
  253. String userId = param["userId"];
  254. if (param["available"]) {
  255. userList.add({
  256. 'userId': userId,
  257. 'type': 'subStream',
  258. 'visible': true,
  259. 'size': {'width': 0, 'height': 0}
  260. });
  261. } else {
  262. for (var i = 0; i < userList.length; i++) {
  263. if (userList[i]['userId'] == userId &&
  264. userList[i]['type'] == 'subStream') {
  265. if (isDoubleTap &&
  266. doubleUserId == userList[i]['userId'] &&
  267. doubleUserIdType == userList[i]['type']) {
  268. doubleTap(userList[i]);
  269. }
  270. trtcCloud.stopRemoteView(
  271. userId, TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB);
  272. userList.removeAt(i);
  273. }
  274. }
  275. }
  276. screenUserList = MeetingTool.getScreenList(userList);
  277. this.setState(() {});
  278. meetModel.setList(userList);
  279. }
  280. }
  281. // Screen scrolling left and right event
  282. initScrollListener() {
  283. scrollControl = ScrollController();
  284. double lastOffset = 0;
  285. scrollControl.addListener(() async {
  286. double screenWidth = MediaQuery.of(context).size.width;
  287. int pageSize = (scrollControl.offset / screenWidth).ceil();
  288. if (lastOffset < scrollControl.offset) {
  289. scrollControl.animateTo(pageSize * screenWidth,
  290. duration: Duration(milliseconds: 100), curve: Curves.ease);
  291. if (scrollControl.offset == pageSize * screenWidth) {
  292. //Slide from left to right
  293. for (var i = 1; i < pageSize * MeetingTool.screenLen; i++) {
  294. await trtcCloud.stopRemoteView(
  295. userList[i]['userId'],
  296. userList[i]['type'] == "video"
  297. ? TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_BIG
  298. : TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB);
  299. }
  300. }
  301. } else {
  302. scrollControl.animateTo((pageSize - 1) * screenWidth,
  303. duration: Duration(milliseconds: 100), curve: Curves.ease);
  304. if (scrollControl.offset == pageSize * screenWidth) {
  305. var pageScreen = screenUserList[pageSize];
  306. int initI = 0;
  307. if (pageSize == 0) {
  308. initI = 1;
  309. }
  310. for (var i = initI; i < pageScreen.length; i++) {
  311. await trtcCloud.startRemoteView(
  312. pageScreen[i]['userId'],
  313. pageScreen[i]['type'] == "video"
  314. ? TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_BIG
  315. : TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB,
  316. pageScreen[i]['viewId']);
  317. }
  318. }
  319. }
  320. lastOffset = scrollControl.offset;
  321. });
  322. }
  323. Future<bool?> showErrordDialog(errorMsg) {
  324. return showDialog<bool>(
  325. context: context,
  326. barrierDismissible: false,
  327. builder: (context) {
  328. return AlertDialog(
  329. title: Text("Tips"),
  330. content: Text(errorMsg),
  331. actions: <Widget>[
  332. TextButton(
  333. child: Text("Confirm"),
  334. onPressed: () {
  335. Navigator.push(
  336. context,
  337. MaterialPageRoute(
  338. builder: (context) => IndexPage(),
  339. ),
  340. );
  341. },
  342. ),
  343. ],
  344. );
  345. },
  346. );
  347. }
  348. Future<bool?> showExitMeetingConfirmDialog() {
  349. return showDialog<bool>(
  350. context: context,
  351. builder: (context) {
  352. return AlertDialog(
  353. title: Text("Tips"),
  354. content: Text("Are you sure to exit the meeting?"),
  355. actions: <Widget>[
  356. TextButton(
  357. child: Text("Cancel"),
  358. onPressed: () => Navigator.of(context).pop(),
  359. ),
  360. TextButton(
  361. child: Text("Confirm"),
  362. onPressed: () async {
  363. /// close trtc
  364. await trtcCloud.stopPublishing();
  365. await trtcCloud.stopLocalPreview();
  366. await trtcCloud.stopAllRemoteView();
  367. await trtcCloud.stopLocalAudio();
  368. await trtcCloud.exitRoom();
  369. Navigator.of(context).pop(true);
  370. },
  371. ),
  372. ],
  373. );
  374. },
  375. );
  376. }
  377. // Double click zoom in and zoom out
  378. doubleTap(item) async {
  379. Size screenSize = MediaQuery.of(context).size;
  380. if (isDoubleTap) {
  381. userList.remove(item);
  382. isDoubleTap = false;
  383. doubleUserId = "";
  384. doubleUserIdType = "";
  385. item['size'] = {'width': 0, 'height': 0};
  386. } else {
  387. userList.remove(item);
  388. isDoubleTap = true;
  389. doubleUserId = item['userId'];
  390. doubleUserIdType = item['type'];
  391. item['size'] = {'width': screenSize.width, 'height': screenSize.height};
  392. }
  393. // userself
  394. if (item['userId'] == userInfo['userId']) {
  395. userList.insert(0, item);
  396. if (!kIsWeb && Platform.isIOS) {
  397. await trtcCloud.stopLocalPreview();
  398. }
  399. } else {
  400. userList.add(item);
  401. if (item['type'] == 'video') {
  402. await trtcCloud.stopRemoteView(
  403. item['userId'], TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_BIG);
  404. } else {
  405. await trtcCloud.stopRemoteView(
  406. item['userId'], TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB);
  407. }
  408. if (isDoubleTap) {
  409. userList[0]['visible'] = false;
  410. } else {
  411. if (!kIsWeb && Platform.isIOS) {
  412. await trtcCloud.stopLocalPreview();
  413. }
  414. if (isOpenCamera) {
  415. userList[0]['visible'] = true;
  416. }
  417. }
  418. }
  419. this.setState(() {});
  420. }
  421. startShare({String shareUserId = '', String shareUserSig = ''}) async {
  422. if (shareUserId == '') await trtcCloud.stopLocalPreview();
  423. trtcCloud.startScreenCapture(
  424. TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB,
  425. TRTCVideoEncParam(
  426. videoFps: 10,
  427. videoResolution: TRTCCloudDef.TRTC_VIDEO_RESOLUTION_1280_720,
  428. videoBitrate: 1600,
  429. videoResolutionMode: TRTCCloudDef.TRTC_VIDEO_RESOLUTION_MODE_PORTRAIT,
  430. ),
  431. appGroup: iosAppGroup,
  432. shareUserId: shareUserId,
  433. shareUserSig: shareUserSig,
  434. );
  435. }
  436. onShareClick() async {
  437. if (kIsWeb) {
  438. String shareUserId = 'share-' + userInfo['userId'];
  439. String shareUserSig = await GenerateTestUserSig.genTestSig(shareUserId);
  440. await startShare(shareUserId: shareUserId, shareUserSig: shareUserSig);
  441. } else if (!kIsWeb && Platform.isAndroid) {
  442. if (!isShowingWindow) {
  443. await startShare();
  444. userList[0]['visible'] = false;
  445. this.setState(() {
  446. isShowingWindow = true;
  447. isOpenCamera = false;
  448. });
  449. } else {
  450. await trtcCloud.stopScreenCapture();
  451. userList[0]['visible'] = true;
  452. trtcCloud.startLocalPreview(isFrontCamera, localViewId);
  453. this.setState(() {
  454. isShowingWindow = false;
  455. isOpenCamera = true;
  456. });
  457. }
  458. } else {
  459. await startShare();
  460. //The screen sharing function can only be tested on the real machine
  461. ReplayKitLauncher.launchReplayKitBroadcast(iosExtensionName);
  462. this.setState(() {
  463. isOpenCamera = false;
  464. });
  465. }
  466. }
  467. bool isRecording = false;
  468. onRecordStart() async {
  469. if (kIsWeb) return;
  470. if (isRecording) return;
  471. isRecording = true;
  472. final captureInterval = 1000;
  473. final recordStoragePath = await getFileStorageDir();
  474. while (isRecording) {
  475. await Future.delayed(Duration(milliseconds: captureInterval));
  476. String suffix = DateTime.now().millisecondsSinceEpoch.toString();
  477. String filePath = "$recordStoragePath/${suffix}_temp.jpg";
  478. await trtcCloud.snapshotVideo(
  479. null,
  480. 0,
  481. filePath,
  482. );
  483. print("snapshotVideo: $filePath");
  484. }
  485. listFiles(recordStoragePath ?? '');
  486. }
  487. onRecordStop() async {
  488. isRecording = false;
  489. }
  490. Future<String?> getFileStorageDir() async {
  491. String tempDir = await getVidCaptureDir();
  492. String suffix = DateTime.now().millisecondsSinceEpoch.toString();
  493. final String parentPath = "${suffix}_vid_capture_temp";
  494. final String dirPath = "$tempDir/$parentPath";
  495. Directory(dirPath).createSync(recursive: true);
  496. return dirPath;
  497. }
  498. /// Vid Capture 专属临时目录
  499. Future<String> getVidCaptureDir() async {
  500. Directory tempDir = await getTemporaryDirectory();
  501. return "${tempDir.path}/vid_capture";
  502. }
  503. Future<void> listFiles(String path) async {
  504. final dir = Directory(path);
  505. if (await dir.exists()) {
  506. await for (var entity in dir.list(recursive: true, followLinks: false)) {
  507. if (entity is File) {
  508. print(entity.path);
  509. }
  510. }
  511. }
  512. }
  513. Widget renderView(item, valueKey, int width, int height) {
  514. if (item['visible']) {
  515. return GestureDetector(
  516. key: valueKey,
  517. onDoubleTap: () {
  518. doubleTap(item);
  519. },
  520. child: TRTCCloudVideoView(
  521. key: valueKey,
  522. /// 视图类型:[TRTC_VideoView_TextureView]/[TRTC_VideoView_SurfaceView]
  523. /// 使用 TRTC_VideoView_TextureView 模式的话不会触发 onViewCreated 回调
  524. /// 使用 TRTC_VideoView_SurfaceView 模式的话会触发 onViewCreated 回调,此模式为平台视图,不支持自定义渲染
  525. /// iOS 不支持 TRTC_VideoView_SurfaceView
  526. /// Windows 和 macOS 只支持 TRTC_VideoView_TextureView
  527. viewType: TRTCCloudDef.TRTC_VideoView_Texture,
  528. /// 视图模式:[TRTC_VideoView_Model_Virtual]/[TRTC_VideoView_Model_Hybrid]
  529. /// 使用 TRTC_VideoView_Model_Virtual 虚拟渲染模式使用的是 AndroidView
  530. /// 使用 TRTC_VideoView_Model_Hybrid 混合渲染模式使用的是 PlatformViewLink 【会闪退,不推荐使用】
  531. viewMode: TRTCCloudDef.TRTC_VideoView_Model_Virtual,
  532. // This parameter is required for rendering desktop.(Android/iOS/web no need)
  533. textureParam: CustomRender(
  534. userId: item['userId'],
  535. isLocal: item['userId'] == userInfo['userId'] ? true : false,
  536. streamType: item['type'] == 'video'
  537. ? TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_BIG
  538. : TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB,
  539. width: width,
  540. height: height,
  541. //// TOTODODO 加一个渲染条件字段 :副窗口
  542. // isSubVideo: false,
  543. ),
  544. onViewCreated: (viewId) async {
  545. print('🚀 视图创建完成 viewId: $viewId');
  546. if (item['userId'] == userInfo['userId']) {
  547. print('🚀 通知 native 创建本地预览的 Texture 通道');
  548. await trtcCloud.startLocalPreview(isFrontCamera, viewId);
  549. setState(() {
  550. localViewId = viewId;
  551. });
  552. } else {
  553. trtcCloud.startRemoteView(
  554. item['userId'],
  555. item['type'] == 'video'
  556. ? TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_BIG
  557. : TRTCCloudDef.TRTC_VIDEO_STREAM_TYPE_SUB,
  558. viewId);
  559. print('🚀 通知 native 创建远程视频的 Texture 通道');
  560. print('🚀 远程用户ID: ${item['userId']}');
  561. print('🚀 远程视频流类型: ${item['type']}');
  562. }
  563. item['viewId'] = viewId;
  564. }));
  565. } else {
  566. return Container(
  567. alignment: Alignment.center,
  568. child: ClipOval(
  569. child: Image.asset('images/avatar3_100.20191230.png', scale: 3.5),
  570. ),
  571. );
  572. }
  573. }
  574. /// The user name and sound are displayed on the video layer
  575. Widget videoVoice(item) {
  576. return Positioned(
  577. child: new Container(
  578. child: Row(children: <Widget>[
  579. Text(
  580. item['userId'] == userInfo['userId']
  581. ? item['userId'] + "(me)"
  582. : item['userId'],
  583. style: TextStyle(color: Colors.white),
  584. ),
  585. Container(
  586. margin: EdgeInsets.only(left: 10),
  587. child: Icon(
  588. Icons.signal_cellular_alt,
  589. color: Colors.white,
  590. size: 20,
  591. ),
  592. ),
  593. ])),
  594. left: 24.0,
  595. bottom: 80.0,
  596. );
  597. }
  598. Widget topSetting() {
  599. return new Align(
  600. child: new Container(
  601. margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
  602. child: new Row(
  603. mainAxisAlignment: MainAxisAlignment.spaceAround,
  604. children: <Widget>[
  605. IconButton(
  606. icon: Icon(
  607. isSpeak ? Icons.volume_up : Icons.hearing,
  608. color: Colors.white,
  609. size: 36.0,
  610. ),
  611. onPressed: () async {
  612. if (isSpeak) {
  613. txDeviceManager.setAudioRoute(
  614. TRTCCloudDef.TRTC_AUDIO_ROUTE_EARPIECE);
  615. } else {
  616. txDeviceManager
  617. .setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER);
  618. }
  619. setState(() {
  620. isSpeak = !isSpeak;
  621. });
  622. }),
  623. IconButton(
  624. icon: Icon(
  625. Icons.camera_alt,
  626. color: Colors.white,
  627. size: 36.0,
  628. ),
  629. onPressed: () async {
  630. if (isFrontCamera) {
  631. txDeviceManager.switchCamera(false);
  632. } else {
  633. txDeviceManager.switchCamera(true);
  634. }
  635. setState(() {
  636. isFrontCamera = !isFrontCamera;
  637. });
  638. }),
  639. Text(meetId.toString(),
  640. style: TextStyle(fontSize: 20, color: Colors.white)),
  641. TextButton(
  642. style: TextButton.styleFrom(
  643. backgroundColor: Colors.red,
  644. textStyle: const TextStyle(color: Colors.white)),
  645. onPressed: () async {
  646. bool? delete = await showExitMeetingConfirmDialog();
  647. if (delete != null) {
  648. Navigator.pop(context);
  649. }
  650. },
  651. child: Text(
  652. "Exit Meeting",
  653. style: TextStyle(fontSize: 16.0),
  654. ),
  655. )
  656. ],
  657. ),
  658. height: 50.0,
  659. color: Color.fromRGBO(200, 200, 200, 0.4),
  660. ),
  661. alignment: Alignment.topCenter);
  662. }
  663. ///Beauty setting floating layer
  664. Widget beautySetting() {
  665. return Positioned(
  666. bottom: 80,
  667. child: Offstage(
  668. offstage: isShowBeauty,
  669. child: Container(
  670. padding: EdgeInsets.all(10),
  671. color: Color.fromRGBO(0, 0, 0, 0.8),
  672. height: 100,
  673. width: MediaQuery.of(context).size.width,
  674. child: Column(
  675. children: [
  676. Row(children: [
  677. Expanded(
  678. flex: 2,
  679. child: Slider(
  680. value: curBeautyValue,
  681. min: 0,
  682. max: 9,
  683. divisions: 9,
  684. onChanged: (double value) {
  685. if (curBeauty == 'smooth' ||
  686. curBeauty == 'nature' ||
  687. curBeauty == 'pitu') {
  688. txBeautyManager.setBeautyLevel(value.round());
  689. } else if (curBeauty == 'whitening') {
  690. txBeautyManager.setWhitenessLevel(value.round());
  691. } else if (curBeauty == 'ruddy') {
  692. txBeautyManager.setRuddyLevel(value.round());
  693. }
  694. this.setState(() {
  695. curBeautyValue = value;
  696. });
  697. },
  698. ),
  699. ),
  700. Text(curBeautyValue.round().toString(),
  701. textAlign: TextAlign.center,
  702. style: TextStyle(color: Colors.white)),
  703. ]),
  704. Padding(
  705. padding: EdgeInsets.only(top: 10),
  706. child: Row(
  707. children: [
  708. GestureDetector(
  709. child: Container(
  710. alignment: Alignment.centerLeft,
  711. width: 80.0,
  712. child: Text(
  713. 'Smooth',
  714. style: TextStyle(
  715. color: curBeauty == 'smooth'
  716. ? Color.fromRGBO(64, 158, 255, 1)
  717. : Colors.white),
  718. ),
  719. ),
  720. onTap: () => this.setState(() {
  721. txBeautyManager.setBeautyStyle(
  722. TRTCCloudDef.TRTC_BEAUTY_STYLE_SMOOTH);
  723. curBeauty = 'smooth';
  724. curBeautyValue = 6;
  725. }),
  726. ),
  727. GestureDetector(
  728. child: Container(
  729. alignment: Alignment.centerLeft,
  730. width: 80.0,
  731. child: Text(
  732. 'Nature',
  733. style: TextStyle(
  734. color: curBeauty == 'nature'
  735. ? Color.fromRGBO(64, 158, 255, 1)
  736. : Colors.white),
  737. ),
  738. ),
  739. onTap: () => this.setState(() {
  740. txBeautyManager.setBeautyStyle(
  741. TRTCCloudDef.TRTC_BEAUTY_STYLE_NATURE);
  742. curBeauty = 'nature';
  743. curBeautyValue = 6;
  744. }),
  745. ),
  746. GestureDetector(
  747. child: Container(
  748. alignment: Alignment.centerLeft,
  749. width: 80.0,
  750. child: Text(
  751. 'Pitu',
  752. style: TextStyle(
  753. color: curBeauty == 'pitu'
  754. ? Color.fromRGBO(64, 158, 255, 1)
  755. : Colors.white),
  756. ),
  757. ),
  758. onTap: () => this.setState(() {
  759. txBeautyManager.setBeautyStyle(
  760. TRTCCloudDef.TRTC_BEAUTY_STYLE_PITU);
  761. curBeauty = 'pitu';
  762. curBeautyValue = 6;
  763. }),
  764. ),
  765. GestureDetector(
  766. child: Container(
  767. alignment: Alignment.centerLeft,
  768. width: 50.0,
  769. child: Text(
  770. 'Ruddy',
  771. style: TextStyle(
  772. color: curBeauty == 'ruddy'
  773. ? Color.fromRGBO(64, 158, 255, 1)
  774. : Colors.white),
  775. ),
  776. ),
  777. onTap: () => this.setState(() {
  778. txBeautyManager.setRuddyLevel(0);
  779. curBeauty = 'ruddy';
  780. curBeautyValue = 0;
  781. }),
  782. ),
  783. ],
  784. ),
  785. ),
  786. ],
  787. ),
  788. ),
  789. ),
  790. );
  791. }
  792. Widget bottomSetting() {
  793. return new Align(
  794. child: new Container(
  795. padding: EdgeInsets.fromLTRB(0, 0, 0, 20),
  796. child: new Row(
  797. mainAxisAlignment: MainAxisAlignment.spaceAround,
  798. children: <Widget>[
  799. IconButton(
  800. icon: Icon(
  801. isOpenMic ? Icons.mic : Icons.mic_off,
  802. color: Colors.white,
  803. size: 36.0,
  804. ),
  805. onPressed: () {
  806. if (isOpenMic) {
  807. trtcCloud.stopLocalAudio();
  808. } else {
  809. trtcCloud.startLocalAudio(quality);
  810. }
  811. setState(() {
  812. isOpenMic = !isOpenMic;
  813. });
  814. }),
  815. IconButton(
  816. icon: Icon(
  817. isOpenCamera ? Icons.videocam : Icons.videocam_off,
  818. color: Colors.white,
  819. size: 36.0,
  820. ),
  821. onPressed: () {
  822. if (isOpenCamera) {
  823. userList[0]['visible'] = false;
  824. trtcCloud.stopLocalPreview();
  825. if (isDoubleTap &&
  826. doubleUserId == userList[0]['userId']) {
  827. doubleTap(userList[0]);
  828. }
  829. } else {
  830. userList[0]['visible'] = true;
  831. }
  832. setState(() {
  833. isOpenCamera = !isOpenCamera;
  834. });
  835. }),
  836. IconButton(
  837. icon: Icon(
  838. Icons.face,
  839. color: Colors.white,
  840. size: 36.0,
  841. ),
  842. onPressed: () {
  843. this.setState(() {
  844. if (isShowBeauty) {
  845. isShowBeauty = false;
  846. } else {
  847. isShowBeauty = true;
  848. }
  849. });
  850. }),
  851. IconButton(
  852. icon: Icon(
  853. Icons.people,
  854. color: Colors.white,
  855. size: 36.0,
  856. ),
  857. onPressed: () {
  858. Navigator.pushNamed(context, '/memberList');
  859. }),
  860. IconButton(
  861. icon: Icon(
  862. Icons.share_rounded,
  863. color: Colors.white,
  864. size: 36.0,
  865. ),
  866. onPressed: () {
  867. this.onShareClick();
  868. },
  869. ),
  870. IconButton(
  871. icon: Icon(
  872. Icons.camera,
  873. color: Colors.white,
  874. size: 36.0,
  875. ),
  876. onPressed: () {
  877. this.onRecordStart();
  878. },
  879. ),
  880. IconButton(
  881. icon: Icon(
  882. Icons.stop_outlined,
  883. color: Colors.white,
  884. size: 36.0,
  885. ),
  886. onPressed: () {
  887. this.onRecordStop();
  888. },
  889. ),
  890. SettingPage(),
  891. ],
  892. ),
  893. height: 70.0,
  894. color: Color.fromRGBO(200, 200, 200, 0.4),
  895. ),
  896. alignment: Alignment.bottomCenter);
  897. }
  898. @override
  899. Widget build(BuildContext context) {
  900. return Scaffold(
  901. key: _scaffoldKey,
  902. body: WillPopScope(
  903. onWillPop: () async {
  904. trtcCloud.exitRoom();
  905. return true;
  906. },
  907. child: Stack(
  908. children: <Widget>[
  909. ListView.builder(
  910. scrollDirection: Axis.horizontal,
  911. physics: new ClampingScrollPhysics(),
  912. itemCount: screenUserList.length,
  913. cacheExtent: 0,
  914. controller: scrollControl,
  915. itemBuilder: (BuildContext context, index) {
  916. var item = screenUserList[index];
  917. return Container(
  918. width: MediaQuery.of(context).size.width,
  919. height: MediaQuery.of(context).size.height,
  920. color: Color.fromRGBO(19, 41, 75, 1),
  921. child: Wrap(
  922. children: List.generate(
  923. item.length,
  924. (index) => LayoutBuilder(
  925. key: ValueKey(item[index]['userId'] +
  926. item[index]['type'] +
  927. item[index]['size']['width'].toString()),
  928. builder: (BuildContext context,
  929. BoxConstraints constraints) {
  930. Size size = MeetingTool.getViewSize(
  931. MediaQuery.of(context).size,
  932. userList.length,
  933. index,
  934. item.length);
  935. double width = size.width;
  936. double height = size.height;
  937. int widthInt = 0;
  938. int heightInt = 0;
  939. if (isDoubleTap) {
  940. //Set the width and height of other video rendering to 1, otherwise the video will not be streamed
  941. if (item[index]['size']['width'] == 0) {
  942. width = 1;
  943. height = 1;
  944. }
  945. }
  946. ValueKey valueKey = ValueKey(item[index]['userId'] +
  947. item[index]['type'] +
  948. (isDoubleTap ? "1" : "0"));
  949. if (item[index]['size']['width'] > 0) {
  950. width = double.parse(
  951. item[index]['size']['width'].toString());
  952. height = double.parse(
  953. item[index]['size']['height'].toString());
  954. }
  955. widthInt = width.toInt();
  956. heightInt = height.toInt();
  957. return Container(
  958. key: valueKey,
  959. height: height,
  960. width: width,
  961. child: Stack(
  962. key: valueKey,
  963. children: <Widget>[
  964. renderView(item[index], valueKey, widthInt,
  965. heightInt),
  966. videoVoice(item[index])
  967. ],
  968. ),
  969. );
  970. },
  971. ),
  972. ),
  973. ),
  974. );
  975. }),
  976. topSetting(),
  977. beautySetting(),
  978. bottomSetting()
  979. ],
  980. ),
  981. ),
  982. );
  983. }
  984. }