Web RTC Design implementation

merge-update-with-lab-changes
Mirza.Shafique 4 years ago
parent 999a45c711
commit ef2bcc1c65

@ -101,7 +101,7 @@ class _ConferencePageState extends State<ConferencePage> {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
backgroundColor: Colors.black,
backgroundColor: Colors.white,
body: _conferenceRoom == null ? showProgress() : buildLayout(),
),
);

@ -0,0 +1,172 @@
import 'dart:async';
import 'package:diplomaticquarterapp/pages/conference/web_rtc/widgets/cam_view_widget.dart';
import 'package:diplomaticquarterapp/pages/conference/widgets/noise_box.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../conference_button_bar.dart';
class CallHomePage extends StatefulWidget {
@override
_CallHomePageState createState() => _CallHomePageState();
}
class _CallHomePageState extends State<CallHomePage> {
bool showNoise = false;
RTCVideoRenderer _localRenderer = RTCVideoRenderer();
RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
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();
//Stream to enable video
MediaStream stream;
@override
void initState() {
// TODO: implement initState
super.initState();
_localRenderer.initialize();
_remoteRenderer.initialize();
enableVideo();
}
enableVideo() async {
//Stream to enable video
stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': true});
// _audioButton.add(false);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_localRenderer.dispose();
_remoteRenderer.dispose();
_audioButton.close();
_videoButton.close();
stream.dispose();
_disposeStreamsAndSubscriptions();
}
Future<void> _disposeStreamsAndSubscriptions() async {
if (_onButtonBarVisibleStreamController != null) await _onButtonBarVisibleStreamController.close();
if (_onButtonBarHeightStreamController != null) await _onButtonBarHeightStreamController.close();
}
@override
Widget build(BuildContext context) {
// return WillPopScope(
// onWillPop: () async => false,
// child: Scaffold(
// backgroundColor: Colors.black,
// // body: ,
// ),
// );
return Scaffold(
backgroundColor: Colors.white,
body: showNoise ? _buildNoiseBox() : buildLayout(),
);
}
LayoutBuilder buildLayout() {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Stack(
children: [
CamViewWidget(
localRenderer: _localRenderer,
remoteRenderer: _remoteRenderer,
stream: stream,
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,
),
],
);
},
);
}
NoiseBox _buildNoiseBox() {
return NoiseBox(
density: NoiseBoxDensity.xLow,
backgroundColor: Colors.grey.shade900,
child: Center(
child: Container(
color: Colors.black54,
width: double.infinity,
height: 40,
child: Center(
child: Text(
'Waiting for another participant to connect to the call...',
key: Key('text-wait'),
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
),
),
),
);
}
Function _onAudioEnable() {
bool enabled = stream.getAudioTracks()[0].enabled;
stream.getAudioTracks()[0].enabled = !enabled;
_audioButton.add(!enabled);
}
Function _onVideoEnabled() {
bool enabled = stream.getVideoTracks()[0].enabled;
stream.getVideoTracks()[0].enabled = !enabled;
_videoButton.add(!enabled);
}
Function _onSwitchCamera() {
// stream.getAudioTracks()[0].enabled = false;
// stream.getVideoTracks()[0].enabled = false;
Helper.switchCamera(stream.getVideoTracks()[0]);
}
void _onShowBar() {
setState(() {
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]);
});
_onButtonBarVisibleStreamController.add(true);
}
void _onHeightBar(double height) {
_onButtonBarHeightStreamController.add(height);
}
void _onHideBar() {
setState(() {
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
});
_onButtonBarVisibleStreamController.add(false);
}
Future<void> _onHangup() async {
print('onHangup');
Navigator.of(context).pop();
}
}

