diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2023-05-29 23:40:14 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2023-05-29 23:40:14 +0300 |
commit | 850083225ca0c3df7168d84655e1352b05b153fa (patch) | |
tree | 2af42e0bef4133333a6eb047ffe0beaca813a6bd | |
parent | bd8d5040eb95a9f11388be982ab71653f7a4949d (diff) |
lws: support h265 camera streams
-rw-r--r-- | localwebsite/config.php | 4 | ||||
-rw-r--r-- | localwebsite/handlers/MiscHandler.php | 74 | ||||
-rw-r--r-- | localwebsite/htdocs/assets/app.css | 8 | ||||
-rw-r--r-- | localwebsite/htdocs/assets/app.js | 175 | ||||
-rw-r--r-- | localwebsite/templates-web/cams.twig | 2 |
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 |