This repository has been archived on 2023-11-18. You can view files and clone it, but cannot push or open issues or pull requests.
ha_client/lib/entity_widgets/common/camera_stream_view.dart

159 lines
4.6 KiB
Dart
Raw Normal View History

2019-01-29 21:59:05 +02:00
part of '../../main.dart';
class CameraStreamView extends StatefulWidget {
2019-01-29 21:59:05 +02:00
CameraStreamView({Key key}) : super(key: key);
2019-01-29 21:59:05 +02:00
@override
_CameraStreamViewState createState() => _CameraStreamViewState();
2019-01-29 21:59:05 +02:00
}
class _CameraStreamViewState extends State<CameraStreamView> {
2019-01-29 21:59:05 +02:00
@override
void initState() {
super.initState();
2019-01-31 01:04:13 +02:00
}
CameraEntity _entity;
2019-01-31 01:04:13 +02:00
http.Client client;
http.StreamedResponse response;
List<int> binaryImage = [];
2019-02-09 02:12:05 +02:00
String cameraState = "Connecting...";
bool timeToStop = false;
Completer streamCompleter;
bool started = false;
2019-01-31 01:04:13 +02:00
2019-02-20 13:57:25 +02:00
void _connect() async {
started = true;
2019-02-20 13:57:25 +02:00
timeToStop = false;
String streamUrl = '$homeAssistantWebHost/api/camera_proxy_stream/${_entity.entityId}?token=${_entity.attributes['access_token']}';
2019-01-31 01:04:13 +02:00
client = new http.Client(); // create a client to make api calls
http.Request request = new http.Request("GET", Uri.parse(streamUrl)); // create get request
Logger.d("[Sending] ==> ${streamUrl}");
2019-02-09 02:12:05 +02:00
response = await client.send(request);
setState(() {
cameraState = "Starting...";
});
Logger.d("[Received] <== ${response.headers}");
String frameBoundary = response.headers['content-type'].split('boundary=')[1];
final int frameBoundarySize = frameBoundary.length;
2019-01-31 01:04:13 +02:00
List<int> primaryBuffer=[];
int imageSizeStart = 59;
int imageSizeEnd = 0;
2019-01-31 01:04:13 +02:00
int imageStart = 0;
2019-02-09 02:12:05 +02:00
int imageSize = 0;
String strBuffer = "";
String contentType = "";
2019-02-09 02:12:05 +02:00
streamCompleter = Completer();
2019-01-31 01:04:13 +02:00
response.stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
primaryBuffer.addAll(data);
imageStart = 0;
imageSizeEnd = 0;
if (primaryBuffer.length >= imageSizeStart + 10) {
contentType = utf8.decode(
primaryBuffer.sublist(frameBoundarySize+16, imageSizeStart + 10), allowMalformed: true).split("\r\n")[0];
2019-02-20 13:57:25 +02:00
//Logger.d("$contentType");
imageSizeStart = frameBoundarySize + 16 + contentType.length + 18;
for (int i = imageSizeStart; i < primaryBuffer.length - 4; i++) {
2019-02-09 02:12:05 +02:00
strBuffer = utf8.decode(
primaryBuffer.sublist(i, i + 4), allowMalformed: true);
2019-02-09 02:12:05 +02:00
if (strBuffer == "\r\n\r\n") {
imageSizeEnd = i;
imageStart = i + 4;
break;
2019-01-31 01:04:13 +02:00
}
}
if (imageSizeEnd > 0) {
imageSize = int.tryParse(utf8.decode(
primaryBuffer.sublist(imageSizeStart, imageSizeEnd),
allowMalformed: true));
2019-02-20 13:57:25 +02:00
//Logger.d("content-length: $imageSize");
if (imageSize != null &&
primaryBuffer.length >= imageStart + imageSize + 2) {
sink.add(
primaryBuffer.sublist(
imageStart, imageStart + imageSize));
primaryBuffer.removeRange(0, imageStart + imageSize + 2);
}
2019-01-31 01:04:13 +02:00
}
}
2019-02-09 02:12:05 +02:00
if (timeToStop) {
sink?.close();
streamCompleter.complete();
}
},
handleError: (error, stack, sink) {
Logger.e("Error parsing MJPEG stream: $error");
2019-01-31 01:04:13 +02:00
},
handleDone: (sink) {
2019-02-20 13:57:25 +02:00
Logger.d("Camera stream finished. Reconnecting...");
2019-02-09 02:12:05 +02:00
sink?.close();
2019-02-20 13:57:25 +02:00
streamCompleter?.complete();
_reconnect();
2019-01-31 01:04:13 +02:00
},
)
).listen((d) {
2019-02-20 13:57:25 +02:00
if (!timeToStop) {
setState(() {
binaryImage = d;
});
}
});
}
void _reconnect() {
disconnect().then((_){
_connect();
2019-01-31 01:04:13 +02:00
});
2019-01-29 21:59:05 +02:00
}
2019-02-20 13:57:25 +02:00
Future disconnect() {
Completer disconF = Completer();
timeToStop = true;
if (streamCompleter != null && !streamCompleter.isCompleted) {
streamCompleter.future.then((_) {
client?.close();
disconF.complete();
});
} else {
client?.close();
disconF.complete();
}
return disconF.future;
}
2019-01-29 21:59:05 +02:00
@override
Widget build(BuildContext context) {
if (!started) {
_entity = EntityModel
.of(context)
.entityWrapper
.entity;
_connect();
}
if (binaryImage.isEmpty) {
2019-02-09 02:12:05 +02:00
return Column(
children: <Widget>[
Text("$cameraState")
],
);
} else {
return Column(
children: <Widget>[
Image.memory(Uint8List.fromList(binaryImage), gaplessPlayback: true),
],
);
}
2019-01-29 21:59:05 +02:00
}
@override
void dispose() {
2019-02-20 13:57:25 +02:00
disconnect();
2019-01-29 21:59:05 +02:00
super.dispose();
}
}