@ -0,0 +1,61 @@
import 'dart:async';
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'draggable_cam.dart';
class CamViewWidget extends StatefulWidget {
RTCVideoRenderer localRenderer;
RTCVideoRenderer remoteRenderer;
MediaStream stream;
BoxConstraints constraints;
StreamController<bool> onButtonBarVisibleStreamController;
StreamController<double> onButtonBarHeightStreamController;
CamViewWidget({this.localRenderer, this.remoteRenderer, this.stream, this.constraints, this.onButtonBarVisibleStreamController, this.onButtonBarHeightStreamController});
@override
_CamViewWidgetState createState() => _CamViewWidgetState();
}
class _CamViewWidgetState extends State<CamViewWidget> {
@override
void initState() {
super.initState();
Future.delayed(const Duration(milliseconds: 300), () {
showCamera();
});
}
showCamera() async {
setState(() async {
widget.localRenderer.srcObject = widget.stream;
widget.remoteRenderer.srcObject = widget.stream;
});
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
child: Stack(
children: [
Container(
child: RTCVideoView(widget.localRenderer, mirror: true),
),
DraggableCam(
key: Key('publisher'),
onButtonBarHeight: widget.onButtonBarHeightStreamController.stream,
onButtonBarVisible: widget.onButtonBarVisibleStreamController.stream,
availableScreenSize: widget.constraints.biggest,
child: RTCVideoView(widget.remoteRenderer),
),
// Expanded(child: RTCVideoView(widget.remoteRenderer)),
],
),
);
}
}

@ -0,0 +1,173 @@
import 'dart:async';
import 'dart:io';
import 'package:diplomaticquarterapp/pages/conference/clipped_video.dart';
import 'package:flutter/material.dart';
class DraggableCam extends StatefulWidget {
final Size availableScreenSize;
final Widget child;
final double scaleFactor;
final Stream<bool> onButtonBarVisible;
final Stream<double> onButtonBarHeight;
const DraggableCam({
Key key,
@required this.availableScreenSize,
this.child,
@required this.onButtonBarVisible,
@required this.onButtonBarHeight,
/// The portion of the screen the DraggableWidget should use.
this.scaleFactor = .25,
}) : assert(scaleFactor != null && scaleFactor > 0 && scaleFactor <= .4),
assert(availableScreenSize != null),
assert(onButtonBarVisible != null),
assert(onButtonBarHeight != null),
super(key: key);
@override
_DraggablePublisherState createState() => _DraggablePublisherState();
}
class _DraggablePublisherState extends State<DraggableCam> {
bool _isButtonBarVisible = true;
double _buttonBarHeight = 0;
double _width;
double _height;
double _top;
double _left;
double _viewPaddingTop;
double _viewPaddingBottom;
final double _padding = 8.0;
final Duration _duration300ms = const Duration(milliseconds: 300);
final Duration _duration0ms = const Duration(milliseconds: 0);
Duration _duration;
StreamSubscription _streamSubscription;
StreamSubscription _streamHeightSubscription;
@override
void initState() {
super.initState();
_duration = _duration300ms;
_width = widget.availableScreenSize.width * widget.scaleFactor;
_height = _width * (widget.availableScreenSize.height / widget.availableScreenSize.width);
_top = widget.availableScreenSize.height - (_buttonBarHeight + _padding) - _height;
_left = widget.availableScreenSize.width - _padding - _width;
_streamSubscription = widget.onButtonBarVisible.listen(_buttonBarVisible);
_streamHeightSubscription = widget.onButtonBarHeight.listen(_getButtonBarHeight);
}
@override
void didChangeDependencies() {
final mediaQuery = MediaQuery.of(context);
_viewPaddingTop = mediaQuery.viewPadding.top;
_viewPaddingBottom = mediaQuery.viewPadding.bottom;
super.didChangeDependencies();
}
@override
void dispose() {
_streamSubscription.cancel();
_streamHeightSubscription.cancel();
super.dispose();
}
void _getButtonBarHeight(double height) {
setState(() {
_buttonBarHeight = height;
_positionWidget();
});
}
void _buttonBarVisible(bool visible) {
if (!mounted) {
return;
}
setState(() {
_isButtonBarVisible = visible;
if (_duration == _duration300ms) {
// only position the widget when we are not currently dragging it around
_positionWidget();
}
});
}
@override
Widget build(BuildContext context) {
return AnimatedPositioned(
top: _top,
left: _left,
width: _width,
height: _height,
duration: _duration,
child: Listener(
onPointerDown: (_) => _duration = _duration0ms,
onPointerMove: (PointerMoveEvent event) {
setState(() {
_left = (_left + event.delta.dx).roundToDouble();
_top = (_top + event.delta.dy).roundToDouble();
});
},
onPointerUp: (_) => _positionWidget(),
onPointerCancel: (_) => _positionWidget(),
child: ClippedVideo(
height: _height,
width: _width,
child: widget.child,
),
),
);
}
double _getCurrentStatusBarHeight() {
if (_isButtonBarVisible) {
return _viewPaddingTop;
}
final _defaultViewPaddingTop = Platform.isIOS ? 20.0 : Platform.isAndroid ? 24.0 : 0.0;
if (_viewPaddingTop > _defaultViewPaddingTop) {
// There must be a hardware notch in the display.
return _viewPaddingTop;
}
return 0.0;
}
double _getCurrentButtonBarHeight() {
if (_isButtonBarVisible) {
return _buttonBarHeight + _viewPaddingBottom;
}
return _viewPaddingBottom;
}
void _positionWidget() {
// Determine the center of the object being dragged so we can decide
// in which corner the object should be placed.
var dx = (_width / 2) + _left;
dx = dx < 0 ? 0 : dx >= widget.availableScreenSize.width ? widget.availableScreenSize.width - 1 : dx;
var dy = (_height / 2) + _top;
dy = dy < 0 ? 0 : dy >= widget.availableScreenSize.height ? widget.availableScreenSize.height - 1 : dy;
final draggableCenter = Offset(dx, dy);
setState(() {
_duration = _duration300ms;
if (Rect.fromLTRB(0, 0, widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2).contains(draggableCenter)) {
// Top-left
_top = _getCurrentStatusBarHeight() + _padding;
_left = _padding;
} else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, 0, widget.availableScreenSize.width, widget.availableScreenSize.height / 2).contains(draggableCenter)) {
// Top-right
_top = _getCurrentStatusBarHeight() + _padding;
_left = widget.availableScreenSize.width - _padding - _width;
} else if (Rect.fromLTRB(0, widget.availableScreenSize.height / 2, widget.availableScreenSize.width / 2, widget.availableScreenSize.height).contains(draggableCenter)) {
// Bottom-left
_top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height;
_left = _padding;
} else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2, widget.availableScreenSize.width, widget.availableScreenSize.height).contains(draggableCenter)) {
// Bottom-right
_top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height;
_left = widget.availableScreenSize.width - _padding - _width;
}
});
}
}

