lws: support h265 camera streams
This commit is contained in:
parent
bd8d5040eb
commit
850083225c
@ -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,
|
||||
|
@ -67,41 +67,71 @@ class MiscHandler extends RequestHandler
|
||||
|
||||
$tab = $high ? 'high' : 'low';
|
||||
|
||||
$hls_opts = [
|
||||
'startPosition' => -1,
|
||||
// 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,
|
||||
// // 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)
|
||||
$js_hls_config['debug'] = true;
|
||||
|
||||
// h265
|
||||
$js_h265webjs_config = [
|
||||
// https://github.com/numberwolf/h265web.js/blob/master/README_EN.MD#freetoken
|
||||
'token' => 'base64:QXV0aG9yOmNoYW5neWFubG9uZ3xudW1iZXJ3b2xmLEdpdGh1YjpodHRwczovL2dpdGh1Yi5jb20vbnVtYmVyd29sZixFbWFpbDpwb3JzY2hlZ3QyM0Bmb3htYWlsLmNvbSxRUTo1MzEzNjU4NzIsSG9tZVBhZ2U6aHR0cDovL3h2aWRlby52aWRlbyxEaXNjb3JkOm51bWJlcndvbGYjODY5NCx3ZWNoYXI6bnVtYmVyd29sZjExLEJlaWppbmcsV29ya0luOkJhaWR1',
|
||||
];
|
||||
|
||||
if ($hls_debug)
|
||||
$hls_opts['debug'] = true;
|
||||
$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)
|
||||
];
|
||||
|
||||
$this->tpl->add_static('hls.js');
|
||||
// $this->tpl->add_external_static('js', 'https://cdn.jsdelivr.net/npm/hls.js@latest');
|
||||
$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');
|
||||
}
|
||||
|
||||
$hls_host = config::get('cam_hls_host');
|
||||
$hls_proto = config::get('cam_hls_proto');
|
||||
$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
|
||||
]);
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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 %}
|
Loading…
x
Reference in New Issue
Block a user