Replace VideoPlayer with web player
This commit is contained in:
		
							
								
								
									
										61
									
								
								assets/html/cameraLiveView.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								assets/html/cameraLiveView.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | <html> | ||||||
|  | <head> | ||||||
|  | <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> | ||||||
|  | <style> | ||||||
|  |     body { | ||||||
|  |         padding: 0; | ||||||
|  |         margin: 0; | ||||||
|  |         widows: 100%; | ||||||
|  |         height: 100%; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     video { | ||||||
|  |         width: 100%; | ||||||
|  |     } | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |     var messageChannel = '{{message_channel}}'; | ||||||
|  | </script> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | <video id="screen" width="100%" controls></video> | ||||||
|  | <script> | ||||||
|  | if (Hls.isSupported()) { | ||||||
|  |     var video = document.getElementById('screen'); | ||||||
|  |     var hls = new Hls(); | ||||||
|  |     hls.on(Hls.Events.ERROR, function (event, data) { | ||||||
|  |             if (data.fatal) { | ||||||
|  |             switch(data.type) { | ||||||
|  |             case Hls.ErrorTypes.NETWORK_ERROR: | ||||||
|  |             // try to recover network error | ||||||
|  |                 console.log("fatal network error encountered, try to recover"); | ||||||
|  |                 hls.startLoad(); | ||||||
|  |                 break; | ||||||
|  |             case Hls.ErrorTypes.MEDIA_ERROR: | ||||||
|  |                 console.log("fatal media error encountered, try to recover"); | ||||||
|  |                 hls.recoverMediaError(); | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |             // cannot recover | ||||||
|  |                 hls.destroy(); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     // bind them together | ||||||
|  |     hls.attachMedia(video); | ||||||
|  |     hls.on(Hls.Events.MEDIA_ATTACHED, function () { | ||||||
|  |         console.log("video and hls.js are now bound together !"); | ||||||
|  |         hls.loadSource("{{stream_url}}"); | ||||||
|  |         hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) { | ||||||
|  |             console.log("manifest loaded, found " + data.levels.length + " quality level"); | ||||||
|  |             video.play(); | ||||||
|  |             video.onloadedmetadata = function() { | ||||||
|  |                 window[messageChannel].postMessage(document.body.clientWidth / video.offsetHeight); | ||||||
|  |             }; | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
| @@ -14,8 +14,6 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|  |  | ||||||
|   CameraEntity _entity; |   CameraEntity _entity; | ||||||
|   String _streamUrl = ""; |   String _streamUrl = ""; | ||||||
|   VideoPlayerController _videoPlayerController; |  | ||||||
|   Timer _monitorTimer; |  | ||||||
|   bool _isLoaded = false; |   bool _isLoaded = false; | ||||||
|   double _aspectRatio = 1.33; |   double _aspectRatio = 1.33; | ||||||
|   String _webViewHtml; |   String _webViewHtml; | ||||||
| @@ -41,11 +39,15 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|     if (_entity.supportStream) { |     if (_entity.supportStream) { | ||||||
|       HomeAssistant().getCameraStream(_entity.entityId) |       HomeAssistant().getCameraStream(_entity.entityId) | ||||||
|         .then((data) { |         .then((data) { | ||||||
|           if (_videoPlayerController != null) { |           _jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}'; | ||||||
|             _videoPlayerController.dispose().then((_) => createPlayer(data)); |             rootBundle.loadString('assets/html/cameraLiveView.html').then((file) { | ||||||
|           } else { |               _webViewHtml = Uri.dataFromString( | ||||||
|             createPlayer(data); |                   file.replaceFirst('{{stream_url}}', '${ConnectionManager().httpWebHost}${data["url"]}').replaceFirst('{{message_channel}}', _jsMessageChannelName), | ||||||
|           } |                   mimeType: 'text/html', | ||||||
|  |                   encoding: Encoding.getByName('utf-8') | ||||||
|  |               ).toString(); | ||||||
|  |               _loading.complete(); | ||||||
|  |             }); | ||||||
|         }) |         }) | ||||||
|         .catchError((e) { |         .catchError((e) { | ||||||
|           _loading.completeError(e); |           _loading.completeError(e); | ||||||
| @@ -67,40 +69,6 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|     return _loading.future; |     return _loading.future; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void createPlayer(data) { |  | ||||||
|     _videoPlayerController = VideoPlayerController.network("${ConnectionManager().httpWebHost}${data["url"]}"); |  | ||||||
|     _videoPlayerController.initialize().then((_) { |  | ||||||
|       setState((){ |  | ||||||
|         _aspectRatio = _videoPlayerController.value.aspectRatio; |  | ||||||
|       }); |  | ||||||
|       _loading.complete(); |  | ||||||
|       autoPlay(); |  | ||||||
|       startMonitor(); |  | ||||||
|     }).catchError((e) { |  | ||||||
|       _loading.completeError(e); |  | ||||||
|       Logger.e("[Camera Player] Error player init. Retrying"); |  | ||||||
|       _loadResources(); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void autoPlay() { |  | ||||||
|     if (!_videoPlayerController.value.isPlaying) { |  | ||||||
|       _videoPlayerController.play(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void startMonitor() { |  | ||||||
|     _monitorTimer?.cancel(); |  | ||||||
|     _monitorTimer = Timer.periodic(Duration(milliseconds: 500), (timer) { |  | ||||||
|       if (_videoPlayerController.value.hasError) { |  | ||||||
|         timer.cancel(); |  | ||||||
|         setState(() { |  | ||||||
|           _isLoaded = false;   |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Widget _buildScreen() { |   Widget _buildScreen() { | ||||||
|     Widget screenWidget; |     Widget screenWidget; | ||||||
|     if (!_isLoaded) { |     if (!_isLoaded) { | ||||||
| @@ -109,16 +77,6 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|           fit: BoxFit.contain, |           fit: BoxFit.contain, | ||||||
|         ) |         ) | ||||||
|       ); |       ); | ||||||
|     } else if (_entity.supportStream) { |  | ||||||
|       if (_videoPlayerController.value.initialized) { |  | ||||||
|         screenWidget = VideoPlayer(_videoPlayerController); |  | ||||||
|       } else { |  | ||||||
|         screenWidget = Center( |  | ||||||
|           child: EntityPicture( |  | ||||||
|             fit: BoxFit.contain, |  | ||||||
|           ) |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } else { |     } else { | ||||||
|       screenWidget = WebView( |       screenWidget = WebView( | ||||||
|         initialUrl: _webViewHtml, |         initialUrl: _webViewHtml, | ||||||
| @@ -130,6 +88,7 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|           JavascriptChannel( |           JavascriptChannel( | ||||||
|             name: _jsMessageChannelName, |             name: _jsMessageChannelName, | ||||||
|             onMessageReceived: ((message) { |             onMessageReceived: ((message) { | ||||||
|  |               Logger.d('[Camera Player] Message from page: $message'); | ||||||
|               setState((){ |               setState((){ | ||||||
|                 _aspectRatio = double.tryParse(message.message) ?? 1.33; |                 _aspectRatio = double.tryParse(message.message) ?? 1.33; | ||||||
|               }); |               }); | ||||||
| @@ -145,28 +104,6 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Widget _buildControls() { |   Widget _buildControls() { | ||||||
|     Widget playControl; |  | ||||||
|       if (_entity.supportStream) { |  | ||||||
|         playControl = Center( |  | ||||||
|           child: IconButton( |  | ||||||
|             icon: Icon((_videoPlayerController != null && _videoPlayerController.value.isPlaying) ? Icons.pause_circle_outline : Icons.play_circle_outline), |  | ||||||
|             iconSize: 60, |  | ||||||
|             color: Theme.of(context).accentColor, |  | ||||||
|             onPressed: (_videoPlayerController == null || _videoPlayerController.value.hasError || !_isLoaded) ? null : |  | ||||||
|               () { |  | ||||||
|                 setState(() { |  | ||||||
|                   if (_videoPlayerController != null && _videoPlayerController.value.isPlaying) { |  | ||||||
|                     _videoPlayerController.pause(); |  | ||||||
|                   } else { |  | ||||||
|                     _videoPlayerController.play(); |  | ||||||
|                   } |  | ||||||
|                 }); |  | ||||||
|               }, |  | ||||||
|           ), |  | ||||||
|         ); |  | ||||||
|       } else { |  | ||||||
|         playControl = Container(); |  | ||||||
|       } |  | ||||||
|       return Row( |       return Row( | ||||||
|         mainAxisSize: MainAxisSize.max, |         mainAxisSize: MainAxisSize.max, | ||||||
|         crossAxisAlignment: CrossAxisAlignment.center, |         crossAxisAlignment: CrossAxisAlignment.center, | ||||||
| @@ -183,14 +120,14 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|             } : null, |             } : null, | ||||||
|           ), |           ), | ||||||
|           Expanded( |           Expanded( | ||||||
|             child: playControl, |             child: Container(), | ||||||
|           ), |           ), | ||||||
|           IconButton( |           IconButton( | ||||||
|             icon: Icon(Icons.fullscreen), |             icon: Icon(Icons.fullscreen), | ||||||
|             iconSize: 40, |             iconSize: 40, | ||||||
|             color: Theme.of(context).accentColor, |             color: Theme.of(context).accentColor, | ||||||
|             onPressed: _isLoaded ? () { |             onPressed: _isLoaded ? () { | ||||||
|               _videoPlayerController?.pause(); |               //_videoPlayerController?.pause(); | ||||||
|               eventBus.fire(ShowEntityPageEvent()); |               eventBus.fire(ShowEntityPageEvent()); | ||||||
|               Navigator.of(context).push( |               Navigator.of(context).push( | ||||||
|                 MaterialPageRoute( |                 MaterialPageRoute( | ||||||
| @@ -239,8 +176,6 @@ class _CameraStreamViewState extends State<CameraStreamView> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void dispose() { |   void dispose() { | ||||||
|     _monitorTimer?.cancel(); |  | ||||||
|     _videoPlayerController?.dispose(); |  | ||||||
|     super.dispose(); |     super.dispose(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -33,7 +33,6 @@ import 'package:battery/battery.dart'; | |||||||
| import 'package:firebase_crashlytics/firebase_crashlytics.dart'; | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; | ||||||
| import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview; | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview; | ||||||
| import 'package:webview_flutter/webview_flutter.dart'; | import 'package:webview_flutter/webview_flutter.dart'; | ||||||
| import 'package:video_player/video_player.dart'; |  | ||||||
| import 'package:syncfusion_flutter_core/core.dart'; | import 'package:syncfusion_flutter_core/core.dart'; | ||||||
| import 'package:syncfusion_flutter_gauges/gauges.dart'; | import 'package:syncfusion_flutter_gauges/gauges.dart'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -32,7 +32,6 @@ dependencies: | |||||||
|   workmanager: ^0.2.2 |   workmanager: ^0.2.2 | ||||||
|   battery: ^0.3.1+7 |   battery: ^0.3.1+7 | ||||||
|   firebase_crashlytics: ^0.1.3+3 |   firebase_crashlytics: ^0.1.3+3 | ||||||
|   video_player: ^0.10.7 |  | ||||||
|   syncfusion_flutter_core: ^18.1.43 |   syncfusion_flutter_core: ^18.1.43 | ||||||
|   syncfusion_flutter_gauges: ^18.1.43 |   syncfusion_flutter_gauges: ^18.1.43 | ||||||
|    |    | ||||||
| @@ -55,6 +54,7 @@ flutter: | |||||||
|     - images/hassio-192x192.png |     - images/hassio-192x192.png | ||||||
|     - assets/js/externalAuth.js |     - assets/js/externalAuth.js | ||||||
|     - assets/html/cameraView.html |     - assets/html/cameraView.html | ||||||
|  |     - assets/html/cameraLiveView.html | ||||||
|  |  | ||||||
|   fonts: |   fonts: | ||||||
|     - family: "Material Design Icons" |     - family: "Material Design Icons" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user