(Super) Timeline, Power Viewer v10
parent
17f535b9ec
commit
b4566ca886
|
@ -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",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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(',')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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));
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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){
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
})
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue