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 = "";
|
|
|
|
VideoPlayerController _videoPlayerController;
|
|
|
|
Timer _monitorTimer;
|
|
|
|
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-02-21 13:23:39 +02:00
|
|
|
if (_entity.supportStream) {
|
|
|
|
HomeAssistant().getCameraStream(_entity.entityId)
|
|
|
|
.then((data) {
|
2020-02-21 17:36:03 +02:00
|
|
|
if (_videoPlayerController != null) {
|
|
|
|
_videoPlayerController.dispose().then((_) => createPlayer(data));
|
2020-02-21 13:23:39 +02:00
|
|
|
} else {
|
|
|
|
createPlayer(data);
|
|
|
|
}
|
|
|
|
})
|
2020-02-21 17:36:03 +02:00
|
|
|
.catchError((e) {
|
|
|
|
_loading.completeError(e);
|
|
|
|
Logger.e("[Camera Player] $e");
|
|
|
|
});
|
2020-02-21 13:23:39 +02:00
|
|
|
} else {
|
2020-02-21 17:36:03 +02:00
|
|
|
_streamUrl = '${ConnectionManager().httpWebHost}/api/camera_proxy_stream/${_entity
|
2020-02-21 13:23:39 +02:00
|
|
|
.entityId}?token=${_entity.attributes['access_token']}';
|
2020-02-21 17:36:03 +02:00
|
|
|
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
2020-02-21 13:23:39 +02:00
|
|
|
rootBundle.loadString('assets/html/cameraView.html').then((file) {
|
2020-02-21 17:36:03 +02:00
|
|
|
_webViewHtml = Uri.dataFromString(
|
|
|
|
file.replaceFirst('{{stream_url}}', _streamUrl).replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
2020-02-21 13:23:39 +02:00
|
|
|
mimeType: 'text/html',
|
|
|
|
encoding: Encoding.getByName('utf-8')
|
|
|
|
).toString();
|
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-02-20 16:33:03 +02:00
|
|
|
void createPlayer(data) {
|
2020-02-21 17:36:03 +02:00
|
|
|
_videoPlayerController = VideoPlayerController.network("${ConnectionManager().httpWebHost}${data["url"]}");
|
|
|
|
_videoPlayerController.initialize().then((_) {
|
2020-02-20 16:33:03 +02:00
|
|
|
setState((){
|
2020-02-21 17:36:03 +02:00
|
|
|
_aspectRatio = _videoPlayerController.value.aspectRatio;
|
2020-02-20 16:33:03 +02:00
|
|
|
});
|
2020-02-21 17:36:03 +02:00
|
|
|
_loading.complete();
|
2020-02-20 16:33:03 +02:00
|
|
|
autoPlay();
|
|
|
|
startMonitor();
|
|
|
|
}).catchError((e) {
|
2020-02-21 17:36:03 +02:00
|
|
|
_loading.completeError(e);
|
2020-02-20 16:33:03 +02:00
|
|
|
Logger.e("[Camera Player] Error player init. Retrying");
|
2020-02-21 17:36:03 +02:00
|
|
|
_loadResources();
|
2020-02-20 16:33:03 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void autoPlay() {
|
2020-02-21 17:36:03 +02:00
|
|
|
if (!_videoPlayerController.value.isPlaying) {
|
|
|
|
_videoPlayerController.play();
|
2020-02-20 16:33:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void startMonitor() {
|
2020-02-21 17:36:03 +02:00
|
|
|
_monitorTimer?.cancel();
|
|
|
|
_monitorTimer = Timer.periodic(Duration(milliseconds: 500), (timer) {
|
|
|
|
if (_videoPlayerController.value.hasError) {
|
|
|
|
timer.cancel();
|
|
|
|
setState(() {
|
|
|
|
_isLoaded = false;
|
|
|
|
});
|
2020-02-20 16:33:03 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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 if (_entity.supportStream) {
|
|
|
|
if (_videoPlayerController.value.initialized) {
|
|
|
|
screenWidget = VideoPlayer(_videoPlayerController);
|
|
|
|
} else {
|
|
|
|
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) {
|
|
|
|
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() {
|
2020-03-12 14:22:38 +02:00
|
|
|
Widget playControl;
|
2020-02-21 17:36:03 +02:00
|
|
|
if (_entity.supportStream) {
|
|
|
|
playControl = Center(
|
|
|
|
child: IconButton(
|
|
|
|
icon: Icon((_videoPlayerController != null && _videoPlayerController.value.isPlaying) ? Icons.pause_circle_outline : Icons.play_circle_outline),
|
|
|
|
iconSize: 60,
|
2020-04-07 00:39:16 +03:00
|
|
|
color: Theme.of(context).accentColor,
|
2020-02-21 17:36:03 +02:00
|
|
|
onPressed: (_videoPlayerController == null || _videoPlayerController.value.hasError || !_isLoaded) ? null :
|
|
|
|
() {
|
|
|
|
setState(() {
|
|
|
|
if (_videoPlayerController != null && _videoPlayerController.value.isPlaying) {
|
|
|
|
_videoPlayerController.pause();
|
|
|
|
} else {
|
|
|
|
_videoPlayerController.play();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
),
|
2020-02-20 16:33:03 +02:00
|
|
|
);
|
|
|
|
} else {
|
2020-02-21 17:36:03 +02:00
|
|
|
playControl = Container();
|
2020-02-20 16:33:03 +02:00
|
|
|
}
|
2020-02-21 17:36:03 +02:00
|
|
|
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(
|
|
|
|
child: playControl,
|
|
|
|
),
|
|
|
|
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-03-12 14:22:38 +02:00
|
|
|
_videoPlayerController?.pause();
|
|
|
|
eventBus.fire(ShowEntityPageEvent());
|
|
|
|
Navigator.of(context).push(
|
|
|
|
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
|
|
|
|
)
|
|
|
|
).then((_) {
|
|
|
|
eventBus.fire(ShowEntityPageEvent(entity: _entity));
|
|
|
|
});
|
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() {
|
2020-02-21 17:36:03 +02:00
|
|
|
_monitorTimer?.cancel();
|
|
|
|
_videoPlayerController?.dispose();
|
2019-01-29 21:59:05 +02:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
}
|