(Super) Timeline, Power Viewer v10

node-20
Moe 2023-08-04 17:00:58 +00:00
parent 17f535b9ec
commit b4566ca886
14 changed files with 1234 additions and 24 deletions

View File

@ -7752,6 +7752,12 @@ module.exports = function(s,config,lang){
<span class="badge bg-light text-dark rounded-pill align-text-bottom cameraCount"><i class="fa fa-spinner fa-pulse"></i></span>`, <span class="badge bg-light text-dark rounded-pill align-text-bottom cameraCount"><i class="fa fa-spinner fa-pulse"></i></span>`,
pageOpen: 'monitorsList', pageOpen: 'monitorsList',
}, },
{
icon: 'barcode',
label: `${lang['Timeline']}`,
pageOpen: 'timeline',
addUl: true,
},
{ {
icon: 'film', icon: 'film',
label: `${lang['Videos']}`, label: `${lang['Videos']}`,
@ -9106,5 +9112,84 @@ module.exports = function(s,config,lang){
}, },
} }
}, },
"Timeline": {
"section": "Timeline",
"blocks": {
"Search Settings": {
"name": lang["Timeline"],
"color": "blue",
"noHeader": true,
"noDefaultSectionClasses": true,
"box-wrapper-class": "flex-direction-column",
"info": [
{
"fieldType": "div",
"class": "row m-0",
"id": "timeline-video-canvas",
},
{
"fieldType": "div",
"class": "row p-1 m-0",
"id": "timeline-info",
"divContent": `
<div class="text-center">
<span class="current-time font-monospace ${textWhiteOnBgDark}"></span>
</div>`
},
{
"fieldType": "div",
"class": "p-2",
"id": "timeline-controls",
"divContent": `
<div class="text-center">
<div class="btn-group">
<a class="btn btn-sm btn-default" timeline-action="jumpLeft" title="${lang.jumpFiveSeconds}"><i class="fa fa-arrow-circle-left"></i></a>
<a class="btn btn-sm btn-primary" timeline-action="playpause" title="${lang.Play}/${lang.Pause}"><i class="fa fa-play-circle-o"></i></a>
<a class="btn btn-sm btn-default" timeline-action="jumpRight" title="${lang.jumpFiveSeconds}"><i class="fa fa-arrow-circle-right"></i></a>
</div>
<div class="btn-group">
<a class="btn btn-sm btn-default btn-success" timeline-action="speed" speed="1" title="${lang.Speed} x1">x1</a>
<a class="btn btn-sm btn-default" timeline-action="speed" speed="2" title="${lang.Speed} x2">x2</a>
<a class="btn btn-sm btn-default" timeline-action="speed" speed="5" title="${lang.Speed} x5">x5</a>
<a class="btn btn-sm btn-default" timeline-action="speed" speed="7" title="${lang.Speed} x7">x7</a>
<a class="btn btn-sm btn-default" timeline-action="speed" speed="10" title="${lang.Speed} x10">x10</a>
</div>
<div class="btn-group">
<a class="btn btn-sm btn-default" timeline-action="gridSize" size="md-12">1</a>
<a class="btn btn-sm btn-default btn-success" timeline-action="gridSize" size="md-6">2</a>
<a class="btn btn-sm btn-default" timeline-action="gridSize" size="md-4">3</a>
</div>
<div class="btn-group">
<input class="form-control form-control-sm" id="timeline-video-object-search" placeholder="${lang['Search Object Tags']}">
</div>
<div class="btn-group">
<a class="btn btn-sm btn-primary" timeline-action="autoGridSizer" title="${lang.autoResizeGrid}"><i class="fa fa-expand"></i></a>
</div>
<div class="btn-group">
<a class="btn btn-sm btn-success" timeline-action="downloadAll" title="${lang.Download}"><i class="fa fa-download"></i></a>
</div>
<div class="btn-group">
<a class="btn btn-sm btn-default" class_toggle="show-only-playing" data-target="#timeline-video-canvas" icon-toggle="fa-eye fa-eye-slash" icon-child="i" title="${lang['Show Only Playing']}"><i class="fa fa-eye"></i></a>
<a class="btn btn-sm btn-default" timeline-action="refresh" title="${lang.Refresh}"><i class="fa fa-refresh"></i></a>
</div>
<div class="btn-group">
<a class="btn btn-sm btn-primary" id="timeline-date-selector" title="${lang.Date}"><i class="fa fa-calendar"></i></a>
</div>
</div>
`,
},
{
"fieldType": "div",
"id": "timeline-bottom-strip",
},
{
"fieldType": "div",
"id": "timeline-pre-buffers",
"class": "hidden",
}
]
},
}
},
}) })
} }

View File

@ -27,6 +27,7 @@
"Accuracy Mode": "Accuracy Mode", "Accuracy Mode": "Accuracy Mode",
"Client ID": "Client ID", "Client ID": "Client ID",
"Tags": "Tags", "Tags": "Tags",
"addTagText": "Add Tags to your monitors to get more choices here.",
"tagsCannotAddText": "Cannot add Tag", "tagsCannotAddText": "Cannot add Tag",
"tagsTriggerCannotAddText": "Trigger tags must match an existing tag that was added to a Monitor previously or is pending to be added to this monitor that is currently being edited.", "tagsTriggerCannotAddText": "Trigger tags must match an existing tag that was added to a Monitor previously or is pending to be added to this monitor that is currently being edited.",
"tagsFieldText": "Automatically group Monitors based on a common identifier.", "tagsFieldText": "Automatically group Monitors based on a common identifier.",
@ -37,6 +38,7 @@
"Tile Size": "Tile Size", "Tile Size": "Tile Size",
"fieldTextTileSize": "When in Accuracy Mode this is the size of each tile in pixels squared. A lower number will have higher accuracy but more resource use.", "fieldTextTileSize": "When in Accuracy Mode this is the size of each tile in pixels squared. A lower number will have higher accuracy but more resource use.",
"Turn Speed": "Turn Speed", "Turn Speed": "Turn Speed",
"Speed": "Speed",
"Session Key": "Session Key", "Session Key": "Session Key",
"Active Monitors": "Active Monitors", "Active Monitors": "Active Monitors",
"Storage Use": "Storage Use", "Storage Use": "Storage Use",
@ -121,8 +123,10 @@
"Slice": "Slice", "Slice": "Slice",
"Stitch": "Stitch", "Stitch": "Stitch",
"Studio": "Studio", "Studio": "Studio",
"Show Only Playing": "Show Only Playing",
"Power Video Viewer": "Power Video Viewer", "Power Video Viewer": "Power Video Viewer",
"Time-lapse": "Time-lapse", "Time-lapse": "Time-lapse",
"Timeline": "Timeline",
"Montage": "Montage", "Montage": "Montage",
"Registered": "Registered", "Registered": "Registered",
"Viewing Server Stats": "Viewing Server Stats", "Viewing Server Stats": "Viewing Server Stats",
@ -1013,6 +1017,10 @@
"File Not Exist": "File Not Exist", "File Not Exist": "File Not Exist",
"No Videos Found": "No Videos Found", "No Videos Found": "No Videos Found",
"activateRequiredLiveStream": "Live Stream is only available here with an Activated installation.", "activateRequiredLiveStream": "Live Stream is only available here with an Activated installation.",
"activationRequired": "Activation Required",
"autoResizeGrid": "Auto Resize Grid",
"jumpFiveSeconds": "Jump 5 Seconds",
"featureRequiresActivationText": "The feature you are trying to use requires that your installation be activated. Please see the Help tab for more information.",
"FileNotExistText": "Cannot save non existant file. Something went wrong.", "FileNotExistText": "Cannot save non existant file. Something went wrong.",
"CameraNotRecordingText": "Settings may be incompatible. Check encoders. Restarting...", "CameraNotRecordingText": "Settings may be incompatible. Check encoders. Restarting...",
"Camera is not running": "Camera is not running", "Camera is not running": "Camera is not running",

