2019-09-09 18:50:35 +03:00
|
|
|
part of '../../../main.dart';
|
2019-01-29 21:59:05 +02:00
|
|
|
|
2019-02-20 16:39:57 +02:00
|
|
|
class CameraStreamView extends StatefulWidget {
|
2019-01-29 21:59:05 +02:00
|
|
|
|
2020-02-21 17:36:03 +02:00
|
|
|
final bool withControls;
|
|
|
|
|
2020-03-14 19:56:07 +02:00
|
|
|
CameraStreamView({Key key, this.withControls: true}) : super(key: key);
|
2019-01-29 21:59:05 +02:00
|
|
|
|
|
|
|
@override
|
2019-02-20 16:39:57 +02:00
|
|
|
_CameraStreamViewState createState() => _CameraStreamViewState();
|
2019-01-29 21:59:05 +02:00
|
|
|
}
|
|
|
|
|
2019-02-20 16:39:57 +02:00
|
|
|
class _CameraStreamViewState extends State<CameraStreamView> {
|
2019-01-29 21:59:05 +02:00
|
|
|
|
2020-02-20 16:33:03 +02:00
|
|
|
CameraEntity _entity;
|
2020-02-21 17:36:03 +02:00
|
|
|
String _streamUrl = "";
|
|
|
|
bool _isLoaded = false;
|
|
|
|
double _aspectRatio = 1.33;
|
|
|
|
String _webViewHtml;
|
|
|
|
String _jsMessageChannelName = 'unknown';
|
|
|
|
Completer _loading;
|
2020-02-20 16:33:03 +02:00
|
|
|
|
2019-01-29 21:59:05 +02:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2019-01-31 01:04:13 +02:00
|
|
|
}
|
|
|
|
|
2020-02-21 17:36:03 +02:00
|
|
|
Future _loadResources() {
|
|
|
|
if (_loading != null && !_loading.isCompleted) {
|
|
|
|
Logger.d("[Camera Player] Resources loading is not finished yet");
|
|
|
|
return _loading.future;
|
|
|
|
}
|
2020-02-21 13:23:39 +02:00
|
|
|
Logger.d("[Camera Player] Loading resources");
|
2020-02-21 17:36:03 +02:00
|
|
|
_loading = Completer();
|
2020-03-14 19:56:07 +02:00
|
|
|
_entity = EntityModel
|
2020-02-21 17:36:03 +02:00
|
|
|
.of(context)
|
|
|
|
.entityWrapper
|
|
|
|
.entity;
|
2020-04-15 17:03:31 +03:00
|
|
|
if (_entity.supportStream && HomeAssistant().isComponentEnabled('stream')) {
|
2020-02-21 13:23:39 +02:00
|
|
|
HomeAssistant().getCameraStream(_entity.entityId)
|
|
|
|
.then((data) {
|
2020-04-08 19:48:13 +03:00
|
|
|
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
|
|
|
rootBundle.loadString('assets/html/cameraLiveView.html').then((file) {
|
|
|
|
_webViewHtml = Uri.dataFromString(
|
2020-05-13 15:46:25 +03:00
|
|
|
file.replaceFirst('{{stream_url}}', '${AppSettings().httpWebHost}${data["url"]}').replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
2020-04-08 19:48:13 +03:00
|
|
|
mimeType: 'text/html',
|
|
|
|
encoding: Encoding.getByName('utf-8')
|
|
|
|
).toString();
|
|
|
|
_loading.complete();
|
|
|
|
});
|
2020-02-21 13:23:39 +02:00
|
|
|
})
|
2020-02-21 17:36:03 +02:00
|
|
|
.catchError((e) {
|
2020-04-11 16:13:20 +03:00
|
|
|
if (e == 'start_stream_failed') {
|
|
|
|
Logger.e("[Camera Player] Home Assistant failed starting stream. Forcing MJPEG: $e");
|
|
|
|
_loadMJPEG().then((_) {
|
|
|
|
_loading.complete();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
_loading.completeError(e);
|
|
|
|
Logger.e("[Camera Player] Error loading stream: $e");
|
|
|
|
}
|
2020-02-21 17:36:03 +02:00
|
|
|
});
|
2020-02-21 13:23:39 +02:00
|
|
|
} else {
|
2020-04-11 16:13:20 +03:00
|
|
|
_loadMJPEG().then((_) {
|
2020-02-21 17:36:03 +02:00
|
|
|
_loading.complete();
|
2020-02-21 13:23:39 +02:00
|
|
|
});
|
|
|
|
}
|
2020-02-21 17:36:03 +02:00
|
|
|
return _loading.future;
|
2020-02-20 16:33:03 +02:00
|
|
|
}
|
2019-01-29 21:59:05 +02:00
|
|
|
|
2020-04-11 16:13:20 +03:00
|
|
|
Future _loadMJPEG() async {
|
2020-05-13 15:46:25 +03:00
|
|
|
_streamUrl = '${AppSettings().httpWebHost}/api/camera_proxy_stream/${_entity
|
2020-04-11 16:13:20 +03:00
|
|
|
.entityId}?token=${_entity.attributes['access_token']}';
|
|
|
|
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
|
|
|
var file = await rootBundle.loadString('assets/html/cameraView.html');
|
|
|
|
_webViewHtml = Uri.dataFromString(
|
|
|
|
file.replaceFirst('{{stream_url}}', _streamUrl).replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
|
|
|
mimeType: 'text/html',
|
|
|
|
encoding: Encoding.getByName('utf-8')
|
|
|
|
).toString();
|
|
|
|
}
|
|
|
|
|
2020-02-21 17:36:03 +02:00
|
|
|
Widget _buildScreen() {
|
|
|
|
Widget screenWidget;
|
|
|
|
if (!_isLoaded) {
|
|
|
|
screenWidget = Center(
|
2020-03-14 20:23:39 +02:00
|
|
|
child: EntityPicture(
|
|
|
|
fit: BoxFit.contain,
|
|
|
|
)
|
2020-02-21 17:36:03 +02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
screenWidget = WebView(
|
|
|
|
initialUrl: _webViewHtml,
|
|
|
|
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
|
|
|
|
debuggingEnabled: Logger.isInDebugMode,
|
|
|
|
gestureNavigationEnabled: false,
|
|
|
|
javascriptMode: JavascriptMode.unrestricted,
|
|
|
|
javascriptChannels: {
|
|
|
|
JavascriptChannel(
|
|
|
|
name: _jsMessageChannelName,
|
|
|
|
onMessageReceived: ((message) {
|
2020-04-08 19:48:13 +03:00
|
|
|
Logger.d('[Camera Player] Message from page: $message');
|
2020-02-21 17:36:03 +02:00
|
|
|
setState((){
|
|
|
|
_aspectRatio = double.tryParse(message.message) ?? 1.33;
|
|
|
|
});
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return AspectRatio(
|
|
|
|
aspectRatio: _aspectRatio,
|
|
|
|
child: screenWidget
|
2019-06-21 21:01:53 +03:00
|
|
|
);
|
2019-02-20 13:57:25 +02:00
|
|
|
}
|
|
|
|
|
2020-02-21 17:36:03 +02:00
|
|
|
Widget _buildControls() {
|
|
|
|
return Row(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: <Widget>[
|
|
|
|
IconButton(
|
|
|
|
icon: Icon(Icons.refresh),
|
|
|
|
iconSize: 40,
|
2020-04-07 00:39:16 +03:00
|
|
|
color: Theme.of(context).accentColor,
|
2020-02-21 17:36:03 +02:00
|
|
|
onPressed: _isLoaded ? () {
|
|
|
|
setState(() {
|
|
|
|
_isLoaded = false;
|
|
|
|
});
|
|
|
|
} : null,
|
|
|
|
),
|
|
|
|
Expanded(
|
2020-04-08 19:48:13 +03:00
|
|
|
child: Container(),
|
2020-02-21 17:36:03 +02:00
|
|
|
),
|
|
|
|
IconButton(
|
|
|
|
icon: Icon(Icons.fullscreen),
|
|
|
|
iconSize: 40,
|
2020-04-07 00:39:16 +03:00
|
|
|
color: Theme.of(context).accentColor,
|
2020-02-21 17:36:03 +02:00
|
|
|
onPressed: _isLoaded ? () {
|
2020-05-01 21:50:50 +03:00
|
|
|
Navigator.of(context).pushReplacement(
|
2020-03-12 14:22:38 +02:00
|
|
|
MaterialPageRoute(
|
|
|
|
builder: (conext) => FullScreenPage(
|
2020-03-14 19:56:07 +02:00
|
|
|
child: EntityModel(
|
|
|
|
child: CameraStreamView(
|
|
|
|
withControls: false
|
|
|
|
),
|
|
|
|
handleTap: false,
|
|
|
|
entityWrapper: EntityWrapper(
|
|
|
|
entity: _entity
|
|
|
|
),
|
2020-03-12 14:22:38 +02:00
|
|
|
),
|
|
|
|
),
|
|
|
|
fullscreenDialog: true
|
|
|
|
)
|
2020-05-01 21:50:50 +03:00
|
|
|
).then((_){
|
|
|
|
eventBus.fire(ShowEntityPageEvent(entityId: _entity.entityId));
|
2020-03-12 14:22:38 +02:00
|
|
|
});
|
2020-02-21 17:36:03 +02:00
|
|
|
} : null,
|
|
|
|
)
|
|
|
|
],
|
2020-02-20 16:33:03 +02:00
|
|
|
);
|
2020-02-21 17:36:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
if (!_isLoaded && (_loading == null || _loading.isCompleted)) {
|
|
|
|
_loadResources().then((_) => setState((){ _isLoaded = true; }));
|
|
|
|
}
|
2020-03-12 14:22:38 +02:00
|
|
|
if (widget.withControls) {
|
|
|
|
return Card(
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
|
|
|
_buildScreen(),
|
|
|
|
_buildControls()
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return _buildScreen();
|
|
|
|
}
|
|
|
|
|
2019-01-29 21:59:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
}
|