2023-05-15 17:07:19 +02:00
< ? php
2023-05-15 18:50:30 +02:00
// Grabbing the camera's info
$camsInfo = []; // Format: [[camId, camName], ...]
foreach ( $_ENV as $envKey => $envValue ) {
if ( strpos ( $envKey , 'NP_CAM_' ) === 0 ) {
array_push ( $camsInfo , [ substr ( $envKey , strlen ( 'NP_CAM_' )), $envValue ]);
}
}
// Grabbing the other env variables.
$pageTitle = $_ENV [ 'NP_TITLE' ] ? ? 'NibblePoker\'s Mini CCTV NVR' ;
$pageFooter = $_ENV [ 'NP_FOOTER' ] ? ? 'Made by <a href="https://github.com/aziascreations">BOZET Herwin</a> on <a href="https://github.com/aziascreations/Docker-Mini-CCTV-NVR">Github</a>' ;
2023-05-15 17:07:19 +02:00
// Root location of all recordings. (Not used yet)
$rootLocation = " ./data/ " ;
// Grabbing the requested cam's ID
// The id should be the same as the sub-folder into which this camera's recordings are located.
$camId = $_GET [ 'cam' ] ? ? null ;
// Determining if the ID is valid and the name that should be shown.
// If the ID is invalid, it is set back to `null`.
if ( is_null ( $camId )) {
$camName = " None " ;
} else {
$isCamValid = false ;
foreach ( $camsInfo as $singleCamInfo ) {
if ( $singleCamInfo [ 0 ] == $camId ) {
$camName = $singleCamInfo [ 1 ];
$isCamValid = true ;
break ;
}
}
if ( ! $isCamValid ) {
$camName = " Unknown " ;
$camId = null ;
}
}
// Grabbing the list of recordings if needed.
if ( is_null ( $camId )) {
// No cam selected, we just use empty variables.
$basePath = " ./ " ;
$files = [];
} else {
$basePath = " /data/ " . $camId . " / " ;
$files = array_values ( array_diff ( scandir ( " ./data/ " . $camId . " / " ), array ( '.' , '..' )));
// Removing the newest one as it is highly likely to currently being written to by ffmpeg.
array_pop ( $files );
}
// If we only need to send the JSON, we send it and don't go further.
$returnJsonOnly = ! is_null ( $_GET [ 'json' ] ? ? null );
if ( $returnJsonOnly ) {
header ( 'Content-Type: application/json; charset=utf-8' );
echo json_encode ( $files );
exit ();
}
// Function used to calculate the disk space taken by recordings later on.
function folderSize ( $dir ){
$count_size = 0 ;
$count = 0 ;
$dir_array = scandir ( $dir );
foreach ( $dir_array as $key => $filename ){
if ( $filename != " .. " && $filename != " . " ){
if ( is_dir ( $dir . " / " . $filename )){
$new_foldersize = foldersize ( $dir . " / " . $filename );
$count_size = $count_size + $new_foldersize ;
} else if ( is_file ( $dir . " / " . $filename )) {
$count_size = $count_size + filesize ( $dir . " / " . $filename );
$count ++ ;
}
}
}
return $count_size ;
}
// Function used to format the disk space taken by recordings later on.
function sizeFormat ( $bytes ) {
$kb = 1024 ;
$mb = $kb * 1024 ;
$gb = $mb * 1024 ;
$tb = $gb * 1024 ;
if (( $bytes >= 0 ) && ( $bytes < $kb )) {
return $bytes . ' B' ;
} elseif (( $bytes >= $kb ) && ( $bytes < $mb )) {
return ceil ( $bytes / $kb ) . ' KiB' ;
} elseif (( $bytes >= $mb ) && ( $bytes < $gb )) {
return ceil ( $bytes / $mb ) . ' MiB' ;
} elseif (( $bytes >= $gb ) && ( $bytes < $tb )) {
return ceil ( $bytes / $gb ) . ' GiB' ;
} elseif ( $bytes >= $tb ) {
return ceil ( $bytes / $tb ) . ' TiB' ;
} else {
return $bytes . ' B' ;
}
}
?>
<! doctype html >
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport "
content = " width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0 " >
< meta http - equiv = " X-UA-Compatible " content = " ie=edge " >
2023-05-15 18:50:30 +02:00
< title >< ? php echo ( $pageTitle ); ?> </title>
2023-05-15 17:07:19 +02:00
< link rel = " stylesheet " href = " /css/simplette.all.min.css " >
< style >
#video-selector {
min - width : 95 % ;
}
video {
2023-05-16 06:05:54 +02:00
max - height : 60 vh ! important ;
max - width : 95 vw ! important ;
2023-05-15 17:07:19 +02:00
border - radius : 0.5 em ;
}
#skippers a {
margin - left : 1 em ;
margin - right : 1 em ;
user - select : none ;
}
input [ type = range ] {
box - shadow : none ;
}
#video-caching {
display : none ;
}
</ style >
</ head >
< body >
< nav class = " margin-container " >
< ul class = " link-list " >
< li >< a href = " / " > Home </ a ></ li >
< li >< a href = " /data " > Raw recordings </ a ></ li >
< ? php
// Adding the cameras in the navbar
foreach ( $camsInfo as $singleCamInfo ) {
echo ( " <li><a href= \" /?cam= " . $singleCamInfo [ 0 ] . " \" > " . $singleCamInfo [ 1 ] . " </a></li> " );
}
?>
</ ul >
</ nav >
2023-05-15 18:50:30 +02:00
< header >< h1 >< b >< ? php echo ( $pageTitle ); ?> </b></h1></header>
2023-05-15 17:07:19 +02:00
< hr >< hr >
< div class = " margin-container auto-paragraph-margin " >
< table style = " width: 100%; " >
< tr >
< td >
2023-05-15 18:50:30 +02:00
< h3 style = " width: 100%; " > Camera : < i >< ? php echo ( $camName ); ?> </i></h3>
2023-05-15 17:07:19 +02:00
</ td >
< td >
< span style = " float: right; " >< ? php
// Printing the space taken by all cams and the current one if possible.
$totalSize = sizeFormat ( folderSize ( " ./data/ " ));
if ( is_null ( $camId )) {
echo ( $totalSize );
} else {
echo ( sizeFormat ( folderSize ( " ./data/ " . $camId . " / " )) . " / " . $totalSize );
}
?> </span>
</ td >
</ tr >
</ table >
</ div >
< hr >
< div class = " margin-container " >
< ? php
if ( is_null ( $camId )) {
// No camera selected.
echo ( " <p class= \" h5 \" style= \" margin-bottom: 0.5em; \" >Select one camera:</p> " );
echo ( " <ul class= \" link-list h5 indent-container \" > " );
// Adding the cameras in the URL list
foreach ( $camsInfo as $singleCamInfo ) {
echo ( " <li><a href= \" /?cam= " . $singleCamInfo [ 0 ] . " \" > " . $singleCamInfo [ 1 ] . " </a></li> " );
}
echo ( " </ul> " );
} else {
// We have selected one, we add the video, slider, jumpers and filename placeholder.
echo ( " <center> " );
2023-05-16 06:05:54 +02:00
echo ( " <video id= \" cctv-out \" volume= \" 0.5 \" controls></video> " );
2023-05-15 17:07:19 +02:00
echo ( " <br> " );
echo ( " <input type= \" range \" id= \" video-selector \" min= \" 0 \" max= \" " . count ( $files ) . " \" value= \" 0 \" > " );
echo ( " <br> " );
echo ( " <p id= \" skippers \" > " );
echo ( " <a id= \" skip-minus-25 \" ><<<< 25</a> " );
echo ( " <a id= \" skip-minus-10 \" ><<< 10</a> " );
echo ( " <a id= \" skip-minus-5 \" ><< 5</a> " );
echo ( " <a id= \" skip-minus-1 \" >< 1</a> " );
echo ( " <a id= \" skip-plus-1 \" >1 ></a> " );
echo ( " <a id= \" skip-plus-5 \" >5 >></a> " );
echo ( " <a id= \" skip-plus-10 \" >10 >>></a> " );
echo ( " <a id= \" skip-plus-25 \" >25 >>>></a> " );
echo ( " </p> " );
echo ( " <br> " );
echo ( " <p>File: <a id= \" url-video \" href= \" # \" >Non définis</a> (<span id= \" vid-count-current \" >0</span>/<span id= \" vid-count-total \" >0</span>)</p> " );
echo ( " </center> " );
echo ( " <br> " );
}
?>
</ div >
< hr >< hr >
< footer >
2023-05-15 18:50:30 +02:00
< p >< ? php echo ( $pageFooter ); ?> </p>
2023-05-15 17:07:19 +02:00
</ footer >
< script >
< ? php
// Adding the base path and initial file listing as JS variables.
echo ( " const basePath = \" " . $basePath . " \" ; " );
echo ( " let files = " . json_encode ( $files ) . " ; " );
?>
const videoCaching = document . createElement ( 'video' );
const videoListUpdateIntervalMs = 10 * 1000 ;
const startOffset = 2 ;
let iCurrentVideo = files . length - startOffset ;
if ( iCurrentVideo < 0 ) {
iCurrentVideo = 0 ;
}
document . addEventListener ( 'DOMContentLoaded' , function () {
videoCaching . preload = 'auto' ;
videoCaching . id = 'video-caching' ;
const eVideo = document . getElementById ( " cctv-out " );
const eVideoSelector = document . getElementById ( " video-selector " );
// If we have a video element in the DOM, we set it up.
if ( eVideo !== null ) {
// Handles every change of video
const playNextVideo = () => {
if ( files . length > 0 && iCurrentVideo <= files . length ) {
const newSource = basePath + files [ iCurrentVideo ];
eVideo . src = newSource ;
// Setting the MIME type on the visible player, just in case.
if ( newSource . endsWith ( " .mkv " )) {
eVideo . type = 'video/x-matroska' ;
} else if ( newSource . endsWith ( " .mp4 " )) {
eVideo . type = 'video/mp4' ;
} else {
eVideo . type = '' ;
}
eVideoSelector . value = iCurrentVideo ;
document . getElementById ( " url-video " ) . href = newSource ;
document . getElementById ( " url-video " ) . text = newSource ;
document . getElementById ( " vid-count-current " ) . textContent = iCurrentVideo ;
eVideo . play ();
// If there is a next video in the list, we attempt to cache it via a hidden player.
if ( iCurrentVideo + 1 < files . length ) {
videoCaching . preload = 'auto' ;
videoCaching . src = basePath + files [ iCurrentVideo + 1 ];
// Setting the MIME type on the caching player, just in case.
if ( files [ iCurrentVideo + 1 ] . endsWith ( " .mkv " )) {
videoCaching . type = 'video/x-matroska' ;
} else if ( files [ iCurrentVideo + 1 ] . endsWith ( " .mp4 " )) {
videoCaching . type = 'video/mp4' ;
} else {
videoCaching . type = '' ;
}
}
}
};
// Repeated function that updates the list of available videos every now and then.
const updateVideoList = () => {
fetch ( window . location + " &json=1 " )
. then ( response => {
if ( ! response . ok ) {
throw new Error ( 'Network response was not ok' );
}
return response . json ();
})
. then ( data => {
let newIndex = data . indexOf ( files [ iCurrentVideo ]);
files = data ;
if ( newIndex === - 1 ) {
newIndex = files . length - startOffset ;
}
iCurrentVideo = newIndex ;
eVideoSelector . value = iCurrentVideo ;
eVideoSelector . max = files . length ;
document . getElementById ( " vid-count-current " ) . textContent = iCurrentVideo ;
document . getElementById ( " vid-count-total " ) . textContent = files . length ;
setTimeout ( updateVideoList , videoListUpdateIntervalMs );
})
. catch ( error => {
setTimeout ( updateVideoList , videoListUpdateIntervalMs );
});
};
// Trigerred when a video ends.
eVideo . addEventListener ( " ended " , () => {
iCurrentVideo ++ ;
playNextVideo ();
});
// Trigerred every time a video plays.
// Used to keep the video's frame at a constant size.
// It looks like ass otherwise since it "flickers" between 2 sizes.
eVideo . addEventListener ( " playing " , function () {
2023-05-16 06:05:54 +02:00
// We only change the size when not in fullscreen mode.
// This helps prevent sizing issues when going out of it after a new recording starts playing.
if ( document . fullscreenElement === null ) {
eVideo . width = eVideo . offsetWidth ;
eVideo . height = eVideo . offsetHeight ;
eVideo . style . minWidth = eVideo . offsetWidth + " px " ;
eVideo . style . minHeight = eVideo . offsetHeight + " px " ;
}
2023-05-15 17:07:19 +02:00
});
// Changes the "current video" number when moving the slider.
eVideoSelector . oninput = function () {
document . getElementById ( " vid-count-current " ) . textContent = eVideoSelector . value ;
};
// Plays the correct video once the slider is released.
eVideoSelector . onchange = function () {
iCurrentVideo = eVideoSelector . value ;
playNextVideo ();
};
// Quick jumps
document . getElementById ( 'skip-minus-25' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . max ( 0 , iCurrentVideo - 25 );
playNextVideo ();
});
document . getElementById ( 'skip-minus-10' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . max ( 0 , iCurrentVideo - 10 );
playNextVideo ();
});
document . getElementById ( 'skip-minus-5' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . max ( 0 , iCurrentVideo - 5 );
playNextVideo ();
});
document . getElementById ( 'skip-minus-1' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . max ( 0 , iCurrentVideo - 1 );
playNextVideo ();
});
document . getElementById ( 'skip-plus-1' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . min ( files . length - 1 , iCurrentVideo + 1 );
playNextVideo ();
});
document . getElementById ( 'skip-plus-5' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . min ( files . length - 1 , iCurrentVideo + 5 );
playNextVideo ();
});
document . getElementById ( 'skip-plus-10' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . min ( files . length - 1 , iCurrentVideo + 10 );
playNextVideo ();
});
document . getElementById ( 'skip-plus-25' ) . addEventListener ( 'click' , function () {
iCurrentVideo = Math . min ( files . length - 1 , iCurrentVideo + 25 );
playNextVideo ();
});
// Starting up the player, the list updater loop and setting the currently played vid number.
document . getElementById ( " vid-count-total " ) . textContent = files . length ;
playNextVideo ();
2023-05-16 06:05:54 +02:00
eVideo . volume = 0.5 ;
2023-05-15 17:07:19 +02:00
setTimeout ( updateVideoList , videoListUpdateIntervalMs );
}
});
</ script >
</ body >
</ html >