190 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| part of '../../../main.dart';
 | |
| 
 | |
| class CameraStreamView extends StatefulWidget {
 | |
| 
 | |
|   final bool withControls;
 | |
| 
 | |
|   CameraStreamView({Key key, this.withControls: true}) : super(key: key);
 | |
| 
 | |
|   @override
 | |
|   _CameraStreamViewState createState() => _CameraStreamViewState();
 | |
| }
 | |
| 
 | |
| class _CameraStreamViewState extends State<CameraStreamView> {
 | |
| 
 | |
|   CameraEntity _entity;
 | |
|   String _streamUrl = "";
 | |
|   bool _isLoaded = false;
 | |
|   double _aspectRatio = 1.33;
 | |
|   String _webViewHtml;
 | |
|   String _jsMessageChannelName = 'unknown';
 | |
|   Completer _loading;
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|   }
 | |
| 
 | |
|   Future _loadResources() {
 | |
|     if (_loading != null && !_loading.isCompleted) {
 | |
|       Logger.d("[Camera Player] Resources loading is not finished yet");
 | |
|       return _loading.future;  
 | |
|     }
 | |
|     Logger.d("[Camera Player] Loading resources");
 | |
|     _loading = Completer();
 | |
|     _entity = EntityModel
 | |
|           .of(context)
 | |
|           .entityWrapper
 | |
|           .entity;
 | |
|     if (_entity.supportStream && HomeAssistant().isComponentEnabled('stream')) {
 | |
|       HomeAssistant().getCameraStream(_entity.entityId)
 | |
|         .then((data) {
 | |
|           _jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
 | |
|             rootBundle.loadString('assets/html/cameraLiveView.html').then((file) {
 | |
|               _webViewHtml = Uri.dataFromString(
 | |
|                   file.replaceFirst('{{stream_url}}', '${AppSettings().httpWebHost}${data["url"]}').replaceFirst('{{message_channel}}', _jsMessageChannelName),
 | |
|                   mimeType: 'text/html',
 | |
|                   encoding: Encoding.getByName('utf-8')
 | |
|               ).toString();
 | |
|               _loading.complete();
 | |
|             });
 | |
|         })
 | |
|         .catchError((e) {
 | |
|           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");
 | |
|           }
 | |
|         });
 | |
|     } else {
 | |
|       _loadMJPEG().then((_) {
 | |
|         _loading.complete();
 | |
|       });
 | |
|     }
 | |
|     return _loading.future;
 | |
|   }
 | |
| 
 | |
|   Future _loadMJPEG() async {
 | |
|     _streamUrl = '${AppSettings().httpWebHost}/api/camera_proxy_stream/${_entity
 | |
|         .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();
 | |
|   }
 | |
| 
 | |
|   Widget _buildScreen() {
 | |
|     Widget screenWidget;
 | |
|     if (!_isLoaded) {
 | |
|       screenWidget = Center(
 | |
|         child: EntityPicture(
 | |
|           fit: BoxFit.contain,
 | |
|         )
 | |
|       );
 | |
|     } else {
 | |
|       screenWidget = WebView(
 | |
|         initialUrl: _webViewHtml,
 | |
|         initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
 | |
|         debuggingEnabled: Logger.isInDebugMode,
 | |
|         gestureNavigationEnabled: false,
 | |
|         javascriptMode: JavascriptMode.unrestricted,
 | |
|         javascriptChannels: {
 | |
|           JavascriptChannel(
 | |
|             name: _jsMessageChannelName,
 | |
|             onMessageReceived: ((message) {
 | |
|               Logger.d('[Camera Player] Message from page: $message');
 | |
|               setState((){
 | |
|                 _aspectRatio = double.tryParse(message.message) ?? 1.33;
 | |
|               });
 | |
|             })
 | |
|           )
 | |
|         }
 | |
|       );
 | |
|     }
 | |
|     return AspectRatio(
 | |
|       aspectRatio: _aspectRatio,
 | |
|       child: screenWidget
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildControls() {
 | |
|       return Row(
 | |
|         mainAxisSize: MainAxisSize.max,
 | |
|         crossAxisAlignment: CrossAxisAlignment.center,
 | |
|         mainAxisAlignment: MainAxisAlignment.center,
 | |
|         children: <Widget>[
 | |
|           IconButton(
 | |
|             icon: Icon(Icons.refresh),
 | |
|             iconSize: 40,
 | |
|             color: Theme.of(context).accentColor,
 | |
|             onPressed: _isLoaded ? () {
 | |
|               setState(() {
 | |
|                 _isLoaded = false;  
 | |
|               });
 | |
|             } : null,
 | |
|           ),
 | |
|           Expanded(
 | |
|             child: Container(),
 | |
|           ),
 | |
|           IconButton(
 | |
|             icon: Icon(Icons.fullscreen),
 | |
|             iconSize: 40,
 | |
|             color: Theme.of(context).accentColor,
 | |
|             onPressed: _isLoaded ? () {
 | |
|               Navigator.of(context).pushReplacement(
 | |
|                 MaterialPageRoute(
 | |
|                   builder: (conext) => FullScreenPage(
 | |
|                     child: EntityModel(
 | |
|                       child: CameraStreamView(
 | |
|                         withControls: false
 | |
|                       ),
 | |
|                       handleTap: false,
 | |
|                       entityWrapper: EntityWrapper(
 | |
|                         entity: _entity
 | |
|                       ),
 | |
|                     ),
 | |
|                   ),
 | |
|                   fullscreenDialog: true
 | |
|                 )
 | |
|               ).then((_){
 | |
|                 eventBus.fire(ShowEntityPageEvent(entityId: _entity.entityId));
 | |
|               });
 | |
|             } : null,
 | |
|           )
 | |
|         ],
 | |
|       );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     if (!_isLoaded && (_loading == null || _loading.isCompleted)) {
 | |
|       _loadResources().then((_) => setState((){ _isLoaded = true; }));
 | |
|     }
 | |
|     if (widget.withControls) {
 | |
|       return Card(
 | |
|         child: Column(
 | |
|           mainAxisSize: MainAxisSize.min,
 | |
|           children: <Widget>[
 | |
|             _buildScreen(),
 | |
|             _buildControls()
 | |
|           ],
 | |
|         ),
 | |
|       );
 | |
|     } else {
 | |
|       return _buildScreen();
 | |
|     }
 | |
|     
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     super.dispose();
 | |
|   }
 | |
| } |