Merge branch 'zik_webrtc_dev_v2.5' into 'development_v2.5'

Project Filter  Applied on Packages & Offers

See merge request Cloud_Solution/diplomatic-quarter!599
merge-requests/602/merge
haroon amjad 4 years ago
commit d43d6c686b

@ -521,4 +521,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 87c85be30da5343f66afbb978b1a421a87b82b01
COCOAPODS: 1.11.2
COCOAPODS: 1.11.3

@ -11,6 +11,7 @@ final OPENTOK_API_KEY = '46209962';
var EXA_CART_API_BASE_URL = 'https://mdlaboratories.com/offersdiscounts';
// var EXA_CART_API_BASE_URL = 'http://10.200.101.75:9000';
var PACKAGES_CATEGORIES = '/api/categories';
var PACKAGES_STORES = '/api/stores';
var PACKAGES_TOKEN = '/api/token';
var PACKAGES_PRODUCTS = '/api/products';
var PACKAGES_CUSTOMER = '/api/customers';
@ -25,7 +26,7 @@ var BASE_URL = 'https://hmgwebservices.com/';
// Pharmacy UAT URLs
// var BASE_PHARMACY_URL = 'https://uat.hmgwebservices.com/epharmacy/api/';
// var PHARMACY_BASE_URL = 'https://uat.hmgwebservices.com/epharmacy/api/';
//
// // Pharmacy Production URLs
var BASE_PHARMACY_URL = 'https://mdlaboratories.com/exacartapi/api/';
var PHARMACY_BASE_URL = 'https://mdlaboratories.com/exacartapi/api/';

