aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-05-29 23:40:14 +0300
committerEvgeny Zinoviev <me@ch1p.io>2023-05-29 23:40:14 +0300
commit850083225ca0c3df7168d84655e1352b05b153fa (patch)
tree2af42e0bef4133333a6eb047ffe0beaca813a6bd
parentbd8d5040eb95a9f11388be982ab71653f7a4949d (diff)
lws: support h265 camera streams
-rw-r--r--localwebsite/config.php4
-rw-r--r--localwebsite/handlers/MiscHandler.php74
-rw-r--r--localwebsite/htdocs/assets/app.css8
-rw-r--r--localwebsite/htdocs/assets/app.js175
-rw-r--r--localwebsite/templates-web/cams.twig2
5 files changed, 217 insertions, 46 deletions
diff --git a/localwebsite/config.php b/localwebsite/config.php
index b54856d..6115d7e 100644
--- a/localwebsite/config.php
+++ b/localwebsite/config.php
@@ -49,8 +49,8 @@ return [
],
'static' => [
- 'app.css' => 10,
- 'app.js' => 5,
+ 'app.css' => 12,
+ 'app.js' => 7,
'polyfills.js' => 1,
'modem.js' => 2,
'inverter.js' => 2,
diff --git a/localwebsite/handlers/MiscHandler.php b/localwebsite/handlers/MiscHandler.php
index e5f09b0..2cf37e9 100644
--- a/localwebsite/handlers/MiscHandler.php
+++ b/localwebsite/handlers/MiscHandler.php
@@ -67,41 +67,71 @@ class MiscHandler extends RequestHandler
$tab = $high ? 'high' : 'low';
- $hls_opts = [
- 'startPosition' => -1,
-
- // // https://github.com/video-dev/hls.js/issues/3884#issuecomment-842380784
- 'liveSyncDuration' => 2,
- 'liveMaxLatencyDuration' => 3,
- 'maxLiveSyncPlaybackRate' => 2,
- 'liveDurationInfinity' => true,
+ // h264
+ $js_hls_config = [
+ 'opts' => [
+ 'startPosition' => -1,
+
+ // // https://github.com/video-dev/hls.js/issues/3884#issuecomment-842380784
+ 'liveSyncDuration' => 2,
+ 'liveMaxLatencyDuration' => 3,
+ 'maxLiveSyncPlaybackRate' => 2,
+ 'liveDurationInfinity' => true,
+ ],
+ 'debugVideoEvents' => !!$video_events,
];
-
if ($hls_debug)
- $hls_opts['debug'] = true;
+ $js_hls_config['debug'] = true;
- $this->tpl->add_static('hls.js');
- // $this->tpl->add_external_static('js', 'https://cdn.jsdelivr.net/npm/hls.js@latest');
+ // h265
+ $js_h265webjs_config = [
+ // https://github.com/numberwolf/h265web.js/blob/master/README_EN.MD#freetoken
+ 'token' => 'base64:QXV0aG9yOmNoYW5neWFubG9uZ3xudW1iZXJ3b2xmLEdpdGh1YjpodHRwczovL2dpdGh1Yi5jb20vbnVtYmVyd29sZixFbWFpbDpwb3JzY2hlZ3QyM0Bmb3htYWlsLmNvbSxRUTo1MzEzNjU4NzIsSG9tZVBhZ2U6aHR0cDovL3h2aWRlby52aWRlbyxEaXNjb3JkOm51bWJlcndvbGYjODY5NCx3ZWNoYXI6bnVtYmVyd29sZjExLEJlaWppbmcsV29ya0luOkJhaWR1',
+ ];
- $hls_host = config::get('cam_hls_host');
- $hls_proto = config::get('cam_hls_proto');
+ $js_config = [
+ 'isLow' => $tab == 'low',
+ 'proto' => config::get('cam_hls_proto'),
+ 'host' => config::get('cam_hls_host'),
+ 'camIds' => $camera_ids,
+ 'camLabels' => array_map(fn($id) => $config['cam_list']['labels'][$id], $camera_ids)
+ ];
+
+ $cams_by_type = [];
+ $include_h264 = false;
+ $include_h265 = false;
+ foreach ($camera_ids as $camera_id) {
+ $var_name = 'include_'.$config['cam_list']['full'][$camera_id]['type'];
+ $cams_by_type[$camera_id] = $config['cam_list']['full'][$camera_id]['type'];
+ $$var_name = true;
+ }
+ if ($include_h264) {
+ $js_config['hlsConfig'] = $js_hls_config;
+ $this->tpl->add_static('hls.js');
+ }
+ if ($include_h265) {
+ $js_config['h265webjsConfig'] = $js_h265webjs_config;
+ $this->tpl->add_static('h265webjs-dist/missile.js');
+ $this->tpl->add_static('h265webjs-dist/h265webjs-v20221106.js');
+ }
+
+ $js_config['camsByType'] = $cams_by_type;
$hls_key = config::get('cam_hls_access_key');
if ($hls_key)
setcookie_safe('hls_key', $hls_key);
- $cam_filter = function($id) use ($config, $camera_ids) {
- return in_array($id, $camera_ids);
- };
+ // $cam_filter = function($id) use ($config, $camera_ids) {
+ // return in_array($id, $camera_ids);
+ // };
$this->tpl->set([
- 'hls_host' => $hls_host,
- 'hls_proto' => $hls_proto,
- 'hls_opts' => $hls_opts,
- 'hls_access_key' => $config['cam_hls_access_key'],
+ 'js_config' => $js_config,
+
+ // 'hls_access_key' => $config['cam_hls_access_key'],
'camera_param' => $camera_param,
- 'cams' => array_values(array_filter($config['cam_list'][$tab], $cam_filter)),
+ // 'cams' => array_values(array_filter($config['cam_list'][$tab], $cam_filter)),
'tab' => $tab,
'video_events' => $video_events
]);
diff --git a/localwebsite/htdocs/assets/app.css b/localwebsite/htdocs/assets/app.css
index d93c4f7..3146bcf 100644
--- a/localwebsite/htdocs/assets/app.css
+++ b/localwebsite/htdocs/assets/app.css
@@ -159,15 +159,17 @@
flex-direction: row;
}
-.camfeeds:not(.is_mobile) > video {
+.camfeeds:not(.is_mobile) > video,
+.camfeeds:not(.is_mobile) > .video-container {
display: flex;
flex-basis: calc(50% - 20px);
justify-content: center;
flex-direction: column;
- width: calc(50% - 10px);
+ width: calc(50% - 10px) !important;
margin: 5px;
}
-.camfeeds.is_mobile > video {
+.camfeeds.is_mobile > video,
+.camfeeds.is_mobile > .video-container {
max-width: 100%;
}
diff --git a/localwebsite/htdocs/assets/app.js b/localwebsite/htdocs/assets/app.js
index 9574c8c..e72ecd9 100644
--- a/localwebsite/htdocs/assets/app.js
+++ b/localwebsite/htdocs/assets/app.js
@@ -90,12 +90,17 @@ window.addClass = function(el, name) {
window.Cameras = {
hlsOptions: null,
- hlsHost: null,
- hlsProto: null,
- debugVideoEvents: false,
+ h265webjsOptions: null,
+ host: null,
+ proto: null,
+ hlsDebugVideoEvents: false,
+
+ getUrl: function(name) {
+ return this.proto + '://' + this.host + '/ipcam/' + name + '/live.m3u8';
+ },
setupHls: function(video, name, useHls) {
- var src = this.hlsProto + '://' + this.hlsHost + '/ipcam/' + name + '/live.m3u8';
+ var src = this.getUrl(name);
// hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled.
@@ -128,7 +133,7 @@ window.Cameras = {
video.src = src;
var events = ['canplay'];
- if (this.debugVideoEvents)
+ if (this.hlsDebugVideoEvents)
events.push('canplay', 'canplaythrough', 'durationchange', 'ended', 'loadeddata', 'loadedmetadata', 'pause', 'play', 'playing', 'progress', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'waiting');
for (var i = 0; i < events.length; i++) {
@@ -146,26 +151,160 @@ window.Cameras = {
}
},
- init: function(cams, options, proto, host, debugVideoEvents) {
- // this.cams = cams;
- this.hlsOptions = options;
- this.hlsProto = proto;
- this.hlsHost = host;
- this.debugVideoEvents = debugVideoEvents
+ setupH265WebJS: function(videoContainer, name) {
+ var containerHeightFixed = false;
+ var config = {
+ player: 'video-'+name,
+ width: videoContainer.offsetWidth,
+ height: parseInt(videoContainer.offsetWidth * 9 / 16, 10),
+ accurateSeek: true,
+ token: this.h265webjsOptions.token,
+ extInfo: {
+ moovStartFlag: true,
+ readyShow: true,
+ autoPlay: true,
+ rawFps: 15,
+ }
+ };
+
+ var mediaInfo;
+ var player = window.new265webjs(this.getUrl(name), config);
+
+ player.onSeekStart = (pts) => {
+ console.log(name + ": onSeekStart:" + pts);
+ };
+
+ player.onSeekFinish = () => {
+ console.log(name + ": onSeekFinish");
+ };
+
+ player.onPlayFinish = () => {
+ console.log(name + ": onPlayFinish");
+ };
+
+ player.onRender = (width, height, imageBufferY, imageBufferB, imageBufferR) => {
+ console.log(name + ": onRender");
+ if (!containerHeightFixed) {
+ var ratio = height / width;
+ videoContainer.style.width = parseInt(videoContainer.offsetWidth * ratio, 10)+'px';
+ containerHeightFixed = true;
+ }
+ };
+
+ player.onOpenFullScreen = () => {
+ console.log(name + ": onOpenFullScreen");
+ };
+
+ player.onCloseFullScreen = () => {
+ console.log(name + ": onCloseFullScreen");
+ };
+
+ player.onSeekFinish = () => {
+ console.log(name + ": onSeekFinish");
+ };
+
+ player.onLoadCache = () => {
+ console.log(name + ": onLoadCache");
+ };
+
+ player.onLoadCacheFinshed = () => {
+ console.log(name + ": onLoadCacheFinshed");
+ };
+
+ player.onReadyShowDone = () => {
+ // console.log(name + ": onReadyShowDone:【You can play now】");
+ player.play()
+ };
+
+ player.onLoadFinish = () => {
+ console.log(name + ": onLoadFinish");
+
+ player.setVoice(1.0);
+
+ mediaInfo = player.mediaInfo();
+ console.log("onLoadFinish mediaInfo===========>", mediaInfo);
+
+ var codecName = "h265";
+ if (mediaInfo.meta.isHEVC === false) {
+ console.log(name + ": onLoadFinish is Not HEVC/H.265");
+ codecName = "h264";
+ } else {
+ console.log(name + ": onLoadFinish is HEVC/H.265");
+ }
+
+ console.log(name + ": onLoadFinish media Codec:" + codecName);
+ console.log(name + ": onLoadFinish media FPS:" + mediaInfo.meta.fps);
+ console.log(name + ": onLoadFinish media size:" + mediaInfo.meta.size.width + "x" + mediaInfo.meta.size.height);
+
+ if (mediaInfo.meta.audioNone) {
+ console.log(name + ": onLoadFinish media no Audio");
+ } else {
+ console.log(name + ": onLoadFinish media sampleRate:" + mediaInfo.meta.sampleRate);
+ }
+
+ if (mediaInfo.videoType == "vod") {
+ console.log(name + ": onLoadFinish media is VOD");
+ console.log(name + ": onLoadFinish media dur:" + Math.ceil(mediaInfo.meta.durationMs) / 1000.0);
+ } else {
+ console.log(name + ": onLoadFinish media is LIVE");
+ }
+ };
+
+ player.onCacheProcess = (cPts) => {
+ console.log(name + ": onCacheProcess:" + cPts);
+ };
+
+ player.onPlayTime = (videoPTS) => {
+ if (mediaInfo.videoType == "vod") {
+ console.log(name + ": onPlayTime:" + videoPTS);
+ } else {
+ // LIVE
+ }
+ };
+
+ player.do();
+ // console.log('setupH265WebJS: video: ', video.offsetWidth, video.offsetHeight)
+ },
+
+ init: function(opts) {
+ this.proto = opts.proto;
+ this.host = opts.host;
+ this.hlsOptions = opts.hlsConfig;
+ this.h265webjsOptions = opts.h265webjsConfig;
let useHls = Hls.isSupported();
- if (!useHls && !this.hasFallbackSupport()) {
+ if (!useHls && !this.hasFallbackSupport() && opts.hlsConfig !== undefined) {
alert('Neither HLS nor vnd.apple.mpegurl is not supported by your browser.');
return;
}
- for (var i = 0; i < cams.length; i++) {
- var name = cams[i];
- var video = document.createElement('video');
- video.setAttribute('id', 'video-'+name);
- document.getElementById('videos').appendChild(video);
+ for (var camId in opts.camsByType) {
+ var name = camId + '';
+ if (opts.isLow)
+ name += '-low';
+ var type = opts.camsByType[camId];
- this.setupHls(video, name, useHls);
+ switch (type) {
+ case 'h265':
+ var videoContainer = document.createElement('div');
+ videoContainer.setAttribute('id', 'video-'+name);
+ videoContainer.setAttribute('style', 'position: relative'); // a hack to fix an error in h265webjs lib
+ videoContainer.className = 'video-container';
+ document.getElementById('videos').appendChild(videoContainer);
+ try {
+ this.setupH265WebJS(videoContainer, name);
+ } catch (e) {
+ console.error('cam'+camId+': error', e)
+ }
+ break;
+
+ case 'h264':
+ var video = document.createElement('video');
+ video.setAttribute('id', 'video-'+name);
+ document.getElementById('videos').appendChild(video);
+ this.setupHls(video, name, useHls);
+ break;
+ }
}
},
diff --git a/localwebsite/templates-web/cams.twig b/localwebsite/templates-web/cams.twig
index 523e27c..38b2954 100644
--- a/localwebsite/templates-web/cams.twig
+++ b/localwebsite/templates-web/cams.twig
@@ -17,5 +17,5 @@
if (isTouchDevice()) {
addClass(ge('videos'), 'is_mobile');
}
-Cameras.init({{ cams|json_encode|raw }}, {{ hls_opts|json_encode|raw }}, '{{ hls_proto }}', '{{ hls_host }}', {{ video_events ? 'true' : 'false' }});
+Cameras.init({{ js_config|json_encode|raw }});
{% endjs %} \ No newline at end of file