support street cameras
This commit is contained in:
parent
02f676029a
commit
225ad92fda
6
doc/ipcam-streaming.md
Normal file
6
doc/ipcam-streaming.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Let's assume IP cameras stream h264 via rtsp.
|
||||||
|
|
||||||
|
To `/etc/fstab`:
|
||||||
|
```
|
||||||
|
tmpfs /var/ipcamfs tmpfs mode=1755,uid=1000,gid=1000 0 0
|
||||||
|
```
|
@ -49,10 +49,15 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'static' => [
|
'static' => [
|
||||||
'app.css' => 6,
|
'app.css' => 8,
|
||||||
'app.js' => 1,
|
'app.js' => 1,
|
||||||
'polyfills.js' => 1,
|
'polyfills.js' => 1,
|
||||||
'modem.js' => 1,
|
'modem.js' => 1,
|
||||||
'inverter.js' => 2,
|
'inverter.js' => 2,
|
||||||
|
],
|
||||||
|
|
||||||
|
'cam_hls_host' => '192.168.1.1',
|
||||||
|
'cam_list' => [
|
||||||
|
// fill me with names
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
@ -20,7 +20,7 @@ abstract class base_tpl {
|
|||||||
public function __construct($templates_dir, $cache_dir) {
|
public function __construct($templates_dir, $cache_dir) {
|
||||||
global $config;
|
global $config;
|
||||||
|
|
||||||
$cl = get_called_class();
|
// $cl = get_called_class();
|
||||||
|
|
||||||
$this->twig = self::twig_instance($templates_dir, $cache_dir, $config['is_dev']);
|
$this->twig = self::twig_instance($templates_dir, $cache_dir, $config['is_dev']);
|
||||||
$this->static_time = time();
|
$this->static_time = time();
|
||||||
|
@ -49,4 +49,17 @@ class MiscHandler extends RequestHandler
|
|||||||
$this->tpl->render_page('pump.twig');
|
$this->tpl->render_page('pump.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function GET_cams() {
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
$this->tpl->add_external_static('js', 'https://cdn.jsdelivr.net/npm/hls.js@latest');
|
||||||
|
|
||||||
|
$this->tpl->set([
|
||||||
|
'hls_host' => $config['cam_hls_host'],
|
||||||
|
'cams' => $config['cam_list']
|
||||||
|
]);
|
||||||
|
$this->tpl->set_title('Камеры');
|
||||||
|
$this->tpl->render_page('cams.twig');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -150,4 +150,19 @@
|
|||||||
@keyframes sk-circleFadeDelay {
|
@keyframes sk-circleFadeDelay {
|
||||||
0%, 39%, 100% { opacity: 0; }
|
0%, 39%, 100% { opacity: 0; }
|
||||||
40% { opacity: 1; }
|
40% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cams page */
|
||||||
|
.camfeeds {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.camfeeds > video {
|
||||||
|
display: flex;
|
||||||
|
flex-basis: calc(50% - 20px);
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
width: calc(50% - 10px);
|
||||||
|
margin: 5px;
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ $router->add('/', 'Misc main');
|
|||||||
$router->add('sensors/', 'Misc sensors_page');
|
$router->add('sensors/', 'Misc sensors_page');
|
||||||
$router->add('pump/', 'Misc pump_page');
|
$router->add('pump/', 'Misc pump_page');
|
||||||
$router->add('phpinfo/', 'Misc phpinfo');
|
$router->add('phpinfo/', 'Misc phpinfo');
|
||||||
|
$router->add('cams/', 'Misc cams');
|
||||||
|
|
||||||
|
|
||||||
$route = routerFind($router);
|
$route = routerFind($router);
|
||||||
|
64
localwebsite/templates-web/cams.twig
Normal file
64
localwebsite/templates-web/cams.twig
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/">Главная</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Камеры</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div id="videos" class="camfeeds"></div>
|
||||||
|
<video height="300" id="video"></video>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function hasFallbackSupport() {
|
||||||
|
var video = document.createElement('video');
|
||||||
|
return video.canPlayType('application/vnd.apple.mpegurl');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupHls(video, name, useHls) {
|
||||||
|
var src = 'http://{{ hls_host }}/ipcam/'+name+'/live.m3u8';
|
||||||
|
|
||||||
|
// hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled.
|
||||||
|
|
||||||
|
// When the browser has built-in HLS support (check using `canPlayType`), we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video element through the `src` property.
|
||||||
|
// This is using the built-in support of the plain video element, without using hls.js.
|
||||||
|
|
||||||
|
if (useHls) {
|
||||||
|
var hls = new Hls({
|
||||||
|
// debug: true,
|
||||||
|
startPosition: -1,
|
||||||
|
});
|
||||||
|
hls.loadSource(src);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
||||||
|
video.muted = true;
|
||||||
|
video.play();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
video.src = src;
|
||||||
|
video.addEventListener('canplay', function () {
|
||||||
|
video.play();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
let useHls = Hls.isSupported();
|
||||||
|
if (!useHls && !hasFallbackSupport()) {
|
||||||
|
alert('Neither HLS nor vnd.apple.mpegurl is not supported by your browser.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cams = {{ cams|json_encode|raw }};
|
||||||
|
for (var i = 0; i < cams.length; i++) {
|
||||||
|
var name = cams[i];
|
||||||
|
var video = document.createElement('video');
|
||||||
|
// video.setAttribute('height', '400');
|
||||||
|
video.setAttribute('id', 'video-'+name);
|
||||||
|
document.getElementById('videos').appendChild(video);
|
||||||
|
|
||||||
|
setupHls(video, name, useHls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
</script>
|
@ -17,5 +17,6 @@
|
|||||||
<li class="list-group-item"><a href="/inverter/">Инвертор</a></li>
|
<li class="list-group-item"><a href="/inverter/">Инвертор</a></li>
|
||||||
<li class="list-group-item"><a href="/pump/">Насос</a></li>
|
<li class="list-group-item"><a href="/pump/">Насос</a></li>
|
||||||
<li class="list-group-item"><a href="/sensors/">Датчики</a></li>
|
<li class="list-group-item"><a href="/sensors/">Датчики</a></li>
|
||||||
|
<li class="list-group-item"><a href="/cams/">Камеры</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
13
systemd/ipcam-rtsp2hls@.service
Normal file
13
systemd/ipcam-rtsp2hls@.service
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=convert rtsp to hls for viewing live camera feeds in browser
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=on-failure
|
||||||
|
User=user
|
||||||
|
Group=user
|
||||||
|
EnvironmentFile=/etc/ipcam-rtsp2hls.conf.d/%i.conf
|
||||||
|
ExecStart=/home/user/homekit/tools/ipcam-rtsp2hls.sh --name %i --user $USER --password $PASSWORD --ip $IP --port $PORT $ARGS
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
95
tools/ipcam-rtsp2hls.sh
Executable file
95
tools/ipcam-rtsp2hls.sh
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PROGNAME="$0"
|
||||||
|
OUTDIR=/var/ipcamfs # should be tmpfs
|
||||||
|
PORT=554
|
||||||
|
NAME=
|
||||||
|
IP=
|
||||||
|
USER=
|
||||||
|
PASSWORD=
|
||||||
|
DEBUG=0
|
||||||
|
CHANNEL=1
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo >&2 "error: $@"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
usage: $PROGNAME [OPTIONS] COMMAND
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--ip camera IP
|
||||||
|
--port RTSP port (default: 554)
|
||||||
|
--name camera name (chunks will be stored under $OUTDIR/{name}/)
|
||||||
|
--user
|
||||||
|
--password
|
||||||
|
--debug
|
||||||
|
--channel 1|2
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_channel() {
|
||||||
|
local c="$1"
|
||||||
|
case "$c" in
|
||||||
|
1|2)
|
||||||
|
:
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "Invalid channel"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -z "$1" ] && usage
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--ip|--port|--name|--user|--password)
|
||||||
|
_var=${1:2}
|
||||||
|
_var=${_var^^}
|
||||||
|
printf -v "$_var" '%s' "$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
--debug)
|
||||||
|
DEBUG=1
|
||||||
|
;;
|
||||||
|
|
||||||
|
--channel)
|
||||||
|
CHANNEL="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
die "Unrecognized argument: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -z "$IP" ] && die "You must specify camera IP address (--ip)."
|
||||||
|
[ -z "$PORT" ] && die "Port can't be empty."
|
||||||
|
[ -z "$NAME" ] && die "You must specify camera name (--name)."
|
||||||
|
[ -z "$USER" ] && die "You must specify username (--user)."
|
||||||
|
[ -z "$PASSWORD" ] && die "You must specify username (--password)."
|
||||||
|
validate_channel "$CHANNEL"
|
||||||
|
|
||||||
|
if [ ! -d "${OUTDIR}/${NAME}" ]; then
|
||||||
|
mkdir "${OUTDIR}/${NAME}" || die "Failed to create ${OUTDIR}/${NAME}!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DEBUG" = "1" ]; then
|
||||||
|
ffmpeg_args="-v info"
|
||||||
|
else
|
||||||
|
ffmpeg_args="-nostats -loglevel error"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ffmpeg $ffmpeg_args -i rtsp://${USER}:${PASSWORD}@${IP}:${PORT}/Streaming/Channels/${CHANNEL} \
|
||||||
|
-c:v copy -c:a copy -bufsize 1835k \
|
||||||
|
-pix_fmt yuv420p \
|
||||||
|
-flags -global_header -hls_time 5 -hls_list_size 6 -hls_wrap 5 \
|
||||||
|
${OUTDIR}/${NAME}/live.m3u8
|
Loading…
x
Reference in New Issue
Block a user