Skip to content

Commit d4ca437

Browse files
committed
chore: Add subscribe sample.
1 parent 89bc1ef commit d4ca437

File tree

15 files changed

+622
-557
lines changed

15 files changed

+622
-557
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
# Changelog
22

33
--------------------------------------------------------------------------------
4+
2022-03-17: Version 0.1.0
5+
6+
* Initial release
7+
8+
2020-03-17: Version 0.2.0
9+
10+
* First working version

example/lib/main.dart

Lines changed: 61 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,80 @@
1+
import 'dart:core';
2+
13
import 'package:flutter/material.dart';
4+
import 'package:flutter_webrtc/flutter_webrtc.dart';
5+
6+
import 'route_item.dart';
7+
import 'pages/publish_sample.dart';
8+
import 'pages/subscribe_sample.dart';
9+
import 'pages/qr_scanner.dart';
210

311
void main() {
12+
if (!WebRTC.platformIsWeb) {
13+
WidgetsFlutterBinding.ensureInitialized();
14+
}
415
runApp(MyApp());
516
}
617

7-
class MyApp extends StatelessWidget {
8-
// This widget is the root of your application.
18+
class MyApp extends StatefulWidget {
919
@override
10-
Widget build(BuildContext context) {
11-
return MaterialApp(
12-
title: 'Flutter Demo',
13-
theme: ThemeData(
14-
// This is the theme of your application.
15-
//
16-
// Try running your application with "flutter run". You'll see the
17-
// application has a blue toolbar. Then, without quitting the app, try
18-
// changing the primarySwatch below to Colors.green and then invoke
19-
// "hot reload" (press "r" in the console where you ran "flutter run",
20-
// or simply save your changes to "hot reload" in a Flutter IDE).
21-
// Notice that the counter didn't reset back to zero; the application
22-
// is not restarted.
23-
primarySwatch: Colors.blue,
24-
),
25-
home: MyHomePage(title: 'Flutter Demo Home Page'),
26-
);
27-
}
20+
_MyAppState createState() => _MyAppState();
2821
}
2922

30-
class MyHomePage extends StatefulWidget {
31-
MyHomePage({Key? key, required this.title}) : super(key: key);
32-
33-
// This widget is the home page of your application. It is stateful, meaning
34-
// that it has a State object (defined below) that contains fields that affect
35-
// how it looks.
36-
37-
// This class is the configuration for the state. It holds the values (in this
38-
// case the title) provided by the parent (in this case the App widget) and
39-
// used by the build method of the State. Fields in a Widget subclass are
40-
// always marked "final".
41-
42-
final String title;
23+
class _MyAppState extends State<MyApp> {
24+
late List<RouteItem> items;
4325

4426
@override
45-
_MyHomePageState createState() => _MyHomePageState();
46-
}
47-
48-
class _MyHomePageState extends State<MyHomePage> {
49-
int _counter = 0;
27+
void initState() {
28+
super.initState();
29+
_initItems();
30+
}
5031

51-
void _incrementCounter() {
52-
setState(() {
53-
// This call to setState tells the Flutter framework that something has
54-
// changed in this State, which causes it to rerun the build method below
55-
// so that the display can reflect the updated values. If we changed
56-
// _counter without calling setState(), then the build method would not be
57-
// called again, and so nothing would appear to happen.
58-
_counter++;
59-
});
32+
ListBody _buildRow(context, item) {
33+
return ListBody(children: <Widget>[
34+
ListTile(
35+
title: Text(item.title),
36+
onTap: () => item.push(context),
37+
trailing: Icon(Icons.arrow_right),
38+
),
39+
Divider()
40+
]);
6041
}
6142

6243
@override
6344
Widget build(BuildContext context) {
64-
// This method is rerun every time setState is called, for instance as done
65-
// by the _incrementCounter method above.
66-
//
67-
// The Flutter framework has been optimized to make rerunning build methods
68-
// fast, so that you can just rebuild anything that needs updating rather
69-
// than having to individually change instances of widgets.
70-
return Scaffold(
71-
appBar: AppBar(
72-
// Here we take the value from the MyHomePage object that was created by
73-
// the App.build method, and use it to set our appbar title.
74-
title: Text(widget.title),
75-
),
76-
body: Center(
77-
// Center is a layout widget. It takes a single child and positions it
78-
// in the middle of the parent.
79-
child: Column(
80-
// Column is also a layout widget. It takes a list of children and
81-
// arranges them vertically. By default, it sizes itself to fit its
82-
// children horizontally, and tries to be as tall as its parent.
83-
//
84-
// Invoke "debug painting" (press "p" in the console, choose the
85-
// "Toggle Debug Paint" action from the Flutter Inspector in Android
86-
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
87-
// to see the wireframe for each widget.
88-
//
89-
// Column has various properties to control how it sizes itself and
90-
// how it positions its children. Here we use mainAxisAlignment to
91-
// center the children vertically; the main axis here is the vertical
92-
// axis because Columns are vertical (the cross axis would be
93-
// horizontal).
94-
mainAxisAlignment: MainAxisAlignment.center,
95-
children: <Widget>[
96-
Text(
97-
'You have pushed the button this many times:',
98-
),
99-
Text(
100-
'$_counter',
101-
style: Theme.of(context).textTheme.headline4,
102-
),
103-
],
104-
),
105-
),
106-
floatingActionButton: FloatingActionButton(
107-
onPressed: _incrementCounter,
108-
tooltip: 'Increment',
109-
child: Icon(Icons.add),
110-
), // This trailing comma makes auto-formatting nicer for build methods.
45+
return MaterialApp(
46+
home: Scaffold(
47+
appBar: AppBar(
48+
title: Text('Flutter WHIP example'),
49+
),
50+
body: ListView.builder(
51+
shrinkWrap: true,
52+
padding: const EdgeInsets.all(0.0),
53+
itemCount: items.length,
54+
itemBuilder: (context, i) {
55+
return _buildRow(context, items[i]);
56+
})),
11157
);
11258
}
59+
60+
void _initItems() {
61+
items = <RouteItem>[
62+
RouteItem(
63+
title: 'Publish Sample',
64+
push: (BuildContext context) {
65+
Navigator.push(
66+
context,
67+
MaterialPageRoute(
68+
builder: (BuildContext context) => WhipPublishSample()));
69+
}),
70+
RouteItem(
71+
title: 'Subscribe Sample',
72+
push: (BuildContext context) {
73+
Navigator.push(
74+
context,
75+
MaterialPageRoute(
76+
builder: (BuildContext context) => WhipSubscribeSample()));
77+
}),
78+
];
79+
}
11380
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import 'dart:core';
2+
3+
import 'package:flutter/foundation.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_webrtc/flutter_webrtc.dart';
6+
import 'package:flutter_whip/flutter_whip.dart';
7+
8+
import 'qr_scanner.dart';
9+
10+
class WhipPublishSample extends StatefulWidget {
11+
static String tag = 'whip_publish_sample';
12+
13+
@override
14+
_WhipPublishSampleState createState() => _WhipPublishSampleState();
15+
}
16+
17+
class _WhipPublishSampleState extends State<WhipPublishSample> {
18+
MediaStream? _localStream;
19+
final _localRenderer = RTCVideoRenderer();
20+
List<MediaDeviceInfo>? _mediaDevicesList;
21+
bool _connecting = false;
22+
late WHIP _whip;
23+
String? url;
24+
25+
@override
26+
void initState() {
27+
super.initState();
28+
initRenderers();
29+
}
30+
31+
@override
32+
void deactivate() {
33+
super.deactivate();
34+
_localRenderer.dispose();
35+
}
36+
37+
void initRenderers() async {
38+
await _localRenderer.initialize();
39+
}
40+
41+
// Platform messages are asynchronous, so we initialize in an async method.
42+
void _connect() async {
43+
if (url == null) {
44+
return;
45+
}
46+
47+
_whip = WHIP(url: url!);
48+
final mediaConstraints = <String, dynamic>{
49+
'audio': true,
50+
'video': {
51+
'mandatory': {
52+
'minWidth': '640',
53+
'minHeight': '480',
54+
'minFrameRate': '30',
55+
},
56+
'facingMode': 'user',
57+
'optional': [],
58+
}
59+
};
60+
61+
try {
62+
var stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
63+
_mediaDevicesList = await navigator.mediaDevices.enumerateDevices();
64+
_localStream = stream;
65+
_localRenderer.srcObject = _localStream;
66+
await _whip.initlize(mode: WhipMode.kSend, stream: _localStream);
67+
await _whip.connect();
68+
} catch (e) {
69+
print(e.toString());
70+
return;
71+
}
72+
if (!mounted) return;
73+
74+
setState(() {
75+
_connecting = true;
76+
});
77+
}
78+
79+
void _disconnect() async {
80+
try {
81+
if (kIsWeb) {
82+
_localStream?.getTracks().forEach((track) => track.stop());
83+
}
84+
await _localStream?.dispose();
85+
_localRenderer.srcObject = null;
86+
_whip.close();
87+
setState(() {
88+
_connecting = false;
89+
});
90+
} catch (e) {
91+
print(e.toString());
92+
}
93+
}
94+
95+
void _toggleCamera() async {
96+
if (_localStream == null) throw Exception('Stream is not initialized');
97+
final videoTrack = _localStream!
98+
.getVideoTracks()
99+
.firstWhere((track) => track.kind == 'video');
100+
await Helper.switchCamera(videoTrack);
101+
}
102+
103+
@override
104+
Widget build(BuildContext context) {
105+
return Scaffold(
106+
appBar: AppBar(title: Text('WHIP Publish Sample'), actions: <Widget>[
107+
if (!_connecting)
108+
IconButton(
109+
icon: Icon(Icons.qr_code_scanner_sharp),
110+
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;
118+
});
119+
});
120+
},
121+
),
122+
if (_connecting)
123+
IconButton(
124+
icon: Icon(Icons.switch_video),
125+
onPressed: _toggleCamera,
126+
),
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+
),
144+
]),
145+
body: OrientationBuilder(
146+
builder: (context, orientation) {
147+
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),
164+
),
165+
)
166+
]);
167+
},
168+
),
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(),
176+
);
177+
}
178+
179+
void _selectAudioOutput(String deviceId) {
180+
_localRenderer.audioOutput(deviceId);
181+
}
182+
}

0 commit comments

Comments
 (0)