View File

@ -130,17 +130,29 @@ module.exports = function(s,config,lang){
}) })
device = activeMonitor.onvifConnection device = activeMonitor.onvifConnection
} }
controlOptions.ProfileToken = device.current_profile.token function returnResponse(){
const actionResponse = await s.runOnvifMethod({ return new Promise((resolve,reject) => {
auth: { if(
ke: options.ke, !device ||
id: options.id, !device.current_profile ||
action: 'continuousMove', !device.current_profile.token
service: 'ptz', ){
}, resolve({ok: false, msg: lang.ONVIFEventsNotAvailableText1})
options: controlOptions, return
}); }
return actionResponse; controlOptions.ProfileToken = device.current_profile.token
s.runOnvifMethod({
auth: {
ke: options.ke,
id: options.id,
action: 'continuousMove',
service: 'ptz',
},
options: controlOptions,
},resolve)
})
}
return await returnResponse();
} }
function stopMoveOnvif(options){ function stopMoveOnvif(options){
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {

View File

@ -351,7 +351,7 @@ module.exports = (s,config,lang) => {
function bindTagLegendForMonitors(groupKey){ function bindTagLegendForMonitors(groupKey){
const newTagLegend = {} const newTagLegend = {}
const theGroup = s.group[groupKey] const theGroup = s.group[groupKey]
const monitorIds = Object.keys(theGroup.rawMonitorConfigurations) const monitorIds = Object.keys(theGroup.rawMonitorConfigurations || {})
monitorIds.forEach((monitorId) => { monitorIds.forEach((monitorId) => {
const monitorConfig = theGroup.rawMonitorConfigurations[monitorId] const monitorConfig = theGroup.rawMonitorConfigurations[monitorId]
const theTags = (monitorConfig.tags || '').split(',') const theTags = (monitorConfig.tags || '').split(',')

View File

@ -124,7 +124,7 @@ module.exports = function(s,config,lang){
k.details = k.details && k.details instanceof Object ? k.details : {} k.details = k.details && k.details instanceof Object ? k.details : {}
var listOEvents = activeMonitor.detector_motion_count || [] var listOEvents = activeMonitor.detector_motion_count || []
var listOTags = listOEvents.filter(row => row.details.reason === 'object').map(row => row.details.matrices.map(matrix => matrix.tag).join(',')).join(',').split(',') var listOTags = listOEvents.filter(row => row.details.reason === 'object').map(row => row.details.matrices.map(matrix => matrix.tag).join(',')).join(',').split(',')
if(listOTags && !k.objects)k.objects = [...new Set(listOTags)].join(','); if(listOTags && !k.objects)k.objects = [...new Set(listOTags)].filter(item => !!item).join(',');
k.filename = k.filename || k.file k.filename = k.filename || k.file
k.ext = k.ext || e.ext || k.filename.split('.')[1] k.ext = k.ext || e.ext || k.filename.split('.')[1]
k.stat = fs.statSync(k.dir+k.file) k.stat = fs.statSync(k.dir+k.file)

View File

@ -83,6 +83,7 @@ module.exports = function(s,config,lang,io){
'home/videosTable', 'home/videosTable',
'home/studio', 'home/studio',
'home/monitorMap', 'home/monitorMap',
'home/timeline',
'confirm', 'confirm',
'home/help', 'home/help',
] ]

164
tools/onvifGetStreamUri.js Normal file
View File

@ -0,0 +1,164 @@
const fetch = require('node-fetch');
const dgram = require('dgram');
const parseString = require('xml2js').parseString;
const base64 = require('base-64');
const USERNAME = process.argv[2]
const PASSWORD = process.argv[3]
if(!USERNAME || !PASSWORD){
console.log(`Missing Username and/or Password!`)
console.log(`Example : node ./onvifGetStreamUri.js "USERNAME" "PASSWORD"`)
console.log(`Put the quotations seen in the example!`)
return
}
function cleanKeys(obj) {
const cleanKey = (key) => key.includes(':') ? key.split(':').pop() : key;
if (Array.isArray(obj)) {
return obj.map(cleanKeys);
} else if (obj !== null && typeof obj === 'object') {
return Object.keys(obj).reduce((newObj, key) => {
newObj[cleanKey(key)] = cleanKeys(obj[key]);
return newObj;
}, {});
}
return obj;
}
async function getStreamUri(deviceXAddr, username, password) {
let envelope = `
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Body>
<GetProfiles xmlns="http://www.onvif.org/ver10/media/wsdl" />
</s:Body>
</s:Envelope>
`;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/soap+xml',
'Authorization': 'Basic ' + base64.encode(username + ':' + password),
},
body: envelope,
};
let response = await fetch(deviceXAddr, options);
let body = await response.text();
parseString(body, async (err, result) => {
if (err) {
console.error(err);
return;
}
const soapBody = cleanKeys(result).Envelope.Body[0]
if (soapBody.Fault) {
console.log(deviceXAddr,'Not Authorized');
return;
}
try{
var profiles = soapBody.GetProfilesResponse[0].Profiles;
}catch(err){
console.log(err.stack)
console.error(deviceXAddr,`getStreamUri soapBody on ERROR`,JSON.stringify(soapBody,null,3))
return
}
if (!profiles || !profiles.length) {
console.log(deviceXAddr,'No profiles found');
return;
}
const firstProfileToken = profiles[0]['$']['token'];
envelope = `
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Body>
<GetStreamUri xmlns="http://www.onvif.org/ver10/media/wsdl">
<StreamSetup>
<Stream xmlns="http://www.onvif.org/ver10/schema">RTP-Unicast</Stream>
<Transport xmlns="http://www.onvif.org/ver10/schema">
<Protocol>RTSP</Protocol>
</Transport>
</StreamSetup>
<ProfileToken>${firstProfileToken}</ProfileToken>
</GetStreamUri>
</s:Body>
</s:Envelope>
`;
options.body = envelope;
response = await fetch(deviceXAddr, options);
body = await response.text();
parseString(body, (err, result) => {
if (err) {
console.error(err);
return;
}
const uri = result['SOAP-ENV:Envelope']['SOAP-ENV:Body'][0]['trt:GetStreamUriResponse'][0]['trt:MediaUri'][0]['tt:Uri'][0];
console.log('Stream URI:', uri);
});
});
}
const DISCOVER_MSG = Buffer.from(`
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery">
<s:Header>
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action>
<a:MessageID>uuid:1000-3000-5000-70000000000000</a:MessageID>
<a:ReplyTo>
<a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To>
</s:Header>
<s:Body>
<d:Probe>
<d:Types>dn:NetworkVideoTransmitter</d:Types>
</d:Probe>
</s:Body>
</s:Envelope>
`);
const socket = dgram.createSocket('udp4');
socket.bind(() => {
socket.setBroadcast(true);
socket.setMulticastTTL(128);
socket.addMembership('239.255.255.250');
socket.send(DISCOVER_MSG, 0, DISCOVER_MSG.length, 3702, '239.255.255.250');
});
socket.on('message', function (message, rinfo) {
parseString(message, (err, result) => {
if (err) {
console.error('Failed to parse XML', err);
return;
}
const cleanJson = cleanKeys(result)
if (!cleanJson.Envelope || !cleanJson.Envelope.Body) {
console.error('Unexpected message format', result);
console.error('cleanJson', cleanJson);
return;
}
const soapBody = cleanJson.Envelope.Body[0];
const probeMatches = soapBody.ProbeMatches;
if (!probeMatches) {
console.error('No probe matches in message', soapBody);
return;
}
const probeMatch = probeMatches[0].ProbeMatch[0];
if (!probeMatch) {
console.error('No probe match in probe matches', probeMatches);
return;
}
let xAddrs = probeMatch.XAddrs[0];
console.log('Found ONVIF device', xAddrs);
getStreamUri(xAddrs, USERNAME, PASSWORD)
.catch(err => console.error('Failed to get stream URI', err));
});
});

View File

@ -0,0 +1,73 @@
#tab-timeline .loading-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
color: #1f80f9;
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
#tab-timeline .loading-mask i {
}
#timeline-video-canvas {
background: rgba(0,0,0,0.9);
flex-grow: 1;
overflow: auto;
flex: 1;
}
#timeline-video-canvas video {
width: 100%;
height: 100%;
}
#timeline-video-canvas .timeline-video.col-md-4 {
height:30vh;
}
#timeline-video-canvas .timeline-video.col-md-6 {
height: 40vh;
}
#timeline-video-canvas .timeline-video.col-md-12 {
height: 80vh;
margin-bottom: 0.5rem !important;
}
#timeline-video-canvas .timeline-video:not(.no-video){
background-color: #000!important;
}
#timeline-video-canvas.show-only-playing .timeline-video.no-video{
display: none;
}
#timeline-info {
background: #004e8e;
}
#timeline-bottom-strip {
background: rgba(0,0,0,0.6);
height: 93px;
overflow: hidden;
}
#timeline-bottom-strip * {
transition: none;
}
#timeline-bottom-strip .vis-timeline {
visibility: visible!important;
border: 1px solid #2a4cb5;
border-radius: 5px;
}
#timeline-bottom-strip .vis-item {
opacity:0.4;
}
#timeline-bottom-strip .vis-time-axis .vis-grid.vis-minor {
border-color: rgb(31 128 249 / 40%);
}
#timeline-bottom-strip .vis-panel.vis-bottom,
#timeline-bottom-strip .vis-panel.vis-center,
#timeline-bottom-strip .vis-panel.vis-left,
#timeline-bottom-strip .vis-panel.vis-right,
#timeline-bottom-strip .vis-panel.vis-top {
border-color: rgb(31 128 249 / 40%)!important;
}

