From 68dcc99a55a1217c34648e4798b9ece7dce28955 Mon Sep 17 00:00:00 2001 From: RishiGandhi4 Date: Wed, 15 May 2024 16:57:24 +0530 Subject: [PATCH] transcription feature added --- android/app/build.gradle | 39 ++++---- assets/transcription.svg | 4 + gradle.properties | 1 + .../conference_meeting_screen.dart | 98 ++++++++++++++++--- .../one-to-one/one_to_one_meeting_screen.dart | 97 +++++++++++++++--- .../common/app_bar/meeting_appbar.dart | 3 +- .../common/app_bar/web_meeting_appbar.dart | 74 +++++++++++++- .../meeting_controls/meeting_action_bar.dart | 13 +++ pubspec.yaml | 2 +- 9 files changed, 283 insertions(+), 48 deletions(-) create mode 100644 assets/transcription.svg create mode 100644 gradle.properties diff --git a/android/app/build.gradle b/android/app/build.gradle index 24fbbfc..d6f3c9a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,8 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,30 +11,20 @@ if (localPropertiesFile.exists()) { } } -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} - - def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') ?: '1' +def flutterVersionName = localProperties.getProperty('flutter.versionName') ?: '1.0' -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { @@ -40,14 +35,18 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "live.videosdk.rtc.flutter" minSdkVersion 23 targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } - + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { release { keyAlias keystoreProperties['keyAlias'] @@ -59,9 +58,7 @@ android { buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig signingConfigs.release } } } diff --git a/assets/transcription.svg b/assets/transcription.svg new file mode 100644 index 0000000..86c18e2 --- /dev/null +++ b/assets/transcription.svg @@ -0,0 +1,4 @@ + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..2a3b6eb --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.home=/usr/lib/jvm/java-17-openjdk-amd64 diff --git a/lib/screens/conference-call/conference_meeting_screen.dart b/lib/screens/conference-call/conference_meeting_screen.dart index c33b9fd..f02385a 100644 --- a/lib/screens/conference-call/conference_meeting_screen.dart +++ b/lib/screens/conference-call/conference_meeting_screen.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -41,6 +42,7 @@ class _ConfereneceMeetingScreenState extends State { bool isRecordingOn = false; bool showChatSnackbar = true; String recordingState = "RECORDING_STOPPED"; + String transcriptionState = "TRANSCRIPTION_STOPPED"; // Meeting late Room meeting; bool _joined = false; @@ -113,6 +115,7 @@ class _ConfereneceMeetingScreenState extends State { meeting: meeting, token: widget.token, recordingState: recordingState, + transcriptionState: transcriptionState, isMicEnabled: audioStream != null, isCamEnabled: videoStream != null, isLocalScreenShareEnabled: shareStream != null, @@ -123,6 +126,7 @@ class _ConfereneceMeetingScreenState extends State { meeting: meeting, token: widget.token, recordingState: recordingState, + transcriptionState: transcriptionState, isFullScreen: fullScreen, ), const Divider(), @@ -164,6 +168,7 @@ class _ConfereneceMeetingScreenState extends State { isCamEnabled: videoStream != null, isScreenShareEnabled: shareStream != null, recordingState: recordingState, + transcriptionState: transcriptionState, // Called when Call End button is pressed onCallEndButtonPressed: () { meeting.end(); @@ -262,25 +267,70 @@ class _ConfereneceMeetingScreenState extends State { context: context); } } else if (option == "recording") { - if (recordingState == - "RECORDING_STOPPING") { + if (recordingState == "RECORDING_STOPPING") { showSnackBarMessage( - message: - "Recording is in stopping state", + message: "Recording is in stopping state", context: context); - } else if (recordingState == - "RECORDING_STARTED") { + } else if (recordingState == "RECORDING_STARTED") { meeting.stopRecording(); - } else if (recordingState == - "RECORDING_STARTING") { + } else if (recordingState == "RECORDING_STARTING") { showSnackBarMessage( - message: - "Recording is in starting state", + message: "Recording is in starting state", + context: context); + } else { + // Define the config and transcription maps + Map config = { + "layout": { + "type": "GRID", + "priority": "SPEAKER", + "gridSize": 4, + }, + "theme": "DARK", + "mode": "video-and-audio", + "quality": "high", + "orientation": "landscape", + }; + + Map transcription = { + "enabled": true, + "summary": { + "enabled": true, + "prompt": "Write summary in sections like Title, Agenda, Speakers, Action Items, Outlines, Notes and Summary", + }, + }; + + // Start recording with the defined config and transcription + meeting.startRecording(config: config, transcription: transcription); + } + } + + else if (option == "transcription") { + if (transcriptionState == "TRANSCRIPTION_STOPPING") { + showSnackBarMessage( + message: "Transcription is in stopping state", + context: context); + } else if (transcriptionState == "TRANSCRIPTION_STARTED") { + meeting.stopTranscription(); + } else if (transcriptionState == "TRANSCRIPTION_STARTING") { + showSnackBarMessage( + message: "TRANSCRIPTION is in starting state", context: context); } else { - meeting.startRecording(); + Map config = { + "webhookUrl": "https://webhook.your-api-server.com", + "summary": { + "enabled": true, + "prompt": + "Write summary in sections like Title, Agenda, Speakers, Action Items, Outlines, Notes and Summary", + } + }; + + // Start recording with the defined config and transcription + meeting.startTranscription(config: config); } - } else if (option == "participants") { + } + + else if (option == "participants") { showModalBottomSheet( context: context, isScrollControlled: false, @@ -338,6 +388,30 @@ class _ConfereneceMeetingScreenState extends State { }); }); + _meeting.on(Events.transcriptionStateChanged, (Map data) { + String status = data['status']; + + showSnackBarMessage( + message: + "Transcription ${status == "TRANSCRIPTION_STARTING" ? "is starting" : status == "TRANSCRIPTION_STARTED" ? "started" : status == "TRANSCRIPTION_STOPPING" ? "is stopping" : "stopped"}", + context: context, + ); + + setState(() { + transcriptionState = status; + }); + }); + + _meeting.on(Events.transcriptionText, (Map data) { + String participantName = data['participantName']; + String text = data['text']; + int timestamp = data['timestamp']; + + log("$participantName: $text $timestamp"); + }); + + + // Called when stream is enabled _meeting.localParticipant.on(Events.streamEnabled, (Stream _stream) { if (_stream.kind == 'video') { diff --git a/lib/screens/one-to-one/one_to_one_meeting_screen.dart b/lib/screens/one-to-one/one_to_one_meeting_screen.dart index 495ecc0..e468672 100644 --- a/lib/screens/one-to-one/one_to_one_meeting_screen.dart +++ b/lib/screens/one-to-one/one_to_one_meeting_screen.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -42,6 +43,7 @@ class _OneToOneMeetingScreenState extends State { bool isRecordingOn = false; bool showChatSnackbar = true; String recordingState = "RECORDING_STOPPED"; + String transcriptionState = "TRANSCRIPTION_STOPPED"; // Meeting late Room meeting; bool _joined = false; @@ -115,6 +117,7 @@ class _OneToOneMeetingScreenState extends State { meeting: meeting, token: widget.token, recordingState: recordingState, + transcriptionState: transcriptionState, isMicEnabled: audioStream != null, isCamEnabled: videoStream != null, isLocalScreenShareEnabled: shareStream != null, @@ -125,6 +128,7 @@ class _OneToOneMeetingScreenState extends State { meeting: meeting, token: widget.token, recordingState: recordingState, + transcriptionState: transcriptionState, isFullScreen: fullScreen, ), const Divider(), @@ -154,6 +158,7 @@ class _OneToOneMeetingScreenState extends State { isCamEnabled: videoStream != null, isScreenShareEnabled: shareStream != null, recordingState: recordingState, + transcriptionState: transcriptionState, // Called when Call End button is pressed onCallEndButtonPressed: () { meeting.end(); @@ -252,25 +257,70 @@ class _OneToOneMeetingScreenState extends State { context: context); } } else if (option == "recording") { - if (recordingState == - "RECORDING_STOPPING") { + if (recordingState == "RECORDING_STOPPING") { showSnackBarMessage( - message: - "Recording is in stopping state", + message: "Recording is in stopping state", context: context); - } else if (recordingState == - "RECORDING_STARTED") { + } else if (recordingState == "RECORDING_STARTED") { meeting.stopRecording(); - } else if (recordingState == - "RECORDING_STARTING") { + } else if (recordingState == "RECORDING_STARTING") { showSnackBarMessage( - message: - "Recording is in starting state", + message: "Recording is in starting state", context: context); } else { - meeting.startRecording(); + // Define the config and transcription maps + Map config = { + "layout": { + "type": "GRID", + "priority": "SPEAKER", + "gridSize": 4, + }, + "theme": "DARK", + "mode": "video-and-audio", + "quality": "high", + "orientation": "landscape", + }; + + Map transcription = { + "enabled": true, + "summary": { + "enabled": true, + "prompt": "Write summary in sections like Title, Agenda, Speakers, Action Items, Outlines, Notes and Summary", + }, + }; + + // Start recording with the defined config and transcription + meeting.startRecording(config: config, transcription: transcription); + } + } + + else if (option == "transcription") { + if (transcriptionState == "TRANSCRIPTION_STOPPING") { + showSnackBarMessage( + message: "Transcription is in stopping state", + context: context); + } else if (transcriptionState == "TRANSCRIPTION_STARTED") { + meeting.stopTranscription(); + } else if (transcriptionState == "TRANSCRIPTION_STARTING") { + showSnackBarMessage( + message: "TRANSCRIPTION is in starting state", + context: context); + } else { + Map config = { + "webhookUrl": "https://webhook.your-api-server.com", + "summary": { + "enabled": true, + "prompt": + "Write summary in sections like Title, Agenda, Speakers, Action Items, Outlines, Notes and Summary", + } + }; + + // Start recording with the defined config and transcription + meeting.startTranscription(config: config); } - } else if (option == "participants") { + } + + else if (option == "participants") { showModalBottomSheet( context: context, // constraints: BoxConstraints( @@ -342,6 +392,29 @@ class _OneToOneMeetingScreenState extends State { }); }); + _meeting.on(Events.transcriptionStateChanged, (Map data) { + String status = data['status']; + + showSnackBarMessage( + message: + "Transcription ${status == "TRANSCRIPTION_STARTING" ? "is starting" : status == "TRANSCRIPTION_STARTED" ? "started" : status == "TRANSCRIPTION_STOPPING" ? "is stopping" : "stopped"}", + context: context, + ); + + setState(() { + transcriptionState = status; + }); + }); + + _meeting.on(Events.transcriptionText, (Map data) { + String participantName = data['participantName']; + String text = data['text']; + int timestamp = data['timestamp']; + + log("$participantName: $text $timestamp"); + }); + + // Called when stream is enabled _meeting.localParticipant.on(Events.streamEnabled, (Stream _stream) { if (_stream.kind == 'video') { diff --git a/lib/widgets/common/app_bar/meeting_appbar.dart b/lib/widgets/common/app_bar/meeting_appbar.dart index c3ce60f..f24802a 100644 --- a/lib/widgets/common/app_bar/meeting_appbar.dart +++ b/lib/widgets/common/app_bar/meeting_appbar.dart @@ -14,13 +14,14 @@ class MeetingAppBar extends StatefulWidget { final String token; final Room meeting; final String recordingState; + final String transcriptionState; final bool isFullScreen; const MeetingAppBar( {Key? key, required this.meeting, required this.token, required this.isFullScreen, - required this.recordingState}) + required this.recordingState, required this.transcriptionState }) : super(key: key); @override diff --git a/lib/widgets/common/app_bar/web_meeting_appbar.dart b/lib/widgets/common/app_bar/web_meeting_appbar.dart index 3c39f3d..bfacb89 100644 --- a/lib/widgets/common/app_bar/web_meeting_appbar.dart +++ b/lib/widgets/common/app_bar/web_meeting_appbar.dart @@ -25,6 +25,7 @@ class WebMeetingAppBar extends StatefulWidget { isLocalScreenShareEnabled, isRemoteScreenShareEnabled; final String recordingState; + final String transcriptionState; const WebMeetingAppBar({ Key? key, @@ -35,6 +36,7 @@ class WebMeetingAppBar extends StatefulWidget { required this.isCamEnabled, required this.isLocalScreenShareEnabled, required this.isRemoteScreenShareEnabled, + required this.transcriptionState, }) : super(key: key); @override @@ -127,7 +129,29 @@ class WebMeetingAppBarState extends State { message: "Recording is in starting state", context: context); } else { - widget.meeting.startRecording(); + // Define the config and transcription maps + Map config = { + "layout": { + "type": "GRID", + "priority": "SPEAKER", + "gridSize": 4, + }, + "theme": "DARK", + "mode": "video-and-audio", + "quality": "high", + "orientation": "landscape", + }; + + Map transcription = { + "enabled": true, + "summary": { + "enabled": true, + "prompt": "Write summary in sections like Title, Agenda, Speakers, Action Items, Outlines, Notes and Summary", + }, + }; + + // Start recording with the defined config and transcription + widget.meeting.startRecording(config: config, transcription: transcription); } }, child: Container( @@ -146,6 +170,54 @@ class WebMeetingAppBarState extends State { ), ), ), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TouchRippleEffect( + borderRadius: BorderRadius.circular(12), + rippleColor: primaryColor, + onTap: () { + if (widget.transcriptionState == "TRANSCRIPTION_STOPPING") { + showSnackBarMessage( + message: "Transcription is in stopping state", + context: context); + } else if (widget.transcriptionState == "TRANSCRIPTION_STARTED") { + widget.meeting.stopTranscription(); + } else if (widget.transcriptionState == "TRANSCRIPTION_STARTING") { + showSnackBarMessage( + message: "Transcription is in starting state", + context: context); + } else { + Map transcriptionConfig = { + "webhookUrl": "https://webhook.your-api-server.com", + "summary": { + "enabled": true, + "prompt": "Write summary in sections like Title, Agenda, Speakers, Action Items, Outlines, Notes and Summary", + } + }; + + // Start transcription with the defined config + widget.meeting.startTranscription(config: transcriptionConfig); + } + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: secondaryColor), + color: primaryColor, + ), + padding: const EdgeInsets.all(11), + child: SvgPicture.asset( + "assets/transcription.svg", + width: 23, + height: 23, + // color: Colors.white, + ), + ), + ), + ), + + // Mic Control Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), diff --git a/lib/widgets/common/meeting_controls/meeting_action_bar.dart b/lib/widgets/common/meeting_controls/meeting_action_bar.dart index aa71bfd..614e2b4 100644 --- a/lib/widgets/common/meeting_controls/meeting_action_bar.dart +++ b/lib/widgets/common/meeting_controls/meeting_action_bar.dart @@ -10,6 +10,7 @@ class MeetingActionBar extends StatelessWidget { // control states final bool isMicEnabled, isCamEnabled, isScreenShareEnabled; final String recordingState; + final String transcriptionState; // callback functions final void Function() onCallEndButtonPressed, @@ -27,6 +28,7 @@ class MeetingActionBar extends StatelessWidget { required this.isCamEnabled, required this.isScreenShareEnabled, required this.recordingState, + required this.transcriptionState, required this.onCallEndButtonPressed, required this.onCallLeaveButtonPressed, required this.onMicButtonPressed, @@ -197,6 +199,17 @@ class MeetingActionBar extends StatelessWidget { null, SvgPicture.asset("assets/ic_recording.svg"), ), + const PopupMenuDivider(), + _buildMeetingPoupItem( + "transcription", + transcriptionState == "TRANSCRIPTION_STARTED" + ? "Stop Transcription" + : transcriptionState == "TRANSCRIPTION_STARTING" + ? "Transcription is starting" + : "Start Transcription", + null, + SvgPicture.asset("assets/transcription.svg"), + ), const PopupMenuDivider(), _buildMeetingPoupItem( "screenshare", diff --git a/pubspec.yaml b/pubspec.yaml index bee9328..5a37771 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - videosdk: 1.1.8 + videosdk: ^1.1.10 http: ^1.1.0 flutter_dotenv: ^5.0.2 flutter_svg: ^2.0.2