@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
44import 'package:flutter/material.dart' ;
55import 'package:flutter_webrtc/flutter_webrtc.dart' ;
66import 'package:flutter_whip/flutter_whip.dart' ;
7+ import 'package:shared_preferences/shared_preferences.dart' ;
78
89import 'qr_scanner.dart' ;
910
@@ -17,21 +18,37 @@ class WhipPublishSample extends StatefulWidget {
1718class _WhipPublishSampleState extends State <WhipPublishSample > {
1819 MediaStream ? _localStream;
1920 final _localRenderer = RTCVideoRenderer ();
20- List < MediaDeviceInfo > ? _mediaDevicesList ;
21+ String stateStr = 'init' ;
2122 bool _connecting = false ;
2223 late WHIP _whip;
23- String ? url;
24+ 25+ TextEditingController _serverController = TextEditingController ();
26+ late SharedPreferences _preferences;
2427
2528 @override
2629 void initState () {
2730 super .initState ();
2831 initRenderers ();
32+ _loadSettings ();
33+ }
34+ 35+ void _loadSettings () async {
36+ _preferences = await SharedPreferences .getInstance ();
37+ this .setState (() {
38+ _serverController.text = _preferences.getString ('pushserver' ) ??
39+ 'http://localhost:8080/whip/live/stream1' ;
40+ });
2941 }
3042
3143 @override
3244 void deactivate () {
3345 super .deactivate ();
3446 _localRenderer.dispose ();
47+ _saveSettings ();
48+ }
49+ 50+ void _saveSettings () {
51+ _preferences.setString ('pushserver' , _serverController.text);
3552 }
3653
3754 void initRenderers () async {
@@ -40,17 +57,45 @@ class _WhipPublishSampleState extends State<WhipPublishSample> {
4057
4158 // Platform messages are asynchronous, so we initialize in an async method.
4259 void _connect () async {
43- if (url == null ) {
60+ final url = _serverController.text;
61+ 62+ if (url.isEmpty) {
4463 return ;
4564 }
4665
47- _whip = WHIP (url: url! );
66+ _whip = WHIP (url: url);
67+ 68+ _whip.onState = (WhipState state) {
69+ setState (() {
70+ switch (state) {
71+ case WhipState .kNew:
72+ stateStr = 'New' ;
73+ break ;
74+ case WhipState .kInitialized:
75+ stateStr = 'Initialized' ;
76+ break ;
77+ case WhipState .kConnecting:
78+ stateStr = 'Connecting' ;
79+ break ;
80+ case WhipState .kConnected:
81+ stateStr = 'Connected' ;
82+ break ;
83+ case WhipState .kDisconnected:
84+ stateStr = 'Closed' ;
85+ break ;
86+ case WhipState .kFailure:
87+ stateStr = 'Failure: \n ${_whip .lastError .toString ()}' ;
88+ break ;
89+ }
90+ });
91+ };
92+ 4893 final mediaConstraints = < String , dynamic > {
4994 'audio' : true ,
5095 'video' : {
5196 'mandatory' : {
52- 'minWidth' : '640 ' ,
53- 'minHeight' : '480 ' ,
97+ 'minWidth' : '1280 ' ,
98+ 'minHeight' : '720 ' ,
5499 'minFrameRate' : '30' ,
55100 },
56101 'facingMode' : 'user' ,
@@ -60,13 +105,14 @@ class _WhipPublishSampleState extends State<WhipPublishSample> {
60105
61106 try {
62107 var stream = await navigator.mediaDevices.getUserMedia (mediaConstraints);
63- _mediaDevicesList = await navigator.mediaDevices.enumerateDevices ();
64108 _localStream = stream;
65109 _localRenderer.srcObject = _localStream;
66110 await _whip.initlize (mode: WhipMode .kSend, stream: _localStream);
67111 await _whip.connect ();
68112 } catch (e) {
69- print (e.toString ());
113+ print ('connect: error => ' + e.toString ());
114+ _localRenderer.srcObject = null ;
115+ _localStream? .dispose ();
70116 return ;
71117 }
72118 if (! mounted) return ;
@@ -108,75 +154,79 @@ class _WhipPublishSampleState extends State<WhipPublishSample> {
108154 IconButton (
109155 icon: Icon (Icons .qr_code_scanner_sharp),
110156 onPressed: () async {
111- Future future = Navigator .of (context).push (
112- MaterialPageRoute (builder: (context) => QRViewExample ()));
113- 114- future.then ((value) {
115- print ('QR code result: $value ' );
116- this .setState (() {
117- url = value;
157+ if (! WebRTC .platformIsDesktop) {
158+ /// only support mobile for now
159+ Future future = Navigator .of (context).push (
160+ MaterialPageRoute (builder: (context) => QRViewExample ()));
161+ future.then ((value) {
162+ print ('QR code result: $value ' );
163+ this .setState (() {
164+ _serverController.text = value;
165+ });
118166 });
119- });
167+ }
120168 },
121169 ),
122170 if (_connecting)
123171 IconButton (
124172 icon: Icon (Icons .switch_video),
125173 onPressed: _toggleCamera,
126174 ),
127- if (_connecting)
128- PopupMenuButton <String >(
129- onSelected: _selectAudioOutput,
130- itemBuilder: (BuildContext context) {
131- if (_mediaDevicesList != null ) {
132- return _mediaDevicesList!
133- .where ((device) => device.kind == 'audiooutput' )
134- .map ((device) {
135- return PopupMenuItem <String >(
136- value: device.deviceId,
137- child: Text (device.label),
138- );
139- }).toList ();
140- }
141- return [];
142- },
143- ),
144175 ]),
145176 body: OrientationBuilder (
146177 builder: (context, orientation) {
147178 return Column (children: < Widget > [
148- FittedBox (
149- child: Text (
150- 'URL: ${url ?? 'Not set, Please scan the QR code ...' }' ,
151- textAlign: TextAlign .left,
152- ),
153- ),
154- Center (
155- child: Container (
156- margin: EdgeInsets .fromLTRB (0.0 , 0.0 , 0.0 , 0.0 ),
157- width: MediaQuery .of (context).size.width,
158- height: MediaQuery .of (context).size.height - 110 ,
159- decoration: BoxDecoration (color: Colors .black54),
160- child: RTCVideoView (_localRenderer,
161- mirror: true ,
162- objectFit:
163- RTCVideoViewObjectFit .RTCVideoViewObjectFitCover ),
179+ Column (children: < Widget > [
180+ FittedBox (
181+ child: Text (
182+ '${stateStr }' ,
183+ textAlign: TextAlign .left,
184+ ),
164185 ),
165- )
186+ if (! _connecting)
187+ Padding (
188+ padding: const EdgeInsets .fromLTRB (10.0 , 18.0 , 10.0 , 0 ),
189+ child: Align (
190+ child: Text ('WHIP URI:' ),
191+ alignment: Alignment .centerLeft,
192+ ),
193+ ),
194+ if (! _connecting)
195+ Padding (
196+ padding: const EdgeInsets .fromLTRB (10.0 , 0.0 , 10.0 , 0 ),
197+ child: TextFormField (
198+ controller: _serverController,
199+ keyboardType: TextInputType .text,
200+ textAlign: TextAlign .center,
201+ decoration: InputDecoration (
202+ contentPadding: EdgeInsets .all (10.0 ),
203+ border: UnderlineInputBorder (
204+ borderSide: BorderSide (color: Colors .black12)),
205+ ),
206+ ),
207+ )
208+ ]),
209+ if (_connecting)
210+ Center (
211+ child: Container (
212+ margin: EdgeInsets .fromLTRB (0.0 , 0.0 , 0.0 , 0.0 ),
213+ width: MediaQuery .of (context).size.width,
214+ height: MediaQuery .of (context).size.height - 110 ,
215+ decoration: BoxDecoration (color: Colors .black54),
216+ child: RTCVideoView (_localRenderer,
217+ mirror: true ,
218+ objectFit:
219+ RTCVideoViewObjectFit .RTCVideoViewObjectFitCover ),
220+ ),
221+ )
166222 ]);
167223 },
168224 ),
169- floatingActionButton: url != null
170- ? FloatingActionButton (
171- onPressed: _connecting ? _disconnect : _connect,
172- tooltip: _connecting ? 'Hangup' : 'Call' ,
173- child: Icon (_connecting ? Icons .call_end : Icons .phone),
174- )
175- : Container (),
225+ floatingActionButton: FloatingActionButton (
226+ onPressed: _connecting ? _disconnect : _connect,
227+ tooltip: _connecting ? 'Hangup' : 'Call' ,
228+ child: Icon (_connecting ? Icons .call_end : Icons .phone),
229+ ),
176230 );
177231 }
178- 179- void _selectAudioOutput (String deviceId) {
180- _localRenderer.audioOutput (deviceId);
181- }
182232}
0 commit comments