@ -22,6 +22,7 @@ import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:diplomaticquarterapp/pages/conference/web_rtc/call_home_page.dart';
import 'dart:math' as math;
class HomePageFragment2 extends StatefulWidget {
@ -95,7 +96,8 @@ class _HomePageFragment2State extends State<HomePageFragment2> {
margin: EdgeInsets.only(left: 20, right: 20, top: 8, bottom: 6),
child: SliderView(
onLoginClick: () {
widget.onLoginClick();
// widget.onLoginClick();
navigateTo(context, CallHomePage());
},
),
// height: MediaQuery.of(context).size.width / 2.6,

@ -1,6 +1,7 @@
import 'package:diplomaticquarterapp/models/LiveCare/IncomingCallData.dart';
import 'package:diplomaticquarterapp/models/LiveCare/room_model.dart';
import 'package:diplomaticquarterapp/pages/conference/conference_page.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/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/material.dart';
@ -15,16 +16,14 @@ class IncomingCall extends StatefulWidget {
_IncomingCallState createState() => _IncomingCallState();
}
class _IncomingCallState extends State<IncomingCall>
with SingleTickerProviderStateMixin {
class _IncomingCallState extends State<IncomingCall> with SingleTickerProviderStateMixin {
AnimationController _animationController;
final player = AudioPlayer();
@override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
_animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500));
WidgetsBinding.instance.addPostFrameCallback((_) => _runAnimation());
@ -57,18 +56,12 @@ class _IncomingCallState extends State<IncomingCall>
Container(
margin: EdgeInsets.only(top: 30.0),
alignment: Alignment.center,
child: Text("Incoming Video Call",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 26.0,
color: Colors.white,
letterSpacing: 1.0)),
child: Text("Incoming Video Call", textAlign: TextAlign.center, style: TextStyle(fontSize: 26.0, color: Colors.white, letterSpacing: 1.0)),
),
Container(
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(50.0, 30.0, 50.0, 20.0),
child: Image.asset(
'assets/images/new-design/hmg_full_logo_hd_white.png'),
child: Image.asset('assets/images/new-design/hmg_full_logo_hd_white.png'),
),
Container(
margin: EdgeInsets.fromLTRB(30.0, 10.0, 30.0, 0.0),
@ -80,33 +73,17 @@ class _IncomingCallState extends State<IncomingCall>
Container(
margin: EdgeInsets.only(top: 20.0),
alignment: Alignment.center,
child: Text("Dr Eyad Ismail Abu Jayab",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
letterSpacing: 0.8,
color: Colors.white)),
child: Text("Dr Eyad Ismail Abu Jayab", textAlign: TextAlign.center, style: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold, letterSpacing: 0.8, color: Colors.white)),
),
Container(
margin: EdgeInsets.only(top: 10.0),
alignment: Alignment.center,
child: Text("ENT Clinic",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
letterSpacing: 0.8,
color: Colors.white)),
child: Text("ENT Clinic", textAlign: TextAlign.center, style: TextStyle(fontSize: 22.0, letterSpacing: 0.8, color: Colors.white)),
),
Container(
margin: EdgeInsets.only(top: 10.0),
alignment: Alignment.center,
child: Text("Speciality",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22.0,
letterSpacing: 0.8,
color: Colors.white)),
child: Text("Speciality", textAlign: TextAlign.center, style: TextStyle(fontSize: 22.0, letterSpacing: 0.8, color: Colors.white)),
),
Container(
decoration: BoxDecoration(
@ -117,30 +94,14 @@ class _IncomingCallState extends State<IncomingCall>
margin: EdgeInsets.only(top: 20.0),
child: Column(
children: <Widget>[
Text("Appointment Information",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
letterSpacing: 1.0,
color: Colors.white)),
Text("Appointment Information", textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, letterSpacing: 1.0, color: Colors.white)),
Container(
margin: EdgeInsets.only(top: 20.0),
child: Text("Sun, 15th Dec, 2019, 09:00",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
letterSpacing: 1.0,
color: Colors.white)),
child: Text("Sun, 15th Dec, 2019, 09:00", textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0, letterSpacing: 1.0, color: Colors.white)),
),
Container(
margin: EdgeInsets.only(top: 20.0),
child: Text("ENT Clinic",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
letterSpacing: 1.0,
color: Colors.white)),
child: Text("ENT Clinic", textAlign: TextAlign.center, style: TextStyle(fontSize: 20.0, letterSpacing: 1.0, color: Colors.white)),
),
],
),
@ -153,9 +114,7 @@ class _IncomingCallState extends State<IncomingCall>
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RotationTransition(
turns: Tween(begin: 0.0, end: -.1)
.chain(CurveTween(curve: Curves.elasticIn))
.animate(_animationController),
turns: Tween(begin: 0.0, end: -.1).chain(CurveTween(curve: Curves.elasticIn)).animate(_animationController),
child: Container(
child: RawMaterialButton(
onPressed: () {
@ -208,16 +167,19 @@ class _IncomingCallState extends State<IncomingCall>
Future<void> _submit() async {
backToHome();
try {
final roomModel = RoomModel(
name: widget.incomingCallData.name,
token: widget.incomingCallData.sessionId,
identity: widget.incomingCallData.identity);
final roomModel = RoomModel(name: widget.incomingCallData.name, token: widget.incomingCallData.sessionId, identity: widget.incomingCallData.identity);
// await Navigator.of(context).push(
// MaterialPageRoute<ConferencePage>(
// fullscreenDialog: true,
// builder: (BuildContext context) =>
// ConferencePage(roomModel: roomModel),
// ),
// );
await Navigator.of(context).push(
MaterialPageRoute<ConferencePage>(
fullscreenDialog: true,
builder: (BuildContext context) =>
ConferencePage(roomModel: roomModel),
builder: (BuildContext context) => CallHomePage(),
),
);
} catch (err) {

@ -71,6 +71,7 @@ dependencies:
#GIF image
flutter_gifimage: ^1.0.1
flutter_webrtc: any
# UI Reqs
dotted_border: 1.0.5

Loading…
Cancel
Save