var loadedVideosInMemory = {} var loadedFramesMemory = {} var loadedFramesMemoryTimeout = {} function getLocalTimelapseImageLink(imageUrl){ if(loadedFramesMemory[imageUrl]){ return loadedFramesMemory[imageUrl] }else{ return new Promise((resolve,reject) => { fetch(imageUrl) .then(res => res.blob()) // Gets the response and returns it as a blob .then(blob => { var objectURL = URL.createObjectURL(blob); loadedFramesMemory[imageUrl] = objectURL clearTimeout(loadedFramesMemoryTimeout[imageUrl]) loadedFramesMemoryTimeout[imageUrl] = setTimeout(function(){ URL.revokeObjectURL(objectURL) delete(loadedFramesMemory[imageUrl]) delete(loadedFramesMemoryTimeout[imageUrl]) },1000 * 60 * 10) resolve(objectURL) }); }) } } async function preloadAllTimelapseFramesToMemoryFromVideoList(framesSortedByDays){ async function syncWait(waitTime){ return new Promise((resolve,reject) => { setTimeout(function(){ resolve() },waitTime) }) } for (let ii = 0; ii < framesSortedByDays.length; ii++) { var frame = framesSortedByDays[ii] console.log ('Loading... ',frame.href) await syncWait(50) await getLocalTimelapseImageLink(frame.href) console.log ('Loaded! ',frame.href) } } function createVideoLinks(video){ var details = safeJsonParse(video.details) var queryString = [] // if(details.isUTC === true){ // queryString.push('isUTC=true') // }else{ // video.time = s.utcToLocal(video.time) // video.end = s.utcToLocal(video.end) // } if(queryString.length > 0){ queryString = '?' + queryString.join('&') }else{ queryString = '' } video.ext = video.ext ? video.ext : 'mp4' if(details.type === 'googd'){ video.href = undefined }else if(!video.ext && video.href){ video.ext = video.href.split('.') video.ext = video.ext[video.ext.length - 1] } video.filename = formattedTimeForFilename(video.time,null,`YYYY-MM-DDTHH-mm-ss`) + '.' + video.ext; var href = getApiPrefix('videos') + '/'+video.mid+'/'+video.filename; video.actionUrl = href video.links = { deleteVideo : href+'/delete' + queryString, changeToUnread : href+'/status/1' + queryString, changeToRead : href+'/status/2' + queryString } if(!video.href || options.hideRemote === true)video.href = href + queryString video.details = details return video } function applyDataListToVideos(videos,events,keyName,reverseList){ var updatedVideos = videos.concat([]) var currentEvents = events.concat([]) updatedVideos.forEach(function(video){ var videoEvents = [] currentEvents.forEach(function(theEvent,index){ var startTime = new Date(video.time) var endTime = new Date(video.end) var eventTime = new Date(theEvent.time) if(theEvent.mid === video.mid && eventTime >= startTime && eventTime <= endTime){ videoEvents.push(theEvent) currentEvents.splice(index, 1) } }) if(reverseList)videoEvents = videoEvents.reverse() video[keyName || 'events'] = videoEvents }) return updatedVideos } function applyTimelapseFramesListToVideos(videos,events,keyName,reverseList){ var thisApiPrefix = getApiPrefix() + '/timelapse/' + $user.ke + '/' var newVideos = applyDataListToVideos(videos,events,keyName,reverseList) newVideos.forEach(function(video){ video.timelapseFrames.forEach(function(row){ var apiURL = thisApiPrefix + row.mid row.href = libURL + apiURL + '/' + row.filename.split('T')[0] + '/' + row.filename }) }) return newVideos } function getFrameOnVideoRow(percentageInward,video){ var startTime = new Date(video.time) var endTime = new Date(video.end) var timeDifference = endTime - startTime var timeInward = timeDifference / (100 / percentageInward) var timeAdded = new Date(startTime.getTime() + timeInward) // ms var frames = video.timelapseFrames || [] var foundFrame = frames.length === 1 ? frames[0] : frames.find(function(row){ return new Date(row.time) >= timeAdded }); return { timeInward: timeInward, foundFrame: foundFrame, timeAdded: timeAdded, } } function getVideoFromDay(percentageInward,videos){ var startTime = new Date(videos[0].time) var endTime = new Date(videos[videos.length - 1].end) var timeDifference = endTime - startTime var timeInward = timeDifference / (100 / percentageInward) var timeAdded = new Date(startTime.getTime() + timeInward) // ms var foundVideo = ([]).concat(videos).reverse().find(function(row){ return new Date(timeAdded - 1000) >= new Date(row.time) }); return foundVideo } function bindFrameFindingByMouseMove(createdCardCarrier,video){ var createdCardElement = createdCardCarrier.find('.video-time-card').first() var timeImg = createdCardElement.find('.video-time-img') var timeStrip = createdCardElement.find('.video-time-strip') var timeNeedleSeeker = createdCardElement.find('.video-time-needle-seeker') if(video.timelapseFrames.length > 0){ createdCardElement.on('mousemove',function(evt){ var offest = createdCardElement.offset() var elementWidth = createdCardElement.width() + 2 var amountMoved = evt.pageX - offest.left var percentMoved = amountMoved / elementWidth * 100 percentMoved = percentMoved > 100 ? 100 : percentMoved < 0 ? 0 : percentMoved var frameFound = getFrameOnVideoRow(percentMoved,video).frameFound if(frameFound){ timeImg.css('background-image',`url(${frameFound.href})`) } timeNeedleSeeker.css('left',`${amountMoved}px`) }) timeImg.css('background-image',`url(${getFrameOnVideoRow(1,video).frameFound.href})`) }else{ if(video.events.length === 0){ timeStrip.hide() }else{ var eventMatrixHtml = `` var objectsFound = {} eventMatrixHtml += ` ` $.each(([]).concat(video.events).splice(0,11),function(n,theEvent){ var imagePath = `${formattedTimeForFilename(theEvent.time,false,'YYYY-MM-DD')}/${formattedTimeForFilename(theEvent.time,false,'YYYY-MM-DDTHH-mm-ss')}.jpg` possibleEventFrames += `
` }) $.each(video.events,function(n,theEvent){ $.each(theEvent.details.matrices,function(n,matrix){ if(!objectsFound[matrix.tag])objectsFound[matrix.tag] = 1 ++objectsFound[matrix.tag] }) }) $.each(objectsFound,function(tag,count){ eventMatrixHtml += `` }) eventMatrixHtml += `
${lang.Events} ${video.events.length}
${tag} ${count}
` timeStrip.append(eventMatrixHtml) } timeImg.remove() } } function bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,videos,allFrames){ var createdCardElement = createdCardCarrier.find('.video-time-card') var timeImg = createdCardElement.find('.video-time-img') var rowHeader = createdCardElement.find('.video-time-header') var timeStrip = createdCardElement.find('.video-time-strip') var timeNeedleSeeker = createdCardElement.find('.video-time-needle-seeker') var dayStart = videos[0].time var dayEnd = videos[videos.length - 1].end var firstFrameOfDay = null $.each(videos,function(day,video){ $.each(video.timelapseFrames,function(day,frame){ if(!firstFrameOfDay)firstFrameOfDay = frame; }) }) if(!firstFrameOfDay){ timeImg.remove() rowHeader.css('position','initial') }else{ timeImg.attr('src',firstFrameOfDay.href) } var videoSlices = createdCardElement.find('.video-day-slice') var videoTimeLabel = createdCardElement.find('.video-time-label') var currentlySelected = null var currentlySelectedFrame = null createdCardElement.on('mousemove',function(evt){ var offest = createdCardElement.offset() var elementWidth = createdCardElement.width() + 2 var amountMoved = evt.pageX - offest.left var percentMoved = amountMoved / elementWidth * 100 percentMoved = percentMoved > 100 ? 100 : percentMoved < 0 ? 0 : percentMoved var videoFound = getVideoFromDay(percentMoved,videos) createdCardElement.find(`[data-time]`).css('background-color','') if(videoFound){ // var videoSlice = createdCardElement.find(`[data-time="${videoFound.time}"]`).css('background-color','rgba(255,255,255,0.3)') // var videoSliceOffest = videoSlice.offset() // var videoSliceElementWidth = videoSlice.width() // var videoSliceAmountMoved = evt.pageX - videoSliceOffest.left // var videoSlicePercentMoved = videoSliceAmountMoved / videoSliceElementWidth * 100 // videoSlicePercentMoved = videoSlicePercentMoved > 100 ? 100 : videoSlicePercentMoved < 0 ? 0 : videoSlicePercentMoved if(currentlySelected && currentlySelected.time !== videoFound.time){ timeNeedleSeeker.attr('video-time-seeked-video-position',videoFound.time) } currentlySelected = Object.assign({},videoFound) } // draw frame var result = getFrameOnVideoRow(percentMoved,{ time: videos[0].time, end: videos[videos.length - 1].end, timelapseFrames: allFrames, }) var frameFound = result.foundFrame videoTimeLabel.text(formattedTime(result.timeAdded,'hh:mm:ss AA, DD-MM-YYYY')) if(frameFound){ currentlySelectedFrame = Object.assign({},frameFound) setTimeout(async function(){ var frameUrl = await getLocalTimelapseImageLink(frameFound.href) if(currentlySelectedFrame.time === frameFound.time)timeImg.attr('src',frameUrl); },1) } timeNeedleSeeker.attr('video-slice-seeked',result.timeInward).css('left',`${percentMoved}%`) }) } function getPercentOfTimePositionFromVideo(video,theEvent){ var startTime = new Date(video.time) var endTime = new Date(video.end) var eventTime = new Date(theEvent.time) var rangeMax = endTime - startTime var eventMs = eventTime - startTime var percentChanged = eventMs / rangeMax * 100 return percentChanged } function createVideoRow(row,classOverride){ var eventMatrixHtml = `` if(row.events && row.events.length > 0){ $.each(row.events,function(n,theEvent){ var leftPercent = getPercentOfTimePositionFromVideo(row,theEvent) eventMatrixHtml += `
` }) } var videoEndpoint = getLocation() + '/' + $user.auth_token + '/videos/' + $user.ke + '/' + row.mid + '/' + row.filename return `
${loadedMonitors[row.mid] ? loadedMonitors[row.mid].name : row.mid}
${moment(row.time).fromNow()}
~${durationBetweenTimes(row.time,row.end)} ${lang.Minutes}
${lang.Started} : ${formattedTime(row.time,true)}
${lang.Ended} : ${formattedTime(row.end,true)}
` } function sortVideosByDays(videos){ var days = {} videos.forEach(function(video){ var videoTime = new Date(video.time) var theDayKey = `${videoTime.getDate()}-${videoTime.getMonth()}-${videoTime.getFullYear()}` if(!days[video.mid])days[video.mid] = {}; if(!days[video.mid][theDayKey])days[video.mid][theDayKey] = []; days[video.mid][theDayKey].push(video) }) return days } function sortFramesByDays(frames){ var days = {} var thisApiPrefix = getApiPrefix() + '/timelapse/' + $user.ke + '/' frames.forEach(function(frame){ var frameTime = new Date(frame.time) var theDayKey = `${frameTime.getDate()}-${frameTime.getMonth()}-${frameTime.getFullYear()}` if(!days[frame.mid])days[frame.mid] = {}; if(!days[frame.mid][theDayKey])days[frame.mid][theDayKey] = []; var apiURL = thisApiPrefix + frame.mid frame.href = libURL + apiURL + '/' + frame.filename.split('T')[0] + '/' + frame.filename days[frame.mid][theDayKey].push(frame) }) console.log(days) return days } function getVideoPercentWidthForDay(row,videos){ var startTime = new Date(row.time) var endTime = new Date(row.end) var timeDifference = endTime - startTime var stripStartTime = new Date(videos[0].time) var stripEndTime = new Date(videos[videos.length - 1].end) var stripTimeDifference = stripEndTime - stripStartTime var percent = (timeDifference / stripTimeDifference) * 100 return percent } function createDayCard(videos,dayKey,monitorId,classOverride){ var html = '' var eventMatrixHtml = `` var dayParts = dayKey.split('-') var day = dayParts[0] var month = dayParts[1] var year = dayParts[2] $.each(videos,function(n,row){ var nextRow = videos[n + 1] if(nextRow)console.log({time: row.end, end: nextRow.time},n + 1) var marginRight = !!nextRow ? getVideoPercentWidthForDay({time: row.end, end: nextRow.time},videos) : 0; eventMatrixHtml += `
` if(row.events && row.events.length > 0){ $.each(row.events,function(n,theEvent){ var leftPercent = getPercentOfTimePositionFromVideo(row,theEvent) eventMatrixHtml += `
` }) } eventMatrixHtml += `
` eventMatrixHtml += `
` }) html += `
${loadedMonitors[monitorId] ? loadedMonitors[monitorId].name : monitorId}
${formattedTime(videos[0].time)} to ${formattedTime(videos[videos.length - 1].end)}
${day}
${month}, ${year}
` return html } function drawVideoRowsToList(targetElement,rows){ var theVideoList = $(targetElement) theVideoList.empty() $.each(rows,function(n,row){ theVideoList.append(createVideoRow(row)) }) liveStamp() } function loadVideoData(video){ delete(video.f) loadedVideosInMemory[`${video.mid}${video.time}`] = video } function getVideos(options,callback){ options = options ? options : {} var requestQueries = [] var monitorId = options.monitorId var limit = options.limit || 5000 var eventStartTime var eventEndTime // var startDate = options.startDate // var endDate = options.endDate if(options.startDate){ eventStartTime = formattedTimeForFilename(options.startDate,false) requestQueries.push(`start=${eventStartTime}`) } if(options.endDate){ eventEndTime = formattedTimeForFilename(options.endDate,false) requestQueries.push(`end=${eventEndTime}`) } $.getJSON(`${getApiPrefix(`videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(data){ var videos = data.videos $.getJSON(`${getApiPrefix(`timelapse`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(timelapseFrames){ $.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(eventData){ var newVideos = applyDataListToVideos(videos,eventData) newVideos = applyTimelapseFramesListToVideos(newVideos,timelapseFrames,'timelapseFrames',true) $.each(newVideos,function(n,video){ loadVideoData(video) }) callback({videos: newVideos, frames: timelapseFrames}) }) }) }) } function getEvents(options,callback){ options = options ? options : {} var requestQueries = [] var monitorId = options.monitorId var limit = options.limit || 5000 var eventStartTime var eventEndTime // var startDate = options.startDate // var endDate = options.endDate if(options.startDate){ eventStartTime = formattedTimeForFilename(options.startDate,false) requestQueries.push(`start=${eventStartTime}`) } if(options.endDate){ eventEndTime = formattedTimeForFilename(options.endDate,false) requestQueries.push(`end=${eventEndTime}`) } if(options.onlyCount){ requestQueries.push(`onlyCount=1`) } $.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.join('&')}`,function(eventData){ callback(eventData) }) } onWebSocketEvent(function(d){ switch(d.f){ case'video_delete': $('[file="'+d.filename+'"][mid="'+d.mid+'"]:not(.modal)').remove(); $('[data-file="'+d.filename+'"][data-mid="'+d.mid+'"]:not(.modal)').remove(); $('[data-time-formed="'+(new Date(d.time))+'"][data-mid="'+d.mid+'"]:not(.modal)').remove(); var videoPlayerId = getVideoPlayerTabId(d) deleteTab(videoPlayerId) // if($.powerVideoWindow.currentDataObject&&$.powerVideoWindow.currentDataObject[d.filename]){ // delete($.timelapse.currentVideos[$.powerVideoWindow.currentDataObject[d.filename].position]) // $.powerVideoWindow.drawTimeline(false) // } // if($.timelapse.currentVideos&&$.timelapse.currentVideos[d.filename]){ // delete($.timelapse.currentVideosArray.videos[$.timelapse.currentVideos[d.filename].position]) // $.timelapse.drawTimeline(false) // } // if($.vidview.loadedVideos && $.vidview.loadedVideos[d.filename])delete($.vidview.loadedVideos[d.filename]) break; } }) $(document).ready(function(){ $('body') .on('click','.open-video',function(){ var el = $(this).parents('[data-mid]') var monitorId = el.attr('data-mid') var videoTime = el.attr('data-time') var video = loadedVideosInMemory[`${monitorId}${videoTime}`] createVideoPlayerTab(video) }) .on('click','[video-time-seeked-video-position]',function(){ var el = $(this) var monitorId = el.attr('data-mid') var videoTime = el.attr('video-time-seeked-video-position') var timeInward = (parseInt(el.attr('video-slice-seeked')) / 1000) - 2 var video = loadedVideosInMemory[`${monitorId}${videoTime}`] timeInward = timeInward < 0 ? 0 : timeInward createVideoPlayerTab(video,timeInward) }) .on('click','.delete-video',function(){ var el = $(this).parents('[data-mid]') var monitorId = el.attr('data-mid') var videoTime = el.attr('data-time') var video = loadedVideosInMemory[`${monitorId}${videoTime}`] var ext = video.filename.split('.') ext = ext[ext.length - 1] var videoEndpoint = getLocation() + '/' + $user.auth_token + '/videos/' + $user.ke + '/' + video.mid + '/' + video.filename $.confirm.create({ title: lang["Delete Video"] + ' : ' + video.filename, body: `${lang.DeleteVideoMsg}

`, clickOptions: { title: ' ' + lang.Delete, class: 'btn-danger btn-sm' }, clickCallback: function(){ $.getJSON(videoEndpoint + '/delete',function(data){ if(data.ok){ console.log('Video Deleted') }else{ console.log('Video Not Deleted',data,deleteEndpoint) } }) } }) }) })