@ -870,6 +870,7 @@ const Map localizedValues = {
"number": {"en": "Number", "ar": "الرقم"},
"not-classified": {"en": "Not classified", "ar": "غير محدد"},
"selectClinic": {"en": "Select Clinic", "ar": " بحث بالعيادة"},
"selectProject": {"en": "Select Hospital", "ar": " بحث المستشفى"},
"searchItemError": {"en": "Item name should be more than 3 character ", "ar": "يجب أن يكون الاسم المدخل أكثر من 3 أحرف"},
"YouCanFind": {"en": "You Can Find ", "ar": "باستطاعتك العثور على "},
"ItemInSearch": {"en": " Item in Search", "ar": " عنصر في البحث "},
@ -1032,7 +1033,7 @@ const Map localizedValues = {
"ar": "يمكنك الان التحدث مباشرة مع قسم المواعيد عن طريق خدمة المحادثة النصية أو طلب معاودة الاتصال"
},
"instructions-pharmacies": {"en": "You can now talk directly to the pharmacist by chat or request a call back", "ar": "يمكنك الآن التحدث مباشرة إلى الصيدلي عن طريق الدردشة أو طلب معاودة الاتصال"},
"select-hospital": {"en": "Choose Hospital", "ar": "اختر المستشفى"},
"select-hospital": {"en": "Choose Hospital", "ar": "اختر المستشفىالمستشفى"},
"start": {"en": "Start", "ar": "ابدأ"},
"info-chat": {
"en": "This service allows you to chat with customer service directly without the need to call.",
@ -1738,6 +1739,7 @@ const Map localizedValues = {
"estimates": {"en": "Estimates the total body fat based on\nthe size", "ar": "تقدير إجمالي الدهون في الجسم بناءً على \ n الحجم"},
"myCart": {"en": "My Cart", "ar": "عربة التسوق"},
"browseOffers": {"en": "Browse offers by clinic", "ar": "تصفح العروض حسب العيادة"},
"browseOffersByProject": {"en": "Browse offers by hospital", "ar": "تصفح العروض حسب المستشفى"},
"inactiveAct": {"en": "Almost inactive (little or no exercise)", "ar": "غير نشط تقريبا (ممارسة الرياضة قليلة أو منعدمة)"},
"light": {"en": "Lightly active (1-3) days per week", "ar": "خفيف النشاط (1-3 أيام في الأسبوع)"},
"moderate": {"en": "Moderately active (3-5) days per week)", "ar": "معتدل النشاط (3-5 أيام في الأسبوع)"},

@ -1,10 +1,11 @@
class OffersProductsRequestModel {
final int categoryId;
final int storeId;
final int limit;
// final int page;
int sinceId;
OffersProductsRequestModel({this.categoryId, this.limit = 50});
OffersProductsRequestModel({this.categoryId, this.limit = 50, this.storeId});
Map<String, String> toFlatMap() {
return {
@ -18,6 +19,9 @@ class OffersProductsRequestModel {
if(categoryId != null && categoryId > 0)
"categoryId": categoryId.toString(),
if(storeId != null && storeId > 0)
"store_id": storeId.toString(),
"sinceId": sinceId != null ? sinceId.toString() : "0",
};
}

@ -0,0 +1,44 @@
class OfferProjectsResponseModel {
OfferProjectsResponseModel({
this.project,});
OfferProjectsResponseModel.fromJson(dynamic json) {
if (json['stores'] != null) {
project = [];
json['stores'].forEach((v) {
project.add(OfferProject.fromJson(v));
});
}
}
List<OfferProject> project;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (project != null) {
map['stores'] = project.map((v) => v.toJson()).toList();
}
return map;
}
}
class OfferProject {
OfferProject({
this.name,
this.id,});
OfferProject.fromJson(dynamic json) {
name = json['name'];
id = json['id'];
}
String name;
int id;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['name'] = name;
map['id'] = id;
return map;
}
}

@ -13,6 +13,7 @@ import 'package:diplomaticquarterapp/core/model/packages_offers/responses/Packag
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/offers_stores_response_model.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/order_response_model.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/tamara_payment_option.dart';
import 'package:diplomaticquarterapp/core/service/base_service.dart';
@ -25,6 +26,7 @@ Map<String, String> packagesAuthHeader = {};
class OffersAndPackagesServices extends BaseService {
AuthenticatedUser patientUser;
List<PackagesCategoriesResponseModel> categoryList = [];
List<OfferProject> projectsList = [];
List<PackagesResponseModel> productList = [];
List<PackagesResponseModel> latestOffersList = [];
List<TamaraPaymentOption> tamaraPaymentOptions = [];
@ -39,7 +41,8 @@ class OffersAndPackagesServices extends BaseService {
PackagesCustomerResponseModel customer;
Future<List<PackagesCategoriesResponseModel>> getAllCategories(OffersCategoriesRequestModel request) async {
Future errorThrow;
if(categoryList.isNotEmpty)
return categoryList;
var url = EXA_CART_API_BASE_URL + PACKAGES_CATEGORIES;
await baseAppClient.simpleGet(url, headers: packagesAuthHeader, onSuccess: (dynamic stringResponse, int statusCode) {
@ -54,11 +57,26 @@ class OffersAndPackagesServices extends BaseService {
return categoryList;
}
Future<List<PackagesResponseModel>> getAllProducts({@required OffersProductsRequestModel request, @required BuildContext context, @required bool showLoading = true}) async {
Future errorThrow;
Future<List<OfferProject>> getAllStores() async {
if(projectsList.isNotEmpty)
return projectsList;
var url = EXA_CART_API_BASE_URL + PACKAGES_STORES;
await baseAppClient.simpleGet(url, headers: packagesAuthHeader, onSuccess: (dynamic stringResponse, int statusCode) {
if (statusCode == 200) {
var jsonResponse = json.decode(stringResponse);
final response = OfferProjectsResponseModel.fromJson(jsonResponse);
projectsList = response.project;
}
}, onFailure: (String error, int statusCode) {
throw error;
}, queryParams: {'fields' : 'id,name'});
request.sinceId = (productList.isNotEmpty) ? productList.last.id : 0;
return projectsList;
}
Future<List<PackagesResponseModel>> getAllProducts({@required OffersProductsRequestModel request, @required BuildContext context, @required bool showLoading = true, bool byOffset = true}) async {
Future errorThrow;
productList = [];
var url = EXA_CART_API_BASE_URL + PACKAGES_PRODUCTS;
await baseAppClient.simpleGet(url, headers: packagesAuthHeader, onSuccess: (dynamic stringResponse, int statusCode) {

@ -25,6 +25,7 @@ class IncomingCallData {
String token;
String isCall;
String sound;
String server;
IncomingCallData(
{this.msgID,
@ -78,6 +79,7 @@ class IncomingCallData {
picture = json['picture'];
isCall = json['is_call'];
sound = json['sound'];
server = json['server'];
}
Map<String, dynamic> toJson() {
@ -106,6 +108,7 @@ class IncomingCallData {
data['picture'] = this.picture;
data['is_call'] = this.isCall;
data['sound'] = this.sound;
data['server'] = this.server;
return data;
}
}

@ -2,7 +2,6 @@ import 'dart:convert';
import 'dart:math' as math;
import 'package:auto_size_text/auto_size_text.dart';
import 'package:crypto/crypto.dart';
import 'package:diplomaticquarterapp/config/size_config.dart';
import 'package:diplomaticquarterapp/core/viewModels/dashboard_view_model.dart';
import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart';
@ -17,6 +16,7 @@ import 'package:diplomaticquarterapp/pages/AlHabibMedicalService/all_habib_medic
import 'package:diplomaticquarterapp/pages/landing/widgets/logged_slider_view.dart';
import 'package:diplomaticquarterapp/pages/landing/widgets/services_view.dart';
import 'package:diplomaticquarterapp/pages/landing/widgets/slider_view.dart';
import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesPage.dart';
import 'package:diplomaticquarterapp/pages/packages_offers/packages_offers_tab_pager.dart';
import 'package:diplomaticquarterapp/theme/colors.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
@ -25,8 +25,9 @@ import 'package:diplomaticquarterapp/uitl/utils_new.dart';
import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:payfort_plugin/payfort_plugin.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:pay/pay.dart';
class HomePageFragment2 extends StatefulWidget {
DashboardViewModel model;
@ -103,23 +104,7 @@ class _HomePageFragment2State extends State<HomePageFragment2> {
onLoginClick: () {
widget.onLoginClick();
projectViewModel.analytics.loginRegistration.login_register_initiate();
// var output;
// PayfortPlugin.getID.then((deviceID) {
// //use this deviceID to send it to your server to get YOR_MERCHANT_REF and YOUR_SDK_TOKEN
// print("DeviceID: $deviceID");
// output = sha256
// .convert(
// utf8.encode("asD123@saereaccess_code=BsM6He4FMBaZ86W64kjZdevice_id=" + deviceID + "language=enmerchant_identifier=ipxnRXXqservice_command=SDK_TOKENasD123@saere"))
// .toString();
// print("signature: $output");
// PayfortPlugin.performPaymentRequest('ipxnRXXq', '92f1dffb1abd43c8b60fe55bfeb10c73', 'haroon', 'en', 'haroon6138@mail.com', '100000', 'PURCHASE', 'SAR', '0' // 0 for test mode and 1 for production
// )
// .then((value) => {
// // value object contains payfort response, such card number, transaction reference, ...
// print("card number ${value["card_number"]}")
// });
// });
// navigateTo(context, CallHomePage());
},
),
// height: MediaQuery.of(context).size.width / 2.6,
@ -299,13 +284,15 @@ class _HomePageFragment2State extends State<HomePageFragment2> {
}
Widget offersButton() {
final bypassPrivilageCheck = false;
return Expanded(
flex: 1,
child: InkWell(
onTap: () {
projectViewModel.analytics.offerPackages.log();
AuthenticatedUser user = projectViewModel.user;
if (projectViewModel.havePrivilege(82)) Navigator.of(context).push(MaterialPageRoute(builder: (context) => PackagesOfferTabPage(user)));
if(projectViewModel.havePrivilege(82) || bypassPrivilageCheck)
Navigator.of(context).push(MaterialPageRoute(builder: (context) => PackagesOfferTabPage(user)));
},
child: Stack(
children: [
@ -452,7 +439,7 @@ class _HomePageFragment2State extends State<HomePageFragment2> {
],
),
),
projectViewModel.havePrivilege(82)
projectViewModel.havePrivilege(82) || bypassPrivilageCheck
? Container()
: Container(
width: double.infinity,

@ -15,7 +15,9 @@ import 'package:diplomaticquarterapp/pages/BookAppointment/Search.dart';
import 'package:diplomaticquarterapp/pages/DrawerPages/family/my-family.dart';
import 'package:diplomaticquarterapp/pages/ToDoList/ToDo.dart';
import 'package:diplomaticquarterapp/pages/landing/home_page_2.dart';
import 'package:diplomaticquarterapp/pages/livecare/incoming_call.dart';
import 'package:diplomaticquarterapp/pages/medical/medical_profile_page_new.dart';
import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/webrtc/start_video_call.dart';
import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart';
import 'package:diplomaticquarterapp/services/clinic_services/get_clinic_service.dart';
import 'package:diplomaticquarterapp/services/family_files/family_files_provider.dart' as family;
@ -418,6 +420,9 @@ class _LandingPageState extends State<LandingPage> with WidgetsBindingObserver {
}
login() async {
// dummyCall();
// return;
var data = await sharedPref.getObject(IMEI_USER_DATA);
sharedPref.remove(REGISTER_DATA_FOR_LOGIIN);
if (data != null) {
@ -443,6 +448,43 @@ class _LandingPageState extends State<LandingPage> with WidgetsBindingObserver {
}
}
dummyCall() async {
final json = {
"callerID": "s1",
"PatientID": "s2",
"msgID": "123",
"notfID": "123",
"notification_foreground": "true",
"count": "1",
"message": "Doctor is calling ",
"AppointmentNo": "123",
"title": "Rayyan Hospital",
"ProjectID": "123",
"NotificationType": "10",
"background": "1",
"doctorname": "Dr Sulaiman Al Habib",
"clinicname": "ENT Clinic",
"speciality": "Speciality",
"appointmentdate": "Sun, 15th Dec, 2019",
"appointmenttime": "09:00",
"type": "video",
"session_id":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTS2I2NjYyOWMzN2ZhOTM3YjFjNDI2Zjg1MTgyNWFmN2M0LTE1OTg3NzQ1MDYiLCJpc3MiOiJTS2I2NjYyOWMzN2ZhOTM3YjFjNDI2Zjg1MTgyNWFmN2M0Iiwic3ViIjoiQUNhYWQ1YTNmOGM2NGZhNjczNTY3NTYxNTc0N2YyNmMyYiIsImV4cCI6MTU5ODc3ODEwNiwiZ3JhbnRzIjp7ImlkZW50aXR5IjoiSGFyb29uMSIsInZpZGVvIjp7InJvb20iOiJTbWFsbERhaWx5U3RhbmR1cCJ9fX0.7XUS5uMQQJfkrBZu9EjQ6STL6R7iXkso6BtO1HmrQKk",
"identity": "Haroon1",
"name": "SmallDailyStandup",
"videoUrl": "video",
"picture": "video",
"is_call": "true",
"server": "192.168.8.104",
};
IncomingCallData incomingCallData = IncomingCallData.fromJson(json);
final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => IncomingCall(incomingCallData: incomingCallData)));
}
getNotificationCount(token) async {
if (await sharedPref.getObject(USER_PROFILE) != null) {
var data = AuthenticatedUser.fromJson(await sharedPref.getObject(USER_PROFILE));

@ -1,13 +1,10 @@
import 'dart:ui';
import 'package:camera/camera.dart';
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/models/LiveCare/IncomingCallData.dart';
import 'package:diplomaticquarterapp/models/LiveCare/room_model.dart';
import 'package:diplomaticquarterapp/pages/conference/web_rtc/call_home_page_.dart';
import 'package:diplomaticquarterapp/pages/conference/widgets/platform_exception_alert_dialog.dart';
import 'package:diplomaticquarterapp/pages/landing/landing_page.dart';
import 'package:diplomaticquarterapp/pages/livecare/video-call-web-page.dart';
import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/webrtc/start_video_call.dart';
import 'package:diplomaticquarterapp/pages/webRTC/OpenTok/OpenTok.dart';
import 'package:diplomaticquarterapp/pages/webRTC/signaling.dart';
import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart';
@ -17,6 +14,8 @@ import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import '../../config/config.dart';
class IncomingCall extends StatefulWidget {
IncomingCallData incomingCallData;
@ -236,13 +235,15 @@ class _IncomingCallState extends State<IncomingCall> with SingleTickerProviderSt
MaterialPageRoute(
// fullscreenDialog: true,
builder: (BuildContext context) {
final caller = widget.incomingCallData.callerID;
final receiver = widget.incomingCallData.receiverID;
final host = widget.incomingCallData.server;
// return StartVideoCall(caller: caller, receiver: receiver, iAmCaller: false, host: host,);
// return VideoCallWebPage(receiverId: widget.incomingCallData.receiverID, callerId: widget.incomingCallData.callerID); // Web WebRTC VideoCall
// return CallHomePage(receiverId: widget.incomingCallData.receiverID, callerId: widget.incomingCallData.callerID); // App WebRTC VideoCall
// return OpenTokConnectCallPage(
// apiKey: OPENTOK_API_KEY,
// sessionId: '1_MX40NjIwOTk2Mn5-MTY0NzE3MzcwNjA0MH55RUJoZnd0ZGh2U3BPc01ENVZBelQvT1Z-fg',
// token: 'T1==cGFydG5lcl9pZD00NjIwOTk2MiZzaWc9M2I4ODYxYTIzMGQ0ZDA3OTYyNDhkODIxNzI5ZjIzODM1NjY2YzExMzpzZXNzaW9uX2lkPTFfTVg0ME5qSXdPVGsyTW41LU1UWTBOekUzTXpjd05qQTBNSDU1UlVKb1puZDBaR2gyVTNCUGMwMUVOVlpCZWxRdlQxWi1mZyZjcmVhdGVfdGltZT0xNjQ3MTczNzA2Jm5vbmNlPTAuMjA3NDI0MzA1NzcwMzY4MiZyb2xlPW1vZGVyYXRvciZleHBpcmVfdGltZT0xNjQ3MjYwMTA2JmluaXRpYWxfbGF5b3V0X2NsYXNzX2xpc3Q9'
// );
return OpenTokConnectCallPage(
apiKey: OPENTOK_API_KEY,
sessionId: widget.incomingCallData.sessionId,

@ -6,6 +6,7 @@ import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersC
import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart';
import 'package:diplomaticquarterapp/core/model/packages_offers/responses/offers_stores_response_model.dart';
import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart';
import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart';
import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart';
@ -88,6 +89,7 @@ class _PackagesHomePageState extends State<PackagesHomePage> {
}
PackagesCategoriesResponseModel selectedClinic;
OfferProject selectedProject;
@override
Widget build(BuildContext context) {
@ -106,6 +108,12 @@ class _PackagesHomePageState extends State<PackagesHomePage> {
if(OFFERS_VIEW_ONLY)
inputWidget(TranslationBase.of(context).search, "", _searchTextController, isInputTypeNum: false),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.only(left: 21, right: 21),
child: CommonDropDownView(TranslationBase.of(context).browseOffersByProject, selectedProject?.name ?? TranslationBase.of(context).selectProject, () => showHospitalSelectionList())
.withBorderedContainer,
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.only(left: 21, right: 21),
child: CommonDropDownView(TranslationBase.of(context).browseOffers, selectedClinic?.name ?? TranslationBase.of(context).selectClinic, () => showClinicSelectionList())
@ -174,13 +182,7 @@ class _PackagesHomePageState extends State<PackagesHomePage> {
int _selectedClinic = -1;
showClinicSelectionList() async {
var clinics = viewModel.service.categoryList;
if (clinics.isEmpty) {
GifLoaderDialogUtils.showMyDialog(context);
clinics = await viewModel.service.getAllCategories(OffersCategoriesRequestModel());
GifLoaderDialogUtils.hideDialog(context);
}
var clinics = await viewModel.service.getAllCategories(OffersCategoriesRequestModel());
List<RadioSelectionDialogModel> list = [
for (int i = 0; i < clinics.length; i++) RadioSelectionDialogModel(clinics[i].name, i),
];
@ -204,6 +206,38 @@ class _PackagesHomePageState extends State<PackagesHomePage> {
);
}
int _selectedHospital = -1;
showHospitalSelectionList() async {
GifLoaderDialogUtils.showMyDialog(context);
var hospitals = await viewModel.service.getAllStores();
GifLoaderDialogUtils.hideDialog(context);
List<RadioSelectionDialogModel> list = [
for (int i = 0; i < hospitals.length; i++) RadioSelectionDialogModel(hospitals[i].name, i),
];
showDialog(
context: context,
builder: (cxt) => RadioSelectionDialog(
listData: list,
selectedIndex: _selectedHospital,
isScrollable: true,
onValueSelected: (index) async {
_selectedHospital = index;
selectedProject = hospitals[index];
var request = OffersProductsRequestModel(storeId: selectedProject.id);
request.sinceId = 0;
var products = await viewModel.service.getAllProducts(request: request, context: context, showLoading: true);
if (products.isNotEmpty)
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ClinicPackagesPage(products: products)));
else
utils.Utils.showErrorToast("No offers available for this clinic");
setState(() {});
},
),
);
}
//----------------------------------
// Main Widgets of Page
//----------------------------------

@ -0,0 +1,15 @@
@JS()
library streammixer.js;
import 'package:js/js.dart';
@JS('multiStreamsMixerFlutter')
external initMultiStreamsMixer(arrayOfMediaStreams);
@JS("MultiStreamsMixer")
class MultiStreamsMixer {
external MultiStreamsMixer(arrayOfMediaStreams, elementClass);
external getMixedStream();
external start();
}

@ -0,0 +1,45 @@
// import 'dart:async';
// import 'dart:html' as html;
//
// import 'dart:typed_data';
//
// extension xHtmlBlob on html.Blob{
//
// /* File Reader --- */
// Future<html.FileReader> _fileDataUrlReader()async{
// html.FileReader reader = html.FileReader();
// reader.readAsDataUrl(this);
// return reader;
// }
//
// Future<html.FileReader> _fileByteReader()async{
// html.FileReader reader = html.FileReader();
// reader.readAsArrayBuffer(this);
// return reader;
// }
// /* --- File Reader */
//
// Future<dynamic> readDataUrl() async{
// Completer completer = Completer();
// _fileDataUrlReader().then((reader){
// reader.onLoadEnd.listen((e) async {
// dynamic data = reader.result;
// completer.complete(data);
// });
// });
// final data = await completer.future;
// return data;
// }
//
// Future<Uint8List> readBytes() async{
// Completer completer = Completer();
// _fileByteReader().then((reader){
// reader.onLoadEnd.listen((e) async {
// dynamic data = reader.result;
// completer.complete(data);
// });
// });
// final data = await completer.future;
// return data;
// }
// }

@ -0,0 +1,15 @@
import 'dart:io';
class DeviceInfo {
static String get label {
return 'Flutter ' +
Platform.operatingSystem +
'(' +
Platform.localHostname +
")";
}
static String get userAgent {
return 'flutter-webrtc/' + 'Web' + '-plugin 0.0.1';
}
}

@ -0,0 +1,11 @@
// ignore: avoid_web_libraries_in_flutter
class DeviceInfo {
static String get label {
return 'Flutter Web';
}
static String get userAgent {
return 'flutter-webrtc/web-plugin 0.0.1 (Mobile)';
}
}

@ -0,0 +1,19 @@
import 'dart:convert';
import 'dart:async';
import 'dart:io';
Future<Map> getTurnCredential(String host, int port) async {
HttpClient client = HttpClient(context: SecurityContext());
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
print('getTurnCredential: Allow self-signed certificate => $host:$port. ');
return true;
};
var url = 'https://$host:$port/api/turn?service=turn&username=flutter-webrtc';
var request = await client.getUrl(Uri.parse(url));
var response = await request.close();
var responseBody = await response.transform(Utf8Decoder()).join();
print('getTurnCredential:response => $responseBody.');
Map data = JsonDecoder().convert(responseBody);
return data;
}

@ -0,0 +1,13 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<Map> getTurnCredential(String host, int port) async {
var url = 'https://$host:$port/api/turn?service=turn&username=flutter-webrtc';
final res = await http.get(Uri.parse(url));
if (res.statusCode == 200) {
var data = json.decode(res.body);
print('getTurnCredential:response => $data.');
return data;
}
return {};
}

@ -0,0 +1,97 @@
// import 'dart:convert';
// import 'dart:html' as html;
//
// import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/utils/blob-extension.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:http/http.dart' as http;
//
// // should run on port '65482' add to additional arguments --web-port=65482
// class UploadRecording{
//
// final url = "https://vcallapi.hmg.com/api/videocall/trackMeeting";
// // final url = "https://10.20.200.186/api/videocall/trackMeeting";
// upload(html.Blob blob, {@required Map<String, String> params, @required Function completion, @required Function(double) onProgress}) async{
//
// final bytes = await blob.readBytes();
// final filename = "${DateTime.now().millisecondsSinceEpoch}.webm";
// final file = http.MultipartFile.fromBytes('file', bytes ?? [], filename: filename);
//
// var request = http.MultipartRequest('POST', Uri.parse(url));
// request.files.add(file);
// request.fields.addAll({'fileName':filename,...params});
//
// print("Uploading video of '${blob.size}' bytes");
// var response = await request.send().catchError((e){
// print(e.toString());
// completion(e.toString());
// });
//
// print(response.statusCode);
// if (response.statusCode == 200) {
// final bytes = await response.stream.toBytes();
// final jsonString = utf8.decode(bytes);
// final json = jsonDecode(jsonString);
// if(json == null)
// completion('200: Invalid response format');
// else
// completion(json);
//
// }else {
// print(response.reasonPhrase);
// completion(response.reasonPhrase);
// }
// }
//
// _upload(html.Blob blob, {@required Map<String, String> params, @required Function completion, @required Function(double) onProgress}) async{
// final blobBytes = await blob.readBytes();
//
// Map<String, dynamic> body = {};
// body.addAll(params);
// body['file'] = blobBytes ?? [];
// body['fileName'] = "${DateTime.now().millisecondsSinceEpoch}.webm";
//
// final json = jsonEncode(body);
//
// var request = http.StreamedRequest('POST', Uri.parse(url));
//
// var totalLength = json.length;// Total length (to calculate upload progress)
// var transferredLength = 0;// Length transferred (to calculate upload progress)
// var uploadProgress = 0.0; // Upload progress (from 0.0 to 1.0)
//
// Stream.value(json).transform(utf8.encoder).listen((chunk) {
//
// transferredLength += chunk.length;
// uploadProgress = transferredLength / totalLength;
// // print("Chunk: ${chunk.length}, transferred: $transferredLength, progress: $uploadProgress");
// request.sink.add(chunk);
// onProgress(uploadProgress*100);
//
// }, onDone: () {
// print("Done. Total: $totalLength, transferred: $transferredLength, progress: $uploadProgress");
// request.sink.close();
// onProgress(uploadProgress*100);
// });
//
//
// print("Uploading video of '${blob.size}' bytes");
// var response = await request.send();
//
// print(response.statusCode);
// if (response.statusCode == 200){
//
// final bytes = await response.stream.toBytes();
// final jsonString = utf8.decode(bytes);
// final json = jsonDecode(jsonString);
// if(json == null)
// completion('200: Invalid response format');
// else
// completion(json);
//
//
// }else {
// print("${response.statusCode}: ${response.reasonPhrase}");
// completion(response.reasonPhrase);
// }
//
// }
// }

@ -0,0 +1,75 @@
import 'dart:io';
import 'dart:math';
import 'dart:convert';
import 'dart:async';
class SimpleWebSocket {
String _url;
var _socket;
Function() onOpen;
Function(dynamic msg) onMessage;
Function(int code, String reaso) onClose;
SimpleWebSocket(this._url);
connect() async {
try {
//_socket = await WebSocket.connect(_url);
print('connecting to $_url');
_socket = await _connectForSelfSignedCert(_url);
onOpen?.call();
_socket.listen((data) {
onMessage?.call(data);
}, onDone: () {
onClose?.call(_socket.closeCode, _socket.closeReason);
});
} catch (e) {
onClose?.call(500, e.toString());
}
}
send(data) {
if (_socket != null) {
_socket.add(data);
print('send: $data');
}
}
close() {
if (_socket != null) _socket.close();
}
Future<WebSocket> _connectForSelfSignedCert(url) async {
try {
Random r = new Random();
String key = base64.encode(List<int>.generate(8, (_) => r.nextInt(255)));
HttpClient client = HttpClient(context: SecurityContext());
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
print(
'SimpleWebSocket: Allow self-signed certificate => $host:$port. ');
return true;
};
HttpClientRequest request =
await client.getUrl(Uri.parse(url)); // form the correct url here
request.headers.add('Connection', 'Upgrade');
request.headers.add('Upgrade', 'websocket');
request.headers.add(
'Sec-WebSocket-Version', '13'); // insert the correct version here
request.headers.add('Sec-WebSocket-Key', key.toLowerCase());
HttpClientResponse response = await request.close();
// ignore: close_sinks
Socket socket = await response.detachSocket();
var webSocket = WebSocket.fromUpgradedSocket(
socket,
protocol: 'signaling',
serverSide: false,
);
return webSocket;
} catch (e) {
throw e;
}
}
}

@ -0,0 +1,48 @@
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
class SimpleWebSocket {
String _url;
var _socket;
Function() onOpen;
Function(dynamic msg) onMessage;
Function(int code, String reason) onClose;
SimpleWebSocket(this._url) {
_url = _url.replaceAll('https:', 'wss:');
}
connect() async {
try {
_socket = WebSocket(_url);
_socket.onOpen.listen((e) {
onOpen?.call();
});
_socket.onMessage.listen((e) {
onMessage?.call(e.data);
});
_socket.onClose.listen((e) {
onClose?.call(e.code, e.reason);
});
} catch (e) {
onClose?.call(500, e.toString());
}
}
send(data) {
if (_socket != null && _socket.readyState == WebSocket.OPEN) {
_socket.send(data);
print('send: $data');
} else {
print('WebSocket not connected, message $data not sent');
}
}
close() {
if (_socket != null) {
_socket.close();
}
}
}

@ -0,0 +1,234 @@
//
// import 'dart:async';
// import 'dart:collection';
// import 'dart:html' as html;
// import 'dart:io';
// import 'dart:js' as js;
// import 'dart:typed_data';
// import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/js_wrapper/multistreammixer-wrapper.dart';
// import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/utils/blob-extension.dart';
// import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/utils/upload-recording.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter_webrtc/flutter_webrtc.dart';
//
// final _recorder_options = {
// 'mimeType': 'video/webm;codecs=vp9,opus',
// 'bitsPerSecond': 128000,
// 'audioBitsPerSecond': 128000,// only for audio track
// 'videoBitsPerSecond': 128000// only for video track
// };
//
// class WebRtcStreamRecorder{
// html.MediaStream remoteStreamWeb;
// html.MediaStream localStreamWeb;
//
// html.MediaRecorder _local_recorder;
// html.MediaRecorder _mixed_recorder;
// html.MediaRecorder _remote_recorder;
// Duration chunkDuration;
//
// WebRtcStreamRecorder({@required MediaStream remoteStream, @required MediaStream localStream}){
// remoteStreamWeb = (remoteStream as dynamic).jsStream; // MediaStreamWeb.jsStream
// localStreamWeb = (localStream as dynamic).jsStream; // MediaStreamWeb.jsStream
// }
//
// Function(html.Blob blob, bool isLast, html.MediaRecorder recorder) _blobChunkCallback;
//
// onBlobChunk(Duration chunkDuration,{@required Function(html.Blob blob, bool isLast, html.MediaRecorder recorder) onBlobChunk}){
// _blobChunkCallback = onBlobChunk;
// this.chunkDuration = chunkDuration;
// }
//
// stop(){
// _mixed_recorder?.stop();
// _remote_recorder?.stop();
// _local_recorder?.stop();
// _sliceTimer?.cancel();
// }
//
// WebRtcStreamRecorder start({bool withMixedStream = true}){
// if(withMixedStream){
// _recordMixedStream();
//
// }else{
// Future.wait<html.Blob>([_startLocal(), _startRemote()]).then((value) async{
// final localVideoData = await value[0].readBytes();
// final remoteVideoData = await value[1].readBytes();
//
// processAndSendClip(localVideoData, 'local-video');
// processAndSendClip(remoteVideoData, 'remote-video');
// print("\n\n\n");
// print("------------------------------------------------------------------------------------------------------------------------");
// print("Recordings Completed");
// print("------------------------------------------------------------------------------------------------------------------------");
// print(" Local: ${localVideoData == null ? 'not recorded' : 'recorded'}");
// print("Remote: ${remoteVideoData == null ? 'not recorded' : 'recorded'}");
// print("------------------------------------------------------------------------------------------------------------------------");
// print("\n\n\n");
//
// });
// }
// return this;
// }
//
// _recordMixedStream(){
// final mixer = initMultiStreamsMixer([remoteStreamWeb,localStreamWeb]);
// final jsMixedStream = mixer.getMixedStream();
// _mixed_recorder = html.MediaRecorder(jsMixedStream, _recorder_options);
// _startRecording(_mixed_recorder).then((blob) async {
// final data = await blob?.readBytes();
// // final url = await blob?.readDataUrl();
// final params = {
// 'requesterId':'1',
// 'source':'ziktest4',
// 'target':'adeltest4',
// 'callStart':'2022-04-13 09:00',
// 'callEnd':'2022-04-13 10:00',
// 'archive':'1'
// };
//
// print("Uploading called");
// UploadRecording().upload(blob, params: params, completion: (response){
// print("Response: $response");
// }, onProgress: (progress){
// print("Upload: ${progress.toInt()}/100");
// });
//
// // String file = await FileSaver.instance.saveFile("recorded", data!, "webm", mimeType: MimeType.WEBM).catchError((e){
// // print(e);
// // });
// // print(file);
// });
// }
//
// Future<html.Blob> _startRemote() async{
// _remote_recorder = html.MediaRecorder(remoteStreamWeb, _recorder_options);
// return _startRecording(_remote_recorder);
// }
//
// Future<html.Blob> _startLocal() async{
// _local_recorder = html.MediaRecorder(localStreamWeb, _recorder_options);
// return _startRecording(_local_recorder);
// }
//
// processAndSendClip(Uint8List data, String filename) async{
// if(data != null){
// // String file = await FileSaver.instance.saveFile("$filename", data, "webm", mimeType: MimeType.OTHER).catchError((e){
// // print(e);
// // });
// // print(file);
// }else{
//
// }
// }
// }
//
// Timer _sliceTimer;
// extension xWebRtcStreamRecorder on WebRtcStreamRecorder{
//
// @override
// Future<html.Blob> _startRecording(html.MediaRecorder recorder) async{
// html.Blob _blob;
// bool isLast = false;
// if(recorder != null){
// final completer = Completer();
// recorder.addEventListener('dataavailable', (html.Event event) async{
// _blob = js.JsObject.fromBrowserObject(event)['data'];
// isLast = recorder.state == 'inactive';
// final data = await _blob?.readBytes();
// print("dataavailable isLast:$isLast data:${data?.length}");
// if(_blobChunkCallback != null)
// _blobChunkCallback(_blob, isLast, recorder);
//
// if(isLast)
// completer.complete();
// });
// recorder.start();
// print("_startRecording Start");
// _enableSliceChunks(recorder);
//
// await completer.future;
// }
//
// print("_startRecording Finish");
// return _blob;
// }
//
// _enableSliceChunks(html.MediaRecorder recorder){
// if(chunkDuration != null && recorder != null){
// _sliceTimer = Timer.periodic(chunkDuration, (timer) {
// if(recorder.stream?.active == true){
// recorder.requestData();
// }else{
// timer.cancel();
// }
// });
// }
// }
// }
//
// // class WebRecorder {
// // static bool isNotRecording = true;
// // static html.MediaRecorder? recorder;
// //
// // final Future Function() whenRecorderStart; // Function to call when recording starts
// // final Future Function() whenRecorderStop; // Function to call when recording finishs
// // final Future Function(dynamic) whenReceiveData;
// //
// // WebRecorder({
// // required this.whenRecorderStart,
// // required this.whenRecorderStop,
// // required this.whenReceiveData
// // });
// //
// // WebRecorder openRecorder({MediaStream? stream}){
// // WebRecorder.isNotRecording = !WebRecorder.isNotRecording;
// // if(WebRecorder.isNotRecording)
// // stopRecoring().whenComplete(whenRecorderStop);
// // else
// // if(stream == null){
// // html.window.navigator
// // .getUserMedia(audio: true)
// // .then((stream) {
// // recorder = html.MediaRecorder(stream);
// // recorder?.addEventListener('dataavailable', hundlerFunctionStream);
// // })
// // .whenComplete((){
// // startRecording().whenComplete(whenRecorderStart);
// // })
// // .catchError((e)=> print);
// // }else{
// // final htmlStream = html.MediaStream(stream.getTracks());
// // recorder = html.MediaRecorder(htmlStream);
// // recorder?.addEventListener('dataavailable', hundlerFunctionStream);
// // }
// //
// // return this;
// // }
// //
// // Future<void> startRecording(){
// // WebRecorder.recorder?.start();
// // return Future.value(true);
// // }
// //
// // Future<void> stopRecoring() async{
// // WebRecorder.recorder?.stop();
// // return Future.value(true);
// // }
// //
// // hundlerFunctionStream(event) async{
// // html.FileReader reader = html.FileReader();
// // html.Blob blob = js.JsObject.fromBrowserObject(event)['data'];
// // reader.readAsArrayBuffer(blob);
// // reader.onLoadEnd.listen((e) async {
// // setData(reader.result);
// // });
// // }
// //
// // setData(data) => whenReceiveData(data);
// //
// // dispose(){
// // WebRecorder.recorder?.removeEventListener('dataavailable', hundlerFunctionStream);
// // WebRecorder.recorder = null;
// // }
// // }

@ -0,0 +1,77 @@
// Copyright (c) 2016, Damon Douglas. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.
/// Simple library for generating random ascii strings.
///
/// More dartdocs go here.
///
///
/// A simple usage example:
///
/// import 'package:random_string/random_string.dart' as random;
/// main() {
/// print(randomBetween(10,20)); // some integer between 10 and 20
/// print(randomNumeric(4)); // sequence of 4 random numbers i.e. 3259
/// print(randomString(10)); // random sequence of 10 characters i.e. e~f93(4l-
/// print(randomAlpha(5)); // random sequence of 5 alpha characters i.e. aRztC
/// print(randomAlphaNumeric(10)); // random sequence of 10 alpha numeric i.e. aRztC1y32B
/// }
library random_string;
import 'dart:math';
const ASCII_START = 33;
const ASCII_END = 126;
const NUMERIC_START = 48;
const NUMERIC_END = 57;
const LOWER_ALPHA_START = 97;
const LOWER_ALPHA_END = 122;
const UPPER_ALPHA_START = 65;
const UPPER_ALPHA_END = 90;
/// Generates a random integer where [from] <= [to].
int randomBetween(int from, int to) {
if (from > to) throw Exception('$from cannot be > $to');
var rand = Random();
return ((to - from) * rand.nextDouble()).toInt() + from;
}
/// Generates a random string of [length] with characters
/// between ascii [from] to [to].
/// Defaults to characters of ascii '!' to '~'.
String randomString(int length, {int from: ASCII_START, int to: ASCII_END}) {
return String.fromCharCodes(
List.generate(length, (index) => randomBetween(from, to)));
}
/// Generates a random string of [length] with only numeric characters.
String randomNumeric(int length) =>
randomString(length, from: NUMERIC_START, to: NUMERIC_END);
/*
/// Generates a random string of [length] with only alpha characters.
String randomAlpha(int length) {
var lowerAlphaLength = randomBetween(0, length);
var upperAlphaLength = length - lowerAlphaLength;
var lowerAlpha = randomString(lowerAlphaLength,
from: LOWER_ALPHA_START, to: LOWER_ALPHA_END);
var upperAlpha = randomString(upperAlphaLength,
from: UPPER_ALPHA_START, to: UPPER_ALPHA_END);
return randomMerge(lowerAlpha, upperAlpha);
}
/// Generates a random string of [length] with alpha-numeric characters.
String randomAlphaNumeric(int length) {
var alphaLength = randomBetween(0, length);
var numericLength = length - alphaLength;
var alpha = randomAlpha(alphaLength);
var numeric = randomNumeric(numericLength);
return randomMerge(alpha, numeric);
}
/// Merge [a] with [b] and scramble characters.
String randomMerge(String a, String b) {
var mergedCodeUnits = List.from("$a$b".codeUnits);
mergedCodeUnits.shuffle();
return String.fromCharCodes(mergedCodeUnits);
}*/

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'dart:core';
class CallSettings extends StatefulWidget {
static String tag = 'call_settings';
@override
_CallSettingsState createState() => _CallSettingsState();
}
class _CallSettingsState extends State<CallSettings> {
@override
initState() {
super.initState();
}
@override
deactivate() {
super.deactivate();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings'),
),
body: OrientationBuilder(
builder: (context, orientation) {
return Center(child: Text("settings"));
},
),
);
}
}

@ -0,0 +1,510 @@
import 'dart:convert';
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../utils/websocket.dart' if (dart.library.js) '../utils/websocket_web.dart';
import '../utils/turn.dart' if (dart.library.js) '../utils/turn_web.dart';
enum SignalingState { Open, Closed, Error }
enum CallState { Calling, Ringing, Invite, Connected, Bye }
const JsonEncoder _encoder = JsonEncoder();
const JsonDecoder _decoder = JsonDecoder();
class SessionOneToOne {
String id;
SocketUser local_user;
SocketUser remote_user;
SessionOneToOne({@required this.id, @required this.local_user, @required this.remote_user});
RTCPeerConnection pc;
RTCDataChannel dc;
List<RTCIceCandidate> remoteCandidates = [];
}
class SocketUser{
String id;
String name;
String userAgent;
Map moreInfo;
SocketUser({@required this.id, @required this.name, @required this.userAgent, @required this.moreInfo});
SocketUser.from(dynamic json){
id = json['id'];
name = json['name'];
userAgent = json['user_agent'];
moreInfo = json['more_info'];
}
Map toJson() => {
"id": id,
"name": name,
"user_agent": userAgent,
"more_info": moreInfo
};
}
class Signaling {
var _host;
var _port = 8086;
var _turnCredential;
SimpleWebSocket _socket;
SessionOneToOne session;
Signaling(this._host, {@required this.session});
MediaStream localStream;
final List<MediaStream> remoteStreams = <MediaStream>[];
Function(SignalingState state) onSignalingStateChange;
Function(SessionOneToOne session, CallState state) onCallStateChange;
Function(MediaStream stream) onLocalStream;
Function(SessionOneToOne session, MediaStream stream) onAddRemoteStream;
Function(SessionOneToOne session, MediaStream stream) onRemoveRemoteStream;
Function(dynamic event) onPeersUpdate;
Function(dynamic event) onConnected;
Function(dynamic event) onRemoteConnected;
Function(SessionOneToOne session, RTCDataChannel dc, RTCDataChannelMessage data) onDataChannelMessage;
Function(SessionOneToOne session, RTCDataChannel dc) onDataChannel;
String get sdpSemantics => WebRTC.platformIsWindows ? 'plan-b' : 'unified-plan';
Map<String, dynamic> _iceServers = {
'iceServers': [
{'url': 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
]
};
final Map<String, dynamic> _dcConstraints = {
'mandatory': {
'OfferToReceiveAudio': false,
'OfferToReceiveVideo': false,
},
'optional': [],
};
close() async {
await finishSessions();
_socket?.close();
}
void switchCamera() {
if (localStream != null) {
Helper.switchCamera(localStream .getVideoTracks()[0]);
}
}
void muteMic() {
if (localStream != null) {
bool enabled = localStream .getAudioTracks()[0].enabled;
localStream .getAudioTracks()[0].enabled = !enabled;
}
}
callAccepted(SessionOneToOne session){
_send('call_accepted', {
'to': session.remote_user?.id,
'from': session.local_user.id,
'session_id': session.id,
});
}
void offer(String media, bool useScreen) async {
if(session == null)
return;
if (media == 'data') {
_createDataChannel(session);
}
_createOffer(session, media);
onCallStateChange?.call(session, CallState.Calling);
}
void bye(SessionOneToOne session) {
_send('bye', {
'session_id': session.id,
'from': session.local_user.id,
});
_closeSession(session);
}
void onMessage(message) async {
Map<String, dynamic> mapData = message;
var data = mapData['data'];
switch (mapData['type']) {
case 'call_accepted':
{
onRemoteConnected?.call(data);
}
break;
case 'connected':
{
if (onConnected != null) {
onConnected?.call(data);
}
}
break;
case 'offer':
{
var peerId = data['from'];
var description = data['description'];
var media = data['media'];
await _initPeerConnection(session, media: media, screenSharing: false);
await session.pc?.setRemoteDescription(RTCSessionDescription(description['sdp'], description['type']));
await _createAnswer(session, media);
if (session.remoteCandidates.isNotEmpty) {
session.remoteCandidates.forEach((candidate) async {
await session.pc?.addCandidate(candidate);
});
session.remoteCandidates.clear();
}
onCallStateChange?.call(session, CallState.Calling);
}
break;
case 'answer':
{
var description = data['description'];
var sessionId = data['session_id'];
session.pc?.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
break;
case 'candidate':
{
var peerId = data['from'];
var candidateMap = data['candidate'];
var sessionId = data['session_id'];
RTCIceCandidate candidate = RTCIceCandidate(candidateMap['candidate'],
candidateMap['sdpMid'], candidateMap['sdpMLineIndex']);
if (session != null) {
if (session.pc != null) {
await session.pc?.addCandidate(candidate);
} else {
session.remoteCandidates.add(candidate);
}
} else {
// _sessions[sessionId] = SessionOneToOne(pid: peerId, sid: sessionId)
// ..remoteCandidates.add(candidate);
}
}
break;
case 'leave':
{
var peerId = data as String;
_closeSessionById(peerId);
}
break;
case 'bye':
{
var sessionId = data['session_id'];
print('bye: ' + sessionId);
if (session != null) {
onCallStateChange?.call(session, CallState.Bye);
_closeSession(session);
}
}
break;
case 'keepalive':
{
print('keepalive response!');
}
break;
default:
break;
}
}
Future<void> connect() async {
_socket = SimpleWebSocket('https://$_host:$_port/ws');
if (_turnCredential == null) {
try {
_turnCredential = await getTurnCredential(_host, _port);
/*
{
"username": "1584195784:mbzrxpgjys",
"password": "isyl6FF6nqMTB9/ig5MrMRUXqZg",
"ttl": 86400,
"uris": ["turn:127.0.0.1:19302?transport=udp"]
}
*/
_iceServers = {
'iceServers': [
{
'urls': _turnCredential['uris'][0],
'username': _turnCredential['username'],
'credential': _turnCredential['password']
},
]
};
} catch (e) {}
}
_socket?.onOpen = () {
print('onOpen');
onSignalingStateChange?.call(SignalingState.Open);
_send('connect', session.local_user.toJson());
};
_socket?.onMessage = (message) {
print('Received data: ' + message);
onMessage(_decoder.convert(message));
};
_socket?.onClose = (int code, String reason) {
print('Closed by server [$code => $reason]!');
onSignalingStateChange?.call(SignalingState.Closed);
};
await _socket?.connect();
}
Future<MediaStream> createStream(String media, bool userScreen) async {
final Map<String, dynamic> mediaConstraints = {
'audio': userScreen ? false : true,
'video': userScreen
? true
: {
'mandatory': {
'minWidth': '640', // Provide your own width, height and frame rate here
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = userScreen
? await navigator.mediaDevices.getDisplayMedia(mediaConstraints)
: await navigator.mediaDevices.getUserMedia(mediaConstraints);
onLocalStream?.call(stream);
return stream;
}
Future<SessionOneToOne> _initPeerConnection(SessionOneToOne session, {@required String media, @required bool screenSharing}) async {
if (media != 'data')
localStream = await createStream(media, screenSharing);
print(_iceServers);
RTCPeerConnection pc = await createPeerConnection({
..._iceServers,
...{'sdpSemantics': sdpSemantics}
}, _config);
if (media != 'data') {
switch (sdpSemantics) {
case 'plan-b':
pc.onAddStream = (MediaStream stream) {
onAddRemoteStream?.call(session, stream);
remoteStreams.add(stream);
};
await pc.addStream(localStream);
break;
case 'unified-plan':
// Unified-Plan
pc.onTrack = (event) {
if (event.track.kind == 'video') {
onAddRemoteStream?.call(session, event.streams[0]);
}
};
localStream .getTracks().forEach((track) {
pc.addTrack(track, localStream);
});
break;
}
// Unified-Plan: Simuclast
/*
await pc.addTransceiver(
track: _localStream.getAudioTracks()[0],
init: RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly, streams: [_localStream]),
);
await pc.addTransceiver(
track: _localStream.getVideoTracks()[0],
init: RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly,
streams: [
_localStream
],
sendEncodings: [
RTCRtpEncoding(rid: 'f', active: true),
RTCRtpEncoding(
rid: 'h',
active: true,
scaleResolutionDownBy: 2.0,
maxBitrate: 150000,
),
RTCRtpEncoding(
rid: 'q',
active: true,
scaleResolutionDownBy: 4.0,
maxBitrate: 100000,
),
]),
);*/
/*
var sender = pc.getSenders().find(s => s.track.kind == "video");
var parameters = sender.getParameters();
if(!parameters)
parameters = {};
parameters.encodings = [
{ rid: "h", active: true, maxBitrate: 900000 },
{ rid: "m", active: true, maxBitrate: 300000, scaleResolutionDownBy: 2 },
{ rid: "l", active: true, maxBitrate: 100000, scaleResolutionDownBy: 4 }
];
sender.setParameters(parameters);
*/
}
pc.onIceCandidate = (candidate) async {
if (candidate == null) {
print('onIceCandidate: complete!');
return;
}
// This delay is needed to allow enough time to try an ICE candidate
// before skipping to the next one. 1 second is just an heuristic value
// and should be thoroughly tested in your own environment.
await Future.delayed(
const Duration(seconds: 1),
() => _send('candidate', {
'to': session.remote_user?.id,
'from': session.local_user.id,
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
'session_id': session.id,
}));
};
pc.onIceConnectionState = (state) {};
pc.onRemoveStream = (stream) {
onRemoveRemoteStream?.call(session, stream);
remoteStreams.removeWhere((it) {
return (it.id == stream.id);
});
};
pc.onDataChannel = (channel) {
_addDataChannel(session, channel);
};
session.pc = pc;
return session;
}
void _addDataChannel(SessionOneToOne session, RTCDataChannel channel) {
channel.onDataChannelState = (e) {};
channel.onMessage = (RTCDataChannelMessage data) {
onDataChannelMessage?.call(session, channel, data);
};
session.dc = channel;
onDataChannel?.call(session, channel);
}
Future<void> _createDataChannel(SessionOneToOne session, {label: 'fileTransfer'}) async {
RTCDataChannelInit dataChannelDict = RTCDataChannelInit()
..maxRetransmits = 30;
RTCDataChannel channel =
await session.pc .createDataChannel(label, dataChannelDict);
_addDataChannel(session, channel);
}
Future<void> _createOffer(SessionOneToOne session, String media) async {
try {
RTCSessionDescription s =
await session.pc .createOffer(media == 'data' ? _dcConstraints : {});
await session.pc .setLocalDescription(s);
_send('offer', {
'to': session.remote_user?.id,
'from': session.local_user.id,
'description': {'sdp': s.sdp, 'type': s.type},
'session_id': session.id,
'media': media,
});
} catch (e) {
print(e.toString());
}
}
Future<void> _createAnswer(SessionOneToOne session, String media) async {
try {
RTCSessionDescription s =
await session.pc .createAnswer(media == 'data' ? _dcConstraints : {});
await session.pc .setLocalDescription(s);
_send('answer', {
'to': session.remote_user?.id,
'from': session.local_user.id,
'description': {'sdp': s.sdp, 'type': s.type},
'session_id': session.id,
});
} catch (e) {
print(e.toString());
}
}
_send(event, data) {
var request = Map();
request["type"] = event;
request["data"] = data;
_socket?.send(_encoder.convert(request));
}
Future<void> finishSessions() async {
_closeSessionById(session.id);
}
void _closeSessionById(String sessionId) {
if (session != null && session.id == sessionId) {
_closeSession(session);
onCallStateChange?.call(session, CallState.Bye);
}
}
Future<void> _closeSession(SessionOneToOne session) async {
localStream?.getTracks()?.forEach((element) async {
await element.stop();
});
await localStream?.dispose();
localStream = null;
await session.pc?.close();
await session.dc?.close();
}
}

@ -0,0 +1,240 @@
import 'dart:async';
import 'package:diplomaticquarterapp/pages/conference/conference_button_bar.dart';
import 'package:diplomaticquarterapp/pages/conference/web_rtc/widgets/cam_view_widget.dart';
import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/utils/device_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:core';
import 'signaling.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
class StartVideoCall extends StatefulWidget {
static String tag = 'webrtc';
final String host;
String caller;
String receiver;
bool iAmCaller;
StartVideoCall({@required this.caller, @required this.receiver, this.iAmCaller = false, @required this.host});
@override
StartVideoCallState createState() => StartVideoCallState(receiverId: receiver, callerId: caller, iAmCaller: iAmCaller);
}
class StartVideoCallState extends State<StartVideoCall> {
Signaling _signaling;
SessionOneToOne _session;
StartVideoCallState({@required String callerId, @required String receiverId, @required bool iAmCaller}){
final self_role = iAmCaller ? "Caller" : "Receiver";
final self_id = iAmCaller ? callerId : receiverId;
final self_user = SocketUser(id: self_id, name: "$self_role-$self_id", userAgent: DeviceInfo.userAgent, moreInfo: {});
final remote_role = !iAmCaller ? "Caller" : "Receiver";
final remote_id = !iAmCaller ? callerId : receiverId;
final remote_user = SocketUser(id: remote_id, name: "$remote_role-$remote_id", userAgent: DeviceInfo.userAgent, moreInfo: {});
final session_id = "$callerId-$receiverId";
_session = SessionOneToOne(id: session_id, local_user: self_user, remote_user: remote_user);
}
bool showNoise = true;
final StreamController<bool> _audioButton = StreamController<bool>.broadcast();
final StreamController<bool> _videoButton = StreamController<bool>.broadcast();
final StreamController<bool> _onButtonBarVisibleStreamController = StreamController<bool>.broadcast();
final StreamController<double> _onButtonBarHeightStreamController = StreamController<double>.broadcast();
final RTCVideoRenderer _localRenderer = RTCVideoRenderer();
final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
MediaStream get localMediaStream => _signaling.localStream;
MediaStream get remoteMediaStream => _signaling.remoteStreams.first;
@override
initState() {
super.initState();
initRenderers();
_connect();
}
initRenderers() async {
await _localRenderer.initialize();
await _remoteRenderer.initialize();
}
@override
deactivate() {
super.deactivate();
_signaling?.close();
_localRenderer.dispose();
_remoteRenderer.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text('P2P Call Sample' + (_session?.local_user != null ? ' [Your ID (${_session?.local_user?.id})] ' : '')),
// actions: <Widget>[
// IconButton(
// icon: const Icon(Icons.settings),
// onPressed: (){
// setState(() {
// });
// },
// tooltip: 'setup',
// ),
// ],
// ),
body: videoCanvasWidgets(),
);
}
LayoutBuilder videoCanvasWidgets() {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Stack(
children: [
CamViewWidget(
localRenderer: _localRenderer,
remoteRenderer: _remoteRenderer,
constraints: constraints,
onButtonBarVisibleStreamController: _onButtonBarVisibleStreamController,
onButtonBarHeightStreamController: _onButtonBarHeightStreamController,
),
ConferenceButtonBar(
audioEnabled: _audioButton.stream,
videoEnabled: _videoButton.stream,
onAudioEnabled: _onAudioEnable,
onVideoEnabled: _onVideoEnabled,
onSwitchCamera: _onSwitchCamera,
onHangup: _onHangup,
onPersonAdd: () {},
onPersonRemove: () {},
onHeight: _onHeightBar,
onShow: _onShowBar,
onHide: _onHideBar,
),
],
);
},
);
}
void _connect() async {
if(_session == null)
return;
_signaling ??= Signaling(widget.host, session: _session)..connect();
_signaling?.onSignalingStateChange = (SignalingState state) {
switch (state) {
case SignalingState.Closed:
case SignalingState.Error:
case SignalingState.Open:
break;
}
};
_signaling?.onCallStateChange = (SessionOneToOne session, CallState state) {
switch (state) {
case CallState.Calling:
setState(() {});
break;
case CallState.Bye:
setState(() {
_localRenderer.srcObject = null;
_remoteRenderer.srcObject = null;
_session = null;
});
break;
case CallState.Invite:
case CallState.Connected:
case CallState.Ringing:
}
};
_signaling?.onConnected = ((event) {
_signaling?.callAccepted(_session);
});
_signaling?.onRemoteConnected = ((event) {
});
_signaling?.onLocalStream = ((stream) {
_localRenderer.srcObject = stream;
setState(() {
});
});
_signaling?.onAddRemoteStream = ((_, stream) {
_remoteRenderer.srcObject = stream;
setState(() {});
});
_signaling?.onRemoveRemoteStream = ((_, stream) {
setState(() {
_remoteRenderer.srcObject = null;
});
});
}
_hangUp() async{
if (_session != null) {
_signaling?.bye(_session);
}
}
_switchCamera() {
_signaling?.switchCamera();
}
_muteMic() {
_signaling?.muteMic();
}
Function _onAudioEnable() {
final audioTrack = localMediaStream.getAudioTracks()[0];
final mute = audioTrack.muted;
Helper.setMicrophoneMute(!mute, audioTrack);
_audioButton.add(mute);
}
Function _onVideoEnabled() {
final videoTrack = localMediaStream.getVideoTracks()[0];
bool videoEnabled = videoTrack.enabled;
localMediaStream.getVideoTracks()[0].enabled = !videoEnabled;
_videoButton.add(!videoEnabled);
}
Function _onSwitchCamera() {
Helper.switchCamera(localMediaStream.getVideoTracks()[0]);
}
void _onShowBar() {
setState(() {
});
_onButtonBarVisibleStreamController.add(true);
}
void _onHeightBar(double height) {
_onButtonBarHeightStreamController.add(height);
}
void _onHideBar() {
setState(() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom]);
});
_onButtonBarVisibleStreamController.add(false);
}
Future<void> _onHangup() async {
_signaling?.finishSessions();
print('onHangup');
Navigator.of(context).pop();
}
}

@ -1351,6 +1351,8 @@ class TranslationBase {
String get selectClinic => localizedValues['selectClinic'][locale.languageCode];
String get selectProject => localizedValues['selectProject'][locale.languageCode];
String get orderStatus => localizedValues['orderStatus'][locale.languageCode];
String get findUs => localizedValues['FindUs'][locale.languageCode];
@ -2678,6 +2680,8 @@ class TranslationBase {
String get browseOffers => localizedValues["browseOffers"][locale.languageCode];
String get browseOffersByProject => localizedValues["browseOffersByProject"][locale.languageCode];
String get myCart => localizedValues["myCart"][locale.languageCode];
String get neck => localizedValues["neck"][locale.languageCode];

Loading…
Cancel
Save