$(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 = `
` 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 += `
` preBufferHtml += `
` }) 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(``) 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 ? `` : '') } 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(``) } 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: `${lang.addTagText}`, }) } 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() } })