View File

@ -440,3 +440,10 @@ ul.squeeze {
.fixed-table-body { .fixed-table-body {
min-height: 400px; min-height: 400px;
} }
.flex-direction-column {
display: flex;
flex-direction: column;
height: 100vh;
}

View File

@ -18,9 +18,11 @@ var chartColors = {
purple: 'rgb(153, 102, 255)', purple: 'rgb(153, 102, 255)',
grey: 'rgb(201, 203, 207)' grey: 'rgb(201, 203, 207)'
}; };
var isAppleDevice = navigator.userAgent.match(/(iPod|iPhone|iPad)/)||(navigator.userAgent.match(/(Safari)/)&&!navigator.userAgent.match('Chrome')); var userAgent = navigator.userAgent;
var isAppleDevice = userAgent.match(/(iPod|iPhone|iPad)/)||(navigator.userAgent.match(/(Safari)/)&&!navigator.userAgent.match('Chrome'));
var isMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) var isMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4)) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))
var isChromiumBased = userAgent.includes('Chrome') && !userAgent.includes('Edg') && !userAgent.includes('OPR') || userAgent.includes('Brave');
var keyShortcuts = {} var keyShortcuts = {}
function base64ArrayBuffer(arrayBuffer) { function base64ArrayBuffer(arrayBuffer) {
var base64 = '' var base64 = ''
@ -73,6 +75,55 @@ function base64ArrayBuffer(arrayBuffer) {
return base64 return base64
} }
function timeAgo(date) {
const now = new Date();
const secondsPast = (now.getTime() - date.getTime()) / 1000;
if(secondsPast < 60) {
return parseInt(secondsPast) + ' seconds ago';
}
if(secondsPast < 3600) {
return parseInt(secondsPast / 60) + ' minutes ago';
}
if(secondsPast < 86400) {
return parseInt(secondsPast / 3600) + ' hours ago';
}
return parseInt(secondsPast / 86400) + ' days ago';
}
function getDayOfWeek(date) {
const days = [lang.Sunday, lang.Monday, lang.Tuesday, lang.Wednesday, lang.Thursday, lang.Friday, lang.Saturday];
return days[date.getDay()];
}
function stringToColor(str) {
let blueColors = [
'#0000FF', // Blue
'#00008B', // Dark Blue
'#0000CD', // Medium Blue
'#000080', // Navy
'#1E90FF', // Dodger Blue
'#4169E1', // Royal Blue
'#4682B4', // Steel Blue
'#5F9EA0', // Cadet Blue
'#6495ED', // Cornflower Blue
'#6A5ACD', // Slate Blue
'#7B68EE', // Medium Slate Blue
'#87CEEB', // Sky Blue
'#87CEFA', // Light Sky Blue
'#8A2BE2', // Blue Violet
'#ADD8E6' // Light Blue
];
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
return blueColors[Math.abs(hash) % blueColors.length];
}
function getTimeBetween(start, end, percent) {
const startDate = new Date(start);
const endDate = new Date(end);
const difference = endDate - startDate;
const time = new Date(startDate.getTime() + difference * percent / 100);
return time;
}
function stringContains(find,string,toLowerCase){ function stringContains(find,string,toLowerCase){
var newString = string + '' var newString = string + ''
if(toLowerCase)newString = newString.toLowerCase() if(toLowerCase)newString = newString.toLowerCase()
@ -1021,6 +1072,20 @@ function setSubmitButton(editorForm,text,icon,toggle){
var submitButtons = editorForm.find('[type="submit"]').prop('disabled',toggle) var submitButtons = editorForm.find('[type="submit"]').prop('disabled',toggle)
submitButtons.html(`<i class="fa fa-${icon}"></i> ${text}`) submitButtons.html(`<i class="fa fa-${icon}"></i> ${text}`)
} }
function featureIsActivated(showNotice){
if(userHasSubscribed){
return true
}else{
if(showNotice){
new PNotify({
title: lang.activationRequired,
text: lang.featureRequiresActivationText,
type: 'warning'
})
}
return false
}
}
$(document).ready(function(){ $(document).ready(function(){
onInitWebsocket(function(){ onInitWebsocket(function(){
loadMonitorsIntoMemory(function(data){ loadMonitorsIntoMemory(function(data){

View File

@ -0,0 +1,768 @@
$(document).ready(function(){
var theWindow = $('#tab-timeline')
var timeStripVideoCanvas = $('#timeline-video-canvas');
var timeStripEl = $('#timeline-bottom-strip');
var timeStripControls = $('#timeline-controls');
var timeStripInfo = $('#timeline-info');
var timeStripPreBuffers = $('#timeline-pre-buffers');
var timeStripObjectSearchInput = $('#timeline-video-object-search');
var dateSelector = $('#timeline-date-selector');
var sideMenuList = $(`#side-menu-link-timeline ul`)
var playToggles = timeStripControls.find('[timeline-action="playpause"]')
var speedButtons = timeStripControls.find('[timeline-action="speed"]')
var gridSizeButtons = timeStripControls.find('[timeline-action="gridSize"]')
var autoGridSizerButtons = timeStripControls.find('[timeline-action="autoGridSizer"]')
var currentTimeLabel = timeStripInfo.find('.current-time')
var timelineActionButtons = timeStripControls.find('[timeline-action]')
var timelineSpeed = 1;
var timelineGridSizing = `md-6`;
var timeStripVis = null;
var timeStripVisTick = null;
var timeStripVisItems = null;
var timeStripCurrentStart = null;
var timeStripCurrentEnd = null;
var timeStripVisTickMovementInterval = null;
var timeStripVisTickMovementIntervalSecond = null;
var timeStripHollowClickQueue = {}
var timeStripTickPosition = new Date()
var timeStripPreBuffersEls = {}
var timeStripItemColors = {}
var timeStripAutoGridSizer = false
var timeStripListOfQueries = []
var timeStripSelectedMonitors = []
var timeStripAutoScrollTimeout = null;
var timeStripAutoScrollPositionStart = null;
var timeStripAutoScrollPositionEnd = null;
var timeStripAutoScrollAmount = null;
var loadedVideosOnTimeStrip = []
var loadedVideosOnCanvas = {}
var loadedVideoElsOnCanvas = {}
var loadedVideoElsOnCanvasNextVideoTimeout = {}
var loadedVideoEndingTimeouts = {}
var isPlaying = false
var earliestStart = null
var latestEnd = null
var timeChanging = false
var dateRangeChanging = false
function setLoadingMask(turnOn){
if(turnOn){
if(theWindow.find('.loading-mask').length === 0){
var html = `<div class="loading-mask"><i class="fa fa-spinner fa-pulse fa-5x"></i></div>`
theWindow.prepend(html)
}
}else{
theWindow.find('.loading-mask').remove()
}
}
function addVideoBeforeAndAfter(videos) {
videos.sort((a, b) => {
if (a.mid === b.mid) {
return new Date(a.time) - new Date(b.time);
}
return a.mid.localeCompare(b.mid);
});
for (let i = 0; i < videos.length; i++) {
if (i > 0 && videos[i].mid === videos[i - 1].mid) {
videos[i].videoBefore = videos[i - 1];
} else {
videos[i].videoBefore = null;
}
if (i < videos.length - 1 && videos[i].mid === videos[i + 1].mid) {
videos[i].videoAfter = videos[i + 1];
} else {
videos[i].videoAfter = null;
}
}
return videos;
}
function findGapsInSearchRanges(timeRanges, range) {
timeRanges.sort((a, b) => a[0] - b[0]);
let gaps = [];
let currentEnd = new Date(range[0]);
for (let i = 0; i < timeRanges.length; i++) {
let [start, end] = timeRanges[i];
if (start > currentEnd) {
gaps.push([currentEnd, start]);
}
if (end > currentEnd) {
currentEnd = end;
}
}
if (currentEnd < range[1]) {
gaps.push([currentEnd, range[1]]);
}
return gaps;
}
async function getVideosInGaps(gaps,monitorIds){
var searchQuery = timeStripObjectSearchInput.val()
var videos = []
async function loopOnGaps(monitorId){
for (let i = 0; i < gaps.length; i++) {
var range = gaps[i]
videos.push(...(await getVideos({
monitorId,
startDate: range[0],
endDate: range[1],
searchQuery,
// archived: false,
// customVideoSet: wantCloudVideo ? 'cloudVideos' : null,
},null,true)).videos);
}
}
if(monitorIds && monitorIds.length > 0){
for (let ii = 0; ii < monitorIds.length; ii++) {
var monitorId = monitorIds[ii]
await loopOnGaps(monitorId)
}
}else{
await loopOnGaps('')
}
return videos;
}
async function getVideosByTimeStripRange(addOrOverWrite){
// timeStripSelectedMonitors = selected monitors
var currentVideosLength = parseInt(loadedVideosOnTimeStrip.length)
var stripDate = getTimestripDate()
var startDate = stripDate.start
var endDate = stripDate.end
var gaps = findGapsInSearchRanges(timeStripListOfQueries, [startDate,endDate])
// console.error([startDate,endDate])
// console.log('range : ',JSON.stringify([startDate,endDate]))
// console.log('timeRanges : ',JSON.stringify(timeStripListOfQueries))
// console.log('gaps : ',JSON.stringify(gaps))
if(gaps.length > 0){
setLoadingMask(true)
timeStripListOfQueries.push(...gaps)
var videos = await getVideosInGaps(gaps,timeStripSelectedMonitors)
videos = addVideoBeforeAndAfter(videos)
loadedVideosOnTimeStrip.push(...videos)
if(currentVideosLength !== loadedVideosOnTimeStrip.length)resetTimelineItems(loadedVideosOnTimeStrip);
setLoadingMask(false)
}
return loadedVideosOnTimeStrip
}
function selectVideosForCanvas(time, videos){
var selectedVideosByMonitorId = {}
$.each(loadedMonitors,function(n,monitor){
selectedVideosByMonitorId[monitor.mid] = null
})
var filteredVideos = videos.filter(video => {
var startTime = new Date(video.time);
var endTime = new Date(video.end);
return time >= startTime && time <= endTime;
});
$.each(filteredVideos,function(n,video){
selectedVideosByMonitorId[video.mid] = video;
})
return selectedVideosByMonitorId;
}
function drawVideosToCanvas(selectedVideosByMonitorId){
var html = ''
var preBufferHtml = ''
$.each(loadedMonitors,function(monitorId,monitor){
var itemColor = stringToColor(monitorId)
timeStripItemColors[monitorId] = itemColor
html += `<div class="timeline-video col-${timelineGridSizing} p-0 m-0 no-video" data-mid="${monitorId}" data-ke="${monitor.ke}" style="background-color:${itemColor}"></div>`
preBufferHtml += `<div class="timeline-video-buffer" data-mid="${monitorId}" data-ke="${monitor.ke}"></div>`
})
timeStripVideoCanvas.html(html)
timeStripPreBuffers.html(preBufferHtml)
$.each(selectedVideosByMonitorId,function(monitorId,video){
if(!video)return;
setVideoInCanvas(video)
})
}
function destroyTimeline(){
try{
timeStripVis.destroy()
}catch(err){
// console.log(err)
}
}
function formatVideosForTimeline(videos){
var i = 0;
var formattedVideos = (videos || []).map((item) => {
var blockColor = timeStripItemColors[item.mid];
++i;
return {
id: i,
content: ``,
style: `background-color: ${blockColor};border-color: ${blockColor}`,
start: item.time,
end: item.end,
group: 1
}
});
return formattedVideos
}
function createTimelineItems(){
var items = new vis.DataSet([]);
var groups = new vis.DataSet([
{id: 1, content: ''}
]);
timeStripVisItems = items
return {
items,
groups,
}
}
function resetTimelineItems(videos){
var newVideos = formatVideosForTimeline(videos)
timeStripVisItems.clear();
timeStripVisItems.add(newVideos);
}
async function resetTimeline(clickTime){
await getAndDrawVideosToTimeline(clickTime,true)
setTickDate(clickTime)
setTimeLabel(clickTime)
setTimeOfCanvasVideos(clickTime)
setHollowClickQueue()
}
function createTimeline(){
var timeChangingTimeout = null
var dateNow = new Date()
var hour = 1000 * 60 * 60
var startTimeForLoad = new Date(dateNow.getTime() - (hour * 24 + hour))
destroyTimeline()
var {
items,
groups,
} = createTimelineItems()
// make chart
timeStripVis = new vis.Timeline(timeStripEl[0], items, groups, {
showCurrentTime: false,
stack: false,
start: timeStripCurrentStart || startTimeForLoad,
end: timeStripCurrentEnd || dateNow,
});
// make tick
timeStripVisTick = timeStripVis.addCustomTime(dateNow, `${lang.Time}`);
timeStripVis.on('click', async function(properties) {
var currentlyPlaying = !!isPlaying;
timeStripPlay(true)
if(!timeChanging){
var clickTime = properties.time;
await resetTimeline(clickTime)
}
if(currentlyPlaying){
setTimeout(() => {
timeStripPlay()
},500)
}
});
timeStripVis.on('rangechange', function(properties){
timeChanging = true
})
timeStripVis.on('rangechanged', function(properties){
clearTimeout(timeChangingTimeout)
timeStripCurrentStart = properties.start;
timeStripCurrentEnd = properties.end;
timeStripAutoScrollPositionStart = getTimeBetween(timeStripCurrentStart,timeStripCurrentEnd,10);
timeStripAutoScrollPositionEnd = getTimeBetween(timeStripCurrentStart,timeStripCurrentEnd,90);
timeStripAutoScrollAmount = getTimelineScrollAmount(timeStripCurrentStart,timeStripCurrentEnd);
if(dateRangeChanging)return;
timeChangingTimeout = setTimeout(function(){
var clickTime = properties.time;
resetDateRangePicker()
setTimeout(() => {
timeChanging = false
getAndDrawVideosToTimeline(clickTime)
},500)
},300)
})
setTimeout(function(){
timeStripEl.find('.vis-timeline').resize()
},2000)
}
function getTimelineScrollAmount(start,end){
var startTime = start.getTime()
var endTime = end.getTime()
var difference = (endTime - startTime) / 1000;
var minute = 60
var hour = 60 * 60
var day = 60 * 60 * 24
// returns hours
if(difference <= 60){
return 0.1
}else if(difference > minute && difference <= hour){
return 0.3
}else if(difference > hour && difference < day){
return 0.6
}else if(difference >= day){
return 10
}
}
function scrollTimeline(addHours){
if(timeStripAutoScrollTimeout)return;
timeStripAutoScrollTimeout = setTimeout(() => {
delete(timeStripAutoScrollTimeout)
timeStripAutoScrollTimeout = null
},2000)
var stripTime = getTimestripDate()
var timeToAdd = addHours * 1000 * 60 * 60
var start = new Date(stripTime.start.getTime() + timeToAdd)
var end = new Date(stripTime.end.getTime() + timeToAdd)
setTimestripDate(start,end)
}
function setTickDate(newDate){
if(isPlaying){
if(newDate >= timeStripAutoScrollPositionEnd){
scrollTimeline(timeStripAutoScrollAmount)
}else if(newDate >= new Date()){
timeStripPlay(true)
}
}
timeStripTickPosition = new Date(newDate)
return timeStripVis.setCustomTime(newDate, timeStripVisTick);
}
function setTimeLabel(newDate){
return currentTimeLabel.text(`${timeAgo(newDate)}, ${getDayOfWeek(newDate)}, ${formattedTime(newDate)}`)
}
function getTickDate() {
return timeStripTickPosition;
}
function getTimestripDate() {
var visibleWindow = timeStripVis.getWindow();
var start = visibleWindow.start;
var end = visibleWindow.end;
return {
start,
end
};
}
function setTimestripDate(newStart, newEnd) {
return timeStripVis.setWindow(newStart, newEnd);
}
function selectAndDrawVideosToCanvas(theTime,redrawVideos){
var selectedVideosForTime = selectVideosForCanvas(theTime,loadedVideosOnTimeStrip)
loadedVideosOnCanvas = selectedVideosForTime;
if(redrawVideos){
drawVideosToCanvas(selectedVideosForTime)
}
}
async function getAndDrawVideosToTimeline(theTime,redrawVideos){
await getVideosByTimeStripRange()
selectAndDrawVideosToCanvas(theTime,redrawVideos)
}
function getVideoContainerInCanvas(video){
return timeStripVideoCanvas.find(`[data-mid="${video.mid}"][data-ke="${video.ke}"]`)
}
function getVideoElInCanvas(video){
return getVideoContainerInCanvas(video).find('video')[0]
}
function getVideoContainerPreBufferEl(video){
return timeStripPreBuffers.find(`[data-mid="${video.mid}"][data-ke="${video.ke}"]`)
}
function getWaitTimeUntilNextVideo(endTimeOfFirstVideo,startTimeOfNextVideo){
return (new Date(startTimeOfNextVideo).getTime() - new Date(endTimeOfFirstVideo).getTime()) / timelineSpeed
}
function clearVideoInCanvas(oldVideo){
var monitorId = oldVideo.mid
loadedVideosOnCanvas[monitorId] = null
loadedVideoElsOnCanvas[monitorId] = null
clearTimeout(loadedVideoEndingTimeouts[monitorId])
var container = getVideoContainerInCanvas(oldVideo).addClass('no-video')
var videoEl = container.find('video')
videoEl.attr('src','')
try{
videoEl[0].pause()
}catch(err){
console.log(err)
}
container.empty()
timeStripAutoGridResize()
}
function setVideoInCanvas(newVideo){
var monitorId = newVideo.mid
getVideoContainerInCanvas(newVideo).removeClass('no-video').html(`<video muted src="${newVideo.href}"></video>`)
var vidEl = getVideoElInCanvas(newVideo)
vidEl.playbackRate = timelineSpeed
if(isPlaying)playVideo(vidEl)
loadedVideoElsOnCanvas[monitorId] = vidEl
loadedVideosOnCanvas[monitorId] = newVideo
timeStripPreBuffersEls[monitorId] = getVideoContainerPreBufferEl(newVideo)
queueNextVideo(newVideo)
timeStripAutoGridResize()
}
function setTimeOfCanvasVideos(newTime){
$.each(loadedVideosOnCanvas,function(n,video){
if(!video)return;
var monitorId = video.mid
var timeAfterStart = (newTime - new Date(video.time)) / 1000;
var videoEl = loadedVideoElsOnCanvas[monitorId]
videoEl.currentTime = timeAfterStart
// playVideo(videoEl)
// pauseVideo(videoEl)
})
}
function queueNextVideo(video){
if(!video)return;
var monitorId = video.mid
var videoEl = loadedVideoElsOnCanvas[monitorId]
var videoAfter = video.videoAfter
var endingTimeout = null;
var alreadyDone = false;
function currentVideoIsOver(){
if(alreadyDone)return;
alreadyDone = true;
clearVideoInCanvas(video)
if(videoAfter){
var waitTimeTimeTillNext = getWaitTimeUntilNextVideo(video.end,videoAfter.time)
// console.log('End of Video',video)
// console.log('Video After',videoAfter)
// console.log('Starting in ',waitTimeTimeTillNext / 1000, 'seconds')
loadedVideoElsOnCanvasNextVideoTimeout[monitorId] = setTimeout(() => {
setVideoInCanvas(videoAfter)
},waitTimeTimeTillNext)
// }else{
// console.log('End of Timeline for Monitor',loadedMonitors[monitorId].name)
}
}
videoEl.onerror = function(err){
err.preventDefault()
console.error(`video error`)
console.error(err)
}
videoEl.ontimeupdate = function(){
if(videoEl.currentTime >= videoEl.duration){
clearTimeout(loadedVideoEndingTimeouts[monitorId])
currentVideoIsOver()
}else if(isPlaying){
var theTime = getTickDate()
var waitTimeTimeTillNext = getWaitTimeUntilNextVideo(theTime,video.end)
clearTimeout(loadedVideoEndingTimeouts[monitorId])
loadedVideoEndingTimeouts[monitorId] = setTimeout(() => {
currentVideoIsOver()
},waitTimeTimeTillNext)
}
}
// pre-buffer it
timeStripPreBuffersEls[monitorId].html(videoAfter ? `<video preload="auto" muted src="${videoAfter.href}"></video>` : '')
}
function findVideoAfterTime(time, monitorId) {
let inputTime = new Date(time);
let matchingVideos = loadedVideosOnTimeStrip.filter(video => {
let videoTime = new Date(video.time);
return video.mid === monitorId && videoTime > inputTime;
});
matchingVideos.sort((a, b) => new Date(a.time) - new Date(b.time));
return matchingVideos.length > 0 ? matchingVideos[0] : null;
}
function setHollowClickQueue(){
$.each(loadedVideosOnCanvas,function(monitorId,video){
if(!video){
// console.log(`Add Hollow Action`, loadedMonitors[monitorId].name)
var tickTime = getTickDate()
var foundVideo = findVideoAfterTime(tickTime,monitorId)
clearTimeout(loadedVideoElsOnCanvasNextVideoTimeout[monitorId])
if(foundVideo){
var waitTimeTimeTillNext = getWaitTimeUntilNextVideo(tickTime,foundVideo.time)
// console.log('Found Video',foundVideo)
// console.log('Video Starts in ',waitTimeTimeTillNext / 1000, 'seconds after Play')
timeStripHollowClickQueue[monitorId] = () => {
// console.log('Hollow Start Point for',loadedMonitors[monitorId].name)
loadedVideoElsOnCanvasNextVideoTimeout[monitorId] = setTimeout(() => {
// console.log('Hollow Replace')
setVideoInCanvas(foundVideo)
},waitTimeTimeTillNext)
}
}else{
// console.log('End of Timeline for Monitor',loadedMonitors[monitorId].name)
timeStripHollowClickQueue[monitorId] = () => {}
}
}else{
timeStripHollowClickQueue[monitorId] = () => {}
}
})
}
function runHollowClickQueues(){
$.each(timeStripHollowClickQueue,function(monitorId,theAction){
theAction()
})
}
function getAllActiveVideosInSlots(){
return timeStripVideoCanvas.find('video')
}
function playVideo(videoEl){
try{
videoEl.playbackRate = timelineSpeed
videoEl.play()
}catch(err){
console.log(err)
}
}
function pauseVideo(videoEl){
try{
videoEl.pause()
}catch(err){
console.log(err)
}
}
function playAllVideos(){
getAllActiveVideosInSlots().each(function(n,video){
playVideo(video)
})
}
function pauseAllVideos(){
getAllActiveVideosInSlots().each(function(n,video){
pauseVideo(video)
})
}
function setPlayToggleUI(icon){
playToggles.html(`<i class="fa fa-${icon}"></i>`)
}
function timeStripPlay(forcePause){
if(!forcePause && !isPlaying){
isPlaying = true
var currentDate = getTickDate().getTime();
var msSpeed = 50
var addition = 0
var newTime
runHollowClickQueues()
playAllVideos()
timeStripVisTickMovementInterval = setInterval(function() {
addition += (msSpeed * timelineSpeed);
newTime = new Date(currentDate + addition)
setTickDate(newTime);
// setTimeOfCanvasVideos(newTime)
}, msSpeed)
timeStripVisTickMovementIntervalSecond = setInterval(function() {
setTimeLabel(newTime);
}, 1000)
setPlayToggleUI(`pause-circle-o`)
}else{
isPlaying = false
pauseAllVideos()
clearInterval(timeStripVisTickMovementInterval)
clearInterval(timeStripVisTickMovementIntervalSecond)
$.each(loadedVideoElsOnCanvasNextVideoTimeout,function(n,timeout){
clearTimeout(timeout)
})
$.each(loadedVideoEndingTimeouts,function(n,timeout){
clearTimeout(timeout)
})
setPlayToggleUI(`play-circle-o`)
}
}
// function downloadPlayingVideo(video){
// if(video.currentSrc){
// var filename = getFilenameFromUrl(video.currentSrc)
// downloadFile(video.currentSrc,filename)
// }
// }
function getLoadedVideosOnCanvas(){
return Object.values(loadedVideosOnCanvas).filter(item => !!item)
}
function downloadAllPlayingVideos(){
zipVideosAndDownloadWithConfirm(getLoadedVideosOnCanvas())
}
async function jumpTimeline(amountInMs,direction){
timeStripPlay(true)
var tickTime = getTickDate().getTime()
var newTime = 0;
if(direction === 'right'){
newTime = tickTime + amountInMs
}else{
newTime = tickTime - amountInMs
}
newTime = new Date(newTime)
await resetTimeline(newTime)
if(tickTime <= timeStripAutoScrollPositionStart){
scrollTimeline(-timeStripAutoScrollAmount)
}else if(tickTime >= timeStripAutoScrollPositionEnd){
scrollTimeline(timeStripAutoScrollAmount)
}
}
function adjustTimelineSpeed(newSpeed){
var currentlyPlaying = !!isPlaying;
if(currentlyPlaying)timeStripPlay(true);
timelineSpeed = newSpeed + 0
setHollowClickQueue()
if(currentlyPlaying)timeStripPlay();
}
function adjustTimelineGridSize(newCol){
timelineGridSizing = `${newCol}`
var containerEls = timeStripVideoCanvas.find('.timeline-video')
containerEls.removeClass (function (index, className) {
return (className.match (/(^|\s)col-\S+/g) || []).join(' ');
}).addClass(`col-${newCol}`)
gridSizeButtons.removeClass('btn-success')
timeStripControls.find(`[timeline-action="gridSize"][size="${newCol}"]`).addClass('btn-success')
}
async function refreshTimeline(){
var currentlyPlaying = !!isPlaying;
timeStripPlay(true)
timeStripListOfQueries = []
loadedVideosOnTimeStrip = []
createTimeline()
await resetTimeline(getTickDate())
if(currentlyPlaying)timeStripPlay();
}
function timeStripAutoGridSizerToggle(){
if(timeStripAutoGridSizer){
timeStripAutoGridSizer = false
autoGridSizerButtons.removeClass('btn-success')
dashboardOptions('timeStripAutoGridSizer','2')
}else{
timeStripAutoGridSizer = true
autoGridSizerButtons.addClass('btn-success')
timeStripAutoGridResize()
dashboardOptions('timeStripAutoGridSizer','1')
}
}
function timeStripAutoGridResize(){
if(!timeStripAutoGridSizer)return;
var numberOfBlocks = getLoadedVideosOnCanvas().length
if(numberOfBlocks <= 1){
adjustTimelineGridSize(`md-12`)
}else if(numberOfBlocks >= 2 && numberOfBlocks < 5){
adjustTimelineGridSize(`md-6`)
}else if(numberOfBlocks >= 5){
adjustTimelineGridSize(`md-4`)
}
}
function resetDateRangePicker(){
var stripDate = getTimestripDate()
var startDate = stripDate.start
var endDate = stripDate.end
var picker = dateSelector.data('daterangepicker')
picker.setStartDate(startDate);
picker.setEndDate(endDate);
}
function drawFoundCamerasSubMenu(){
var tags = getListOfTagsFromMonitors()
var allFound = [
{
attributes: `timeline-menu-action="selectMonitorGroup" tag=""`,
class: `cursor-pointer`,
color: 'green',
label: lang['All Monitors'],
}
]
$.each(tags,function(tag,monitors){
allFound.push({
attributes: `timeline-menu-action="selectMonitorGroup" tag="${tag}"`,
class: `cursor-pointer`,
color: 'blue',
label: tag,
})
})
if(allFound.length === 1){
allFound.push({
attributes: ``,
class: ``,
color: ' d-none',
label: `<small class="mt-1">${lang.addTagText}</small>`,
})
}
var html = buildSubMenuItems(allFound)
sideMenuList.html(html)
}
sideMenuList.on('click','[timeline-menu-action]',function(){
var el = $(this)
var type = el.attr('timeline-menu-action')
switch(type){
case'selectMonitorGroup':
var tag = el.attr('tag')
if(!tag){
timeStripSelectedMonitors = []
}else{
var tags = getListOfTagsFromMonitors()
var monitorIds = tags[tag]
timeStripSelectedMonitors = [...monitorIds];
}
refreshTimeline()
break;
}
})
timelineActionButtons.click(function(){
var el = $(this)
var type = el.attr('timeline-action')
switch(type){
case'playpause':
timeStripPlay()
break;
case'downloadAll':
if(featureIsActivated(true)){
downloadAllPlayingVideos()
}
break;
case'jumpLeft':
jumpTimeline(5000,'left')
break;
case'jumpRight':
jumpTimeline(5000,'right')
break;
case'speed':
var speed = parseInt(el.attr('speed'))
if(featureIsActivated(true)){
adjustTimelineSpeed(speed)
speedButtons.removeClass('btn-success')
el.addClass('btn-success')
}
break;
case'gridSize':
var size = el.attr('size')
adjustTimelineGridSize(size)
break;
case'refresh':
refreshTimeline()
break;
case'autoGridSizer':
timeStripAutoGridSizerToggle()
break;
}
})
timeStripObjectSearchInput.change(function(){
refreshTimeline()
})
timeStripVideoCanvas.on('dblclick','.timeline-video',function(){
var monitorId = $(this).attr('data-mid')
openVideosTableView(monitorId)
})
addOnTabOpen('timeline', async function () {
createTimeline()
await resetTimeline(getTickDate())
drawFoundCamerasSubMenu()
})
addOnTabReopen('timeline', function () {
drawFoundCamerasSubMenu()
})
addOnTabAway('timeline', function () {
timeStripPlay(true)
})
loadDateRangePicker(dateSelector,{
timePicker: true,
timePicker24Hour: true,
timePickerSeconds: true,
timePickerIncrement: 30,
autoApply: true,
buttonClasses: 'hidden',
drops: 'up',
maxDate: new Date(),
onChange: function(start, end, label) {
if(!timeChanging){
setLoadingMask(true)
dateRangeChanging = true
setTimestripDate(start, end)
var newTickPosition = getTimeBetween(start,end,50);
setTickDate(newTickPosition)
setTimeout(() => {
dateRangeChanging = false
refreshTimeline()
},2000)
}
}
})
var currentOptions = dashboardOptions()
if(isChromiumBased){
[ 7, 10 ].forEach((speed) => {
timeStripControls.find(`[timeline-action="speed"][speed="${speed}"]`).remove()
});
}
if(!currentOptions.timeStripAutoGridSizer || currentOptions.timeStripAutoGridSizer === '1'){
timeStripAutoGridSizerToggle()
}
})

View File

@ -408,7 +408,7 @@ function loadEventsData(videoEvents){
loadedEventsInMemory[`${anEvent.mid}${anEvent.time}`] = anEvent loadedEventsInMemory[`${anEvent.mid}${anEvent.time}`] = anEvent
}) })
} }
function getVideos(options,callback){ function getVideos(options,callback,noEvents){
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
options = options ? options : {} options = options ? options : {}
var searchQuery = options.searchQuery var searchQuery = options.searchQuery
@ -444,7 +444,7 @@ function getVideos(options,callback){
}) })
}) })
$.getJSON(`${getApiPrefix(`timelapse`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(timelapseFrames){ $.getJSON(`${getApiPrefix(`timelapse`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(timelapseFrames){
$.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`limit=${eventLimit}`]).join('&')}`,function(eventData){ function completeRequest(eventData){
var theEvents = eventData.events || eventData; var theEvents = eventData.events || eventData;
var newVideos = applyDataListToVideos(videos,theEvents) var newVideos = applyDataListToVideos(videos,theEvents)
newVideos = applyTimelapseFramesListToVideos(newVideos,timelapseFrames.frames || timelapseFrames,'timelapseFrames',true).map((video) => { newVideos = applyTimelapseFramesListToVideos(newVideos,timelapseFrames.frames || timelapseFrames,'timelapseFrames',true).map((video) => {
@ -455,7 +455,14 @@ function getVideos(options,callback){
loadVideosData(newVideos) loadVideosData(newVideos)
if(callback)callback({videos: newVideos, frames: timelapseFrames}); if(callback)callback({videos: newVideos, frames: timelapseFrames});
resolve({videos: newVideos, frames: timelapseFrames}) resolve({videos: newVideos, frames: timelapseFrames})
}) }
if(noEvents){
completeRequest([])
}else{
$.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`limit=${eventLimit}`]).join('&')}`,function(eventData){
completeRequest(eventData)
})
}
}) })
}) })
}) })

