Replace VideoPlayer with web player
This commit is contained in:
parent
08ee3f3d80
commit
e18b9ebe14
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