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;
|
||||
String _streamUrl = "";
|
||||
VideoPlayerController _videoPlayerController;
|
||||
Timer _monitorTimer;
|
||||
bool _isLoaded = false;
|
||||
double _aspectRatio = 1.33;
|
||||
String _webViewHtml;
|
||||
@ -41,11 +39,15 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
if (_entity.supportStream) {
|
||||
HomeAssistant().getCameraStream(_entity.entityId)
|
||||
.then((data) {
|
||||
if (_videoPlayerController != null) {
|
||||
_videoPlayerController.dispose().then((_) => createPlayer(data));
|
||||
} else {
|
||||
createPlayer(data);
|
||||
}
|
||||
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
||||
rootBundle.loadString('assets/html/cameraLiveView.html').then((file) {
|
||||
_webViewHtml = Uri.dataFromString(
|
||||
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) {
|
||||
_loading.completeError(e);
|
||||
@ -67,40 +69,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
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 screenWidget;
|
||||
if (!_isLoaded) {
|
||||
@ -109,16 +77,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
fit: BoxFit.contain,
|
||||
)
|
||||
);
|
||||
} else if (_entity.supportStream) {
|
||||
if (_videoPlayerController.value.initialized) {
|
||||
screenWidget = VideoPlayer(_videoPlayerController);
|
||||
} else {
|
||||
screenWidget = Center(
|
||||
child: EntityPicture(
|
||||
fit: BoxFit.contain,
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
screenWidget = WebView(
|
||||
initialUrl: _webViewHtml,
|
||||
@ -130,6 +88,7 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
JavascriptChannel(
|
||||
name: _jsMessageChannelName,
|
||||
onMessageReceived: ((message) {
|
||||
Logger.d('[Camera Player] Message from page: $message');
|
||||
setState((){
|
||||
_aspectRatio = double.tryParse(message.message) ?? 1.33;
|
||||
});
|
||||
@ -145,28 +104,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
}
|
||||
|
||||
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(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@ -183,14 +120,14 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
} : null,
|
||||
),
|
||||
Expanded(
|
||||
child: playControl,
|
||||
child: Container(),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.fullscreen),
|
||||
iconSize: 40,
|
||||
color: Theme.of(context).accentColor,
|
||||
onPressed: _isLoaded ? () {
|
||||
_videoPlayerController?.pause();
|
||||
//_videoPlayerController?.pause();
|
||||
eventBus.fire(ShowEntityPageEvent());
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
@ -239,8 +176,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_monitorTimer?.cancel();
|
||||
_videoPlayerController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -33,7 +33,6 @@ import 'package:battery/battery.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview;
|
||||
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_gauges/gauges.dart';
|
||||
|
||||
|
@ -32,7 +32,6 @@ dependencies:
|
||||
workmanager: ^0.2.2
|
||||
battery: ^0.3.1+7
|
||||
firebase_crashlytics: ^0.1.3+3
|
||||
video_player: ^0.10.7
|
||||
syncfusion_flutter_core: ^18.1.43
|
||||
syncfusion_flutter_gauges: ^18.1.43
|
||||
|
||||
@ -55,6 +54,7 @@ flutter:
|
||||
- images/hassio-192x192.png
|
||||
- assets/js/externalAuth.js
|
||||
- assets/html/cameraView.html
|
||||
- assets/html/cameraLiveView.html
|
||||
|
||||
fonts:
|
||||
- family: "Material Design Icons"
|
||||
|
Reference in New Issue
Block a user