View File

@ -37,8 +37,11 @@ $(document).ready(function(e){
imgBlock.find('img').attr('src',href) imgBlock.find('img').attr('src',href)
}) })
} }
function openVideosTableView(monitorId,startDate,endDate){ window.openVideosTableView = function(monitorId){
drawVideosTableViewElements(monitorId,startDate,endDate) openTab(`videosTableView`,{})
drawMonitorListToSelector(monitorsList,null,null,true)
monitorsList.val(monitorId)
drawVideosTableViewElements()
} }
loadDateRangePicker(dateSelector,{ loadDateRangePicker(dateSelector,{
onChange: function(start, end, label) { onChange: function(start, end, label) {
@ -242,11 +245,7 @@ $(document).ready(function(e){
.on('click','.open-videosTable',function(e){ .on('click','.open-videosTable',function(e){
e.preventDefault() e.preventDefault()
var monitorId = getRowsMonitorId(this) var monitorId = getRowsMonitorId(this)
openTab(`videosTableView`,{},null,null,null,() => { openVideosTableView()
drawMonitorListToSelector(monitorsList,null,null,true)
monitorsList.val(monitorId)
drawVideosTableViewElements()
})
return false; return false;
}); });
sideLinkListBox sideLinkListBox

View File

@ -0,0 +1,21 @@
<main class="page-tab" id="tab-timeline" style="position:relative;margin-right:-1.5rem;margin-left:-1.5rem;">
<div class="dark">
<%
var drawBlock
var buildOptions
%>
<%
include fieldBuilders.ejs
%>
<%
var pageName = 'Timeline';
Object.keys(define[pageName].blocks).forEach(function(blockKey){
var pageLayout = define[pageName].blocks[blockKey]
drawBlock(pageLayout)
})
%>
</div>
<link rel="stylesheet" href="<%-window.libURL%>assets/css/bs5.timeline.css" />
<script src="<%-window.libURL%>assets/js/bs5.timeline.js"></script>
</main>