diff --git a/Docker/README.md b/Docker/README.md
index 111bd0a3..815ebd8f 100644
--- a/Docker/README.md
+++ b/Docker/README.md
@@ -20,7 +20,7 @@ bash <(curl -s https://gitlab.com/Shinobi-Systems/Shinobi-Installer/raw/master/s
 
 Once complete open port `8080` of your Docker host in a web browser.
 
-## Run Way
+## "Run" Way
 
 **Installing Shinobi**
 
@@ -85,6 +85,12 @@ docker run -d --name='Shinobi' -p '8080:8080/tcp' -v "/dev/shm/Shinobi/streams":
 
  > Host mount paths have been updated in this document.
 
+ ### Running without Included Database (NoDB)
+
+ For information about this please see this Merge Request done by @thtmnisamnstr. It thoroughly documents how to use the NoDB installation method.
+
+ https://gitlab.com/Shinobi-Systems/Shinobi/-/merge_requests/443
+
  ### Volumes
 
  | Volumes                      | Description                                                                                                                                         |
diff --git a/Docker/init.sh b/Docker/init.sh
index 0bd57db2..e94374b3 100644
--- a/Docker/init.sh
+++ b/Docker/init.sh
@@ -70,13 +70,18 @@ cronKey="$(head -c 1024 < /dev/urandom | sha256sum | awk '{print substr($1,1,29)
 
 cd /home/Shinobi
 mkdir -p libs/customAutoLoad
+
 if [ -e "/config/conf.json" ]; then
     cp /config/conf.json conf.json
 elif [ ! -e "./conf.json" ]; then
     cp conf.sample.json conf.json
 fi
+# Create /config/conf.json if it doesn't exist
+if [ ! -e "/config/conf.json" ]; then
+  node tools/modifyConfiguration.js cpuUsageMarker=CPU subscriptionId=$SUBSCRIPTION_ID thisIsDocker=true pluginKeys="$PLUGIN_KEYS" databaseType="$DB_TYPE" db="$DATABASE_CONFIG" ssl="$SSL_CONFIG"
+  cp /config/conf.json conf.json
+fi
 sudo sed -i -e 's/change_this_to_something_very_random__just_anything_other_than_this/'"$cronKey"'/g' conf.json
-node tools/modifyConfiguration.js cpuUsageMarker=CPU subscriptionId=$SUBSCRIPTION_ID thisIsDocker=true pluginKeys="$PLUGIN_KEYS" db="$DATABASE_CONFIG" ssl="$SSL_CONFIG"
 
 
 echo "============="
diff --git a/Dockerfile b/Dockerfile
index 4d2d1e41..6f4f1072 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,6 +5,7 @@ ENV DB_USER=majesticflame \
     DB_HOST='localhost' \
     DB_DATABASE=ccio \
     DB_PORT=3306 \
+    DB_TYPE='mysql' \
     SUBSCRIPTION_ID=sub_XXXXXXXXXXXX \
     PLUGIN_KEYS='{}' \
     SSL_ENABLED='false' \
diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7
index eed0ba2a..2e724f77 100644
--- a/Dockerfile.arm32v7
+++ b/Dockerfile.arm32v7
@@ -5,6 +5,7 @@ ENV DB_USER=majesticflame \
     DB_HOST='localhost' \
     DB_DATABASE=ccio \
     DB_PORT=3306 \
+    DB_TYPE='mysql' \
     SUBSCRIPTION_ID=sub_XXXXXXXXXXXX \
     PLUGIN_KEYS='{}' \
     SSL_ENABLED='false' \
diff --git a/Dockerfile.nodb b/Dockerfile.nodb
new file mode 100644
index 00000000..8648484f
--- /dev/null
+++ b/Dockerfile.nodb
@@ -0,0 +1,97 @@
+FROM node:16-buster-slim
+
+ENV DB_USER=majesticflame \
+    DB_PASSWORD='' \
+    DB_HOST='localhost' \
+    DB_DATABASE=ccio \
+    DB_PORT=3306 \
+    DB_TYPE='mysql' \
+    SUBSCRIPTION_ID=sub_XXXXXXXXXXXX \
+    PLUGIN_KEYS='{}' \
+    SSL_ENABLED='false' \
+    SSL_COUNTRY='CA' \
+    SSL_STATE='BC' \
+    SSL_LOCATION='Vancouver' \
+    SSL_ORGANIZATION='Shinobi Systems' \
+    SSL_ORGANIZATION_UNIT='IT Department' \
+    SSL_COMMON_NAME='nvr.ninja' \
+    DB_DISABLE_INCLUDED=true
+ARG DEBIAN_FRONTEND=noninteractive
+
+RUN mkdir -p /home/Shinobi /config /var/lib/mysql
+
+RUN apt update -y
+RUN apt install wget curl net-tools -y
+
+# Install FFmpeg
+
+RUN apt update --fix-missing
+RUN apt install -y software-properties-common \
+        libfreetype6-dev \
+        libgnutls28-dev \
+        libmp3lame-dev \
+        libass-dev \
+        libogg-dev \
+        libtheora-dev \
+        libvorbis-dev \
+        libvpx-dev \
+        libwebp-dev \
+        libssh2-1-dev \
+        libopus-dev \
+        librtmp-dev \
+        libx264-dev \
+        libx265-dev \
+        yasm
+RUN apt install -y \
+        build-essential \
+        bzip2 \
+        coreutils \
+        procps \
+        gnutls-bin \
+        nasm \
+        tar \
+        x264
+
+RUN apt install -y zip
+
+RUN apt install -y \
+                ffmpeg \
+                git \
+                make \
+                g++ \
+                gcc \
+                pkg-config \
+                python3 \
+                wget \
+                tar \
+                sudo \
+                xz-utils
+
+
+WORKDIR /home/Shinobi
+COPY . .
+#RUN rm -rf /home/Shinobi/plugins
+COPY ./plugins  /home/Shinobi/plugins
+RUN chmod -R 777 /home/Shinobi/plugins
+RUN npm i npm@latest -g && \
+    npm install --unsafe-perm && \
+    npm install pm2 -g
+COPY ./Docker/pm2.yml ./
+
+RUN npm i pg --save
+
+# Copy default configuration files
+# COPY ./config/conf.json ./config/super.json /home/Shinobi/
+RUN chmod -f +x /home/Shinobi/Docker/init.sh
+RUN sed -i -e 's/\r//g' /home/Shinobi/Docker/init.sh
+# RUN chmod -f +x /home/Shinobi/shinobi
+
+VOLUME ["/home/Shinobi/videos"]
+VOLUME ["/home/Shinobi/libs/customAutoLoad"]
+VOLUME ["/config"]
+
+EXPOSE 8080 443 21 25
+
+ENTRYPOINT ["sh","/home/Shinobi/Docker/init.sh"]
+
+CMD [ "pm2-docker", "/home/Shinobi/Docker/pm2.yml" ]
diff --git a/Dockerfile.nvidia b/Dockerfile.nvidia
index 529d41b4..d6221971 100644
--- a/Dockerfile.nvidia
+++ b/Dockerfile.nvidia
@@ -5,6 +5,7 @@ ENV DB_USER=majesticflame \
     DB_HOST='localhost' \
     DB_DATABASE=ccio \
     DB_PORT=3306 \
+    DB_TYPE='mysql' \
     SUBSCRIPTION_ID=sub_XXXXXXXXXXXX \
     PLUGIN_KEYS='{}' \
     SSL_ENABLED='false' \
diff --git a/definitions/base.js b/definitions/base.js
index a2245ee0..48f021cd 100644
--- a/definitions/base.js
+++ b/definitions/base.js
@@ -3105,6 +3105,31 @@ module.exports = function(s,config,lang){
                                 }
                              ]
                          },
+                         {
+                            "name": "detail=detector_object_ignore_not_move",
+                            "field": lang["Ignore Non-Moving"],
+                            "default": "0",
+                            "fieldType": "select",
+                            "selector": "h_obj_ignore_move",
+                            "possible": [
+                               {
+                                  "name": lang.No,
+                                  "value": "0"
+                               },
+                               {
+                                  "name": lang.Yes,
+                                  "value": "1"
+                               }
+                            ]
+                         },
+                         {
+                             hidden: true,
+                            "name": "detail=detector_object_move_percent",
+                            "field": lang['Minimum Movement'],
+                            "description": lang.inPercent,
+                            "default": "5",
+                            "form-group-class": "h_obj_ignore_move_input h_obj_ignore_move_1"
+                         },
                          {
                              isAdvanced: true,
                             "name": "detail=detector_send_frames_object",
@@ -4935,24 +4960,6 @@ module.exports = function(s,config,lang){
                           }
                       ]
                   },
-                  {
-                      "field": lang['Show Thumbnails in Video List'],
-                      attribute:'localStorage="showThumbnail"',
-                      "description": "",
-                      "default": "0",
-                      "example": "",
-                      "fieldType": "select",
-                      "possible": [
-                          {
-                             "name": lang.No,
-                             "value": "0"
-                          },
-                          {
-                             "name": lang.Yes,
-                             "value": "1"
-                          }
-                      ]
-                  },
                   {
                       "field": lang.Themes,
                       "name": "detail=theme",
diff --git a/languages/en_CA.json b/languages/en_CA.json
index 54047166..8daab026 100644
--- a/languages/en_CA.json
+++ b/languages/en_CA.json
@@ -196,6 +196,8 @@
    "Trigger Camera Groups": "Trigger Camera Groups",
    "Motion Detection": "Motion Detection",
    "Object Detection": "Object Detection",
+   "Minimum Movement": "Minimum Movement",
+   "inPercent": "In Percent",
    "Hide Detection on Stream": "Hide Detection on Stream",
    "JPEG Mode": "JPEG Mode",
    "Reconnect Stream": "Reconnect Stream",
@@ -394,7 +396,7 @@
    "Found Devices": "Found Devices",
    "Switch on for Still Image": "Switch on for Still Image",
    "Live Stream Toggle": "Live Stream Toggle",
-   "RegionNote": "When adding points click on the edge of the polygon. Right click a point to remove.",
+   "RegionNote": "When adding points click on the edge of the polygon. Right click a point to remove it. The dimensions here are based on the Detector Settings of your Monitor. Regions will automatically scale to match Object Detection dimensions.",
    "Points": "Points",
    "Minimum Change": "Minimum Change",
    "Maximum Change": "Maximum Change",
@@ -841,6 +843,7 @@
    "Delete Motionless Video": "Delete Motionless Video",
    "Send Frames": "Send Frames <small>Push frames to be analyzed</small>",
    "Detector Rate": "Detector Rate <small>(FPS)</small>",
+   "Ignore Non-Moving": "Ignore Non-Moving",
    "Feed-in Image Width": "Feed-in Image Width",
    "Feed-in Image Height": "Feed-in Image Height",
    "Check for Motion First": "Check for Motion First",
diff --git a/libs/basic/utils.js b/libs/basic/utils.js
index 8bbb9c0d..be6e92fd 100644
--- a/libs/basic/utils.js
+++ b/libs/basic/utils.js
@@ -114,14 +114,16 @@ module.exports = (processCwd,config) => {
             method : options.method || 'GET',
             headers: {'Content-Type': 'application/json'}
         }
-        if(typeof options.postData === 'object'){
-            requestOptions.body = JSON.stringify(options.postData)
-        } else if(typeof options.postData === 'string'){
-            try{
-                JSON.parse(options.postData)
-                requestOptions.body = options.postData
-            }catch(err){
-                
+        if(requestOptions.method !== 'GET'){
+            if(typeof options.postData === 'object'){
+                requestOptions.body = JSON.stringify(options.postData)
+            }else if(options.postData && typeof options.postData === 'string'){
+                try{
+                    JSON.parse(options.postData)
+                    requestOptions.body = options.postData
+                }catch(err){
+
+                }
             }
         }
         if(hasUsernameAndPassword && hasDigestAuthEnabled){
diff --git a/libs/commander/workerv2.js b/libs/commander/workerv2.js
index dfa7a950..fdffd758 100644
--- a/libs/commander/workerv2.js
+++ b/libs/commander/workerv2.js
@@ -27,7 +27,7 @@ parentPort.on('message',(data) => {
         case'init':
             config = Object.assign({},data.config)
             lang = Object.assign({},data.lang)
-            remoteConnectionPort = config.ssl ? config.ssl.port || 443 : config.port || 8080
+            remoteConnectionPort = config.ssl && JSON.stringify(config.ssl) !== '{}' ? config.ssl.port || 443 : config.port || 8080
             initialize()
         break;
         case'exit':
@@ -332,7 +332,7 @@ function closeResponseTunnel(originalRequestId){
 function initialize(){
     const selectedP2PServerId = config.p2pServerList[config.p2pHostSelected] ? config.p2pHostSelected : Object.keys(config.p2pServerList)[0]
     const p2pServerDetails = config.p2pServerList[selectedP2PServerId]
-    const selectedHost = 'ws://' + p2pServerDetails.host + ':' + p2pServerDetails.p2pPort
+    const selectedHost = `${p2pServerDetails.secure ? `wss` : 'ws'}://` + p2pServerDetails.host + ':' + p2pServerDetails.p2pPort
     config.selectedHost = selectedHost
     startConnection(selectedHost,config.p2pApiKey)
 }
diff --git a/libs/events/tracking.js b/libs/events/tracking.js
new file mode 100644
index 00000000..b0bdff2c
--- /dev/null
+++ b/libs/events/tracking.js
@@ -0,0 +1,157 @@
+const movingThings = require('shinobi-node-moving-things-tracker').Tracker
+module.exports = (s,config,lang,app,io) => {
+    const objectTrackers = {}
+    const objectTrackerTimeouts = {}
+    function resetObjectTracker(trackerId,matrices){
+        const Tracker = movingThings.newTracker();
+        objectTrackers[trackerId] = {
+            frameCount: 1,
+            tracker: Tracker,
+            lastPositions: []
+        }
+        return objectTrackers[trackerId]
+    }
+    function setLastTracked(trackerId, trackedMatrices){
+        const theTracker = objectTrackers[trackerId]
+        theTracker.lastPositions = trackedMatrices
+    }
+    function getTracked(trackerId){
+        const theTracker = objectTrackers[trackerId]
+        const frameCount = theTracker.frameCount
+        const trackedObjects = theTracker.tracker.getJSONOfTrackedItems().map((matrix) => {
+            return {
+                id: matrix.id,
+                tag: matrix.name,
+                x: matrix.x,
+                y: matrix.y,
+                width: matrix.w,
+                height: matrix.h,
+                confidence: matrix.confidence,
+                isZombie: matrix.isZombie,
+            }
+        })
+        return trackedObjects;
+    }
+    function trackObject(trackerId,matrices){
+        if(!objectTrackers[trackerId]){
+            resetObjectTracker(trackerId)
+        }
+        const mappedMatrices = matrices.map((matrix) => {
+            return {
+                x: matrix.x,
+                y: matrix.y,
+                w: matrix.width,
+                h: matrix.height,
+                confidence: matrix.confidence,
+                name: matrix.tag,
+            }
+        });
+        const theTracker = objectTrackers[trackerId]
+        theTracker.tracker.updateTrackedItemsWithNewFrame(mappedMatrices, theTracker.frameCount);
+        ++theTracker.frameCount
+    }
+    function trackObjectWithTimeout(trackerId,matrices){
+        clearTimeout(objectTrackerTimeouts[trackerId]);
+        objectTrackerTimeouts[trackerId] = setTimeout(() => {
+            objectTrackers[trackerId].tracker.reset()
+            delete(objectTrackers[trackerId])
+            delete(objectTrackerTimeouts[trackerId])
+        },1000 * 60);
+        trackObject(trackerId,matrices);
+    }
+    function objectHasMoved(matrices, options = {}) {
+      const { imgHeight = 1, imgWidth = 1, threshold = 0 } = options;
+      for (let i = 0; i < matrices.length; i++) {
+        const current = matrices[i];
+        if (i < matrices.length - 1) {
+          const next = matrices[i + 1];
+          let totalDistanceMoved = 0;
+          let numPointsCompared = 0;
+          if (next) {
+            // Compare each corner of the matrices
+            const currentCorners = [
+              { x: current.x, y: current.y },
+              { x: current.x + current.width, y: current.y },
+              { x: current.x, y: current.y + current.height },
+              { x: current.x + current.width, y: current.y + current.height }
+            ];
+            const nextCorners = [
+              { x: next.x, y: next.y },
+              { x: next.x + next.width, y: next.y },
+              { x: next.x, y: next.y + next.height },
+              { x: next.x + next.width, y: next.y + next.height }
+            ];
+            for (let j = 0; j < currentCorners.length; j++) {
+              const currentCorner = currentCorners[j];
+              const nextCorner = nextCorners[j];
+              const dx = nextCorner.x - currentCorner.x;
+              const dy = nextCorner.y - currentCorner.y;
+              const distanceMoved = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
+              const distanceMovedPercent =
+                (100 * distanceMoved) / Math.max(current.width, current.height);
+              totalDistanceMoved += distanceMovedPercent;
+              numPointsCompared++;
+            }
+            const averageDistanceMoved = totalDistanceMoved / numPointsCompared;
+            if (averageDistanceMoved < threshold) {
+              continue;
+            } else {
+              return true;
+            }
+          }
+        }
+      }
+      return false;
+    }
+    function groupMatricesById(matrices) {
+      const matrixById = {};
+      const matrixTags = {};
+
+      matrices.forEach(matrix => {
+        const id = matrix.id;
+        const tag = matrix.tag;
+        if (!matrixById[id]) {
+          matrixById[id] = [];
+        }
+        matrixTags[tag] = id;
+        matrixById[id].push(matrix);
+      });
+
+      return matrixById
+    }
+    function getAllMatricesThatMoved(monitorConfig,matrices){
+        const monitorDetails = monitorConfig.details
+        const imgWidth = parseInt(monitorDetails.detector_scale_x_object) || 1280
+        const imgHeight = parseInt(monitorDetails.detector_scale_y_object) || 720
+        const objectMovePercent = parseInt(monitorDetails.detector_object_move_percent) || 5
+        const groupKey = monitorConfig.ke
+        const monitorId = monitorConfig.mid
+        const trackerId = `${groupKey}${monitorId}`
+        const theTracker = objectTrackers[trackerId]
+        const lastPositions = theTracker.lastPositions
+        const sortedById = groupMatricesById([...lastPositions,...matrices])
+        const movedMatrices = []
+        for (const objectId in sortedById) {
+            const sortedList = sortedById[objectId]
+            if(sortedList[1]){
+                const matrixHasMoved = objectHasMoved(sortedList,{
+                    threshold: objectMovePercent,
+                    imgWidth: imgWidth,
+                    imgHeight: imgHeight,
+                });
+                if(matrixHasMoved){
+                    movedMatrices.push(sortedList[1])
+                }
+            }
+        }
+        return movedMatrices
+    }
+    return {
+        trackObjectWithTimeout,
+        resetObjectTracker,
+        trackObject,
+        getTracked,
+        setLastTracked,
+        getAllMatricesThatMoved,
+    }
+}
diff --git a/libs/events/utils.js b/libs/events/utils.js
index 679824b5..16994645 100644
--- a/libs/events/utils.js
+++ b/libs/events/utils.js
@@ -24,6 +24,12 @@ module.exports = (s,config,lang,app,io) => {
         cutVideoLength,
         reEncodeVideoAndBinOriginalAddToQueue
     } = require('../video/utils.js')(s,config,lang)
+    const {
+        getTracked,
+        setLastTracked,
+        trackObjectWithTimeout,
+        getAllMatricesThatMoved,
+    } = require('./tracking.js')(s,config,lang,app,io)
     const {
         isEven,
         fetchTimeout,
@@ -97,7 +103,7 @@ module.exports = (s,config,lang,app,io) => {
         }
         return newString
     }
-    const isAtleastOneMatrixInRegion = function(regions,matrices,callback){
+    const isAtleastOneMatrixInRegion = function(regions,matrices){
         var regionPolys = []
         var matrixPoints = []
         regions.forEach(function(region,n){
@@ -108,46 +114,20 @@ module.exports = (s,config,lang,app,io) => {
             regionPolys[n] = new P(new V(0,0), polyPoints)
         })
         var collisions = []
-        var foundInRegion = false
         matrices.forEach(function(matrix){
             var matrixPoly = new B(new V(matrix.x, matrix.y), matrix.width, matrix.height).toPolygon()
+            var foundInRegion = false
             regionPolys.forEach(function(region,n){
-                var response = new SAT.Response()
-                var collided = SAT.testPolygonPolygon(matrixPoly, region, response)
-                if(collided === true){
-                    collisions.push({
-                        matrix: matrix,
-                        region: regions[n]
-                    })
-                    foundInRegion = true
+                if(!foundInRegion){
+                    var response = new SAT.Response()
+                    var collided = SAT.testPolygonPolygon(matrixPoly, region, response)
+                    if(collided === true){
+                        foundInRegion = true
+                        collisions.push(matrix)
+                    }
                 }
             })
         })
-        if(callback)callback(foundInRegion,collisions)
-        return foundInRegion
-    }
-    const scanMatricesforCollisions = function(region,matrices){
-        var matrixPoints = []
-        var collisions = []
-        if (!region || !matrices){
-            if(callback)callback(collisions)
-            return collisions
-        }
-        var polyPoints = []
-        region.points.forEach(function(point){
-            polyPoints.push(new V(parseInt(point[0]),parseInt(point[1])))
-        })
-        var regionPoly = new P(new V(0,0), polyPoints)
-        matrices.forEach(function(matrix){
-            if (matrix){
-                var matrixPoly = new B(new V(matrix.x, matrix.y), matrix.width, matrix.height).toPolygon()
-                var response = new SAT.Response()
-                var collided = SAT.testPolygonPolygon(matrixPoly, regionPoly, response)
-                if(collided === true){
-                    collisions.push(matrix)
-                }
-            }
-        })
         return collisions
     }
     const getLargestMatrix = (matrices) => {
@@ -367,22 +347,6 @@ module.exports = (s,config,lang,app,io) => {
             }
         })
     }
-    const checkForObjectsInRegions = (monitorConfig,eventDetails,filter,d,didCountingAlready) => {
-        const monitorDetails = monitorConfig.details
-        if(hasMatrices(eventDetails) && monitorDetails.detector_obj_region === '1'){
-            var regions = s.group[monitorConfig.ke].activeMonitors[monitorConfig.mid].parsedObjects.cords
-            var isMatrixInRegions = isAtleastOneMatrixInRegion(regions,eventDetails.matrices)
-            if(isMatrixInRegions){
-                s.debugLog('Matrix in region!')
-                if(filter.countObjects && monitorDetails.detector_obj_count === '1' && monitorDetails.detector_obj_count_in_region === '1' && !didCountingAlready){
-                    countObjects(d)
-                }
-            }else{
-                return false
-            }
-        }
-        return true
-    }
     const runEventExecutions = async (eventTime,monitorConfig,eventDetails,forceSave,filter,d, triggerEvent) => {
         const monitorDetails = monitorConfig.details
         const detailString = JSON.stringify(eventDetails)
@@ -674,6 +638,8 @@ module.exports = (s,config,lang,app,io) => {
     }
     const triggerEvent = async (d,forceSave) => {
         var didCountingAlready = false
+        const groupKey = d.ke
+        const monitorId = d.mid || d.id
         const filter = {
             halt : false,
             addToMotionCounter : true,
@@ -698,7 +664,6 @@ module.exports = (s,config,lang,app,io) => {
         s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
             extender(d,filter)
         })
-        const eventDetails = d.details
         const passedEventFilters = checkEventFilters(d,activeMonitor.details,filter)
         if(!passedEventFilters)return;
         const eventTime = new Date()
@@ -722,20 +687,37 @@ module.exports = (s,config,lang,app,io) => {
         ){
             addToEventCounter(d)
         }
+        const eventDetails = d.details
         if(
             (filter.countObjects || monitorDetails.detector_obj_count === '1') &&
             monitorDetails.detector_obj_count_in_region !== '1'
         ){
             didCountingAlready = true
-            countObjects(d)
+            countObjects(eventDetails.matrices)
         }
         if(filter.useLock){
             const passedMotionLock = checkMotionLock(d,monitorDetails)
             if(!passedMotionLock)return
         }
-        const passedObjectInRegionCheck = checkForObjectsInRegions(monitorConfig,eventDetails,filter,d,didCountingAlready)
-        if(!passedObjectInRegionCheck)return
-
+        const thisHasMatrices = hasMatrices(eventDetails)
+        if(thisHasMatrices && monitorDetails.detector_obj_region === '1'){
+            var regions = s.group[monitorConfig.ke].activeMonitors[monitorConfig.mid].parsedObjects.cordsForObjectDetection
+            var matricesInRegions = isAtleastOneMatrixInRegion(regions,eventDetails.matrices)
+            eventDetails.matrices = matricesInRegions
+            if(matricesInRegions.length === 0)return;
+            if(filter.countObjects && monitorDetails.detector_obj_count === '1' && monitorDetails.detector_obj_count_in_region === '1' && !didCountingAlready){
+                countObjects(eventDetails.matrices)
+            }
+        }
+        if(thisHasMatrices && monitorDetails.detector_object_ignore_not_move === '1'){
+            const trackerId = `${groupKey}${monitorId}`
+            trackObjectWithTimeout(trackerId,eventDetails.matrices)
+            const trackedObjects = getTracked(trackerId)
+            const objectsThatMoved = getAllMatricesThatMoved(monitorConfig,trackedObjects)
+            setLastTracked(trackerId, trackedObjects)
+            if(objectsThatMoved.length === 0)return;
+            eventDetails.matrices = objectsThatMoved
+        }
         //
         d.doObjectDetection = (
             eventDetails.reason !== 'object' &&
@@ -762,10 +744,34 @@ module.exports = (s,config,lang,app,io) => {
             doObjectDetection: d.doObjectDetection
         },`DETECTOR_${monitorConfig.ke}${monitorConfig.mid}`);
     }
+    function convertRegionPointsToNewDimensions(regions, options) {
+      const { fromWidth, fromHeight, toWidth, toHeight } = options;
+
+      // Compute the conversion factors for x and y coordinates
+      const xFactor = toWidth / fromWidth;
+      const yFactor = toHeight / fromHeight;
+
+      // Clone the regions array and update the points for each region
+      const newRegions = regions.map(region => {
+        const { points } = region;
+
+        // Clone the points array and update the coordinates
+        const newPoints = points.map(([x, y]) => {
+          const newX = Math.round(x * xFactor);
+          const newY = Math.round(y * yFactor);
+          return [newX.toString(), newY.toString()];
+        });
+
+        // Clone the region object and update the points
+        return { ...region, points: newPoints };
+      });
+
+      return newRegions;
+    }
     return {
         countObjects: countObjects,
-        isAtleastOneMatrixInRegion: isAtleastOneMatrixInRegion,
-        scanMatricesforCollisions: scanMatricesforCollisions,
+        isAtleastOneMatrixInRegion,
+        convertRegionPointsToNewDimensions,
         getLargestMatrix: getLargestMatrix,
         addToEventCounter: addToEventCounter,
         clearEventCounter: clearEventCounter,
@@ -774,7 +780,6 @@ module.exports = (s,config,lang,app,io) => {
         checkEventFilters: checkEventFilters,
         checkMotionLock: checkMotionLock,
         runMultiEventBasedRecord: runMultiEventBasedRecord,
-        checkForObjectsInRegions: checkForObjectsInRegions,
         runEventExecutions: runEventExecutions,
         createEventBasedRecording: createEventBasedRecording,
         closeEventBasedRecording: closeEventBasedRecording,
diff --git a/libs/monitor/utils.js b/libs/monitor/utils.js
index 1b3147b7..01ce2840 100644
--- a/libs/monitor/utils.js
+++ b/libs/monitor/utils.js
@@ -26,6 +26,7 @@ module.exports = (s,config,lang) => {
     const {
         addEventDetailsToString,
         closeEventBasedRecording,
+        convertRegionPointsToNewDimensions,
         triggerEvent,
     } = require('../events/utils.js')(s,config,lang)
     const {
@@ -49,7 +50,7 @@ module.exports = (s,config,lang) => {
     const getUpdateableFields = require('./updatedFields.js')
     const processKill = (proc) => {
         const response = {ok: true}
-        const processPID = parseInt(`${proc.pid}`)
+        const processPID = proc && proc.pid ? parseInt(`${proc.pid}`) : null
         return new Promise((resolve,reject) => {
             let alreadyResolved = false
             function doResolve(response){
@@ -1703,7 +1704,18 @@ module.exports = (s,config,lang) => {
                     activeMonitor.details = e.details
                     switch(v){
                         case'cords':
-                            activeMonitor.parsedObjects[v] = Object.values(s.parseJSON(e.details[v]))
+                            const fromWidth = parseInt(e.details.detector_scale_x) || 640
+                            const fromHeight = parseInt(e.details.detector_scale_y) || 480
+                            const toWidth = parseInt(e.details.detector_scale_x_object) || 1280
+                            const toHeight = parseInt(e.details.detector_scale_y_object) || 720
+                            const theCords = Object.values(s.parseJSON(e.details[v])) || [];
+                            activeMonitor.parsedObjects.cordsForObjectDetection = convertRegionPointsToNewDimensions(theCords,{
+                                fromWidth,
+                                fromHeight,
+                                toWidth,
+                                toHeight,
+                            });
+                            activeMonitor.parsedObjects.cords = theCords
                         break;
                         default:
                             activeMonitor.parsedObjects[v] = s.parseJSON(e.details[v])
diff --git a/libs/uploaders/s3based.js b/libs/uploaders/s3based.js
index e15bc8e2..c800bd7a 100644
--- a/libs/uploaders/s3based.js
+++ b/libs/uploaders/s3based.js
@@ -331,16 +331,16 @@ module.exports = function(s,config,lang){
                       "value": "us-west-1"
                    },
                    {
-                      "name": "US West 2)",
+                      "name": "US West 2",
                       "value": "us-west-2"
                    },
                    {
                       "name": "US East 1",
-                      "value": "us-east-2"
+                      "value": "us-east-1"
                    },
                    {
                       "name": "US East 2",
-                      "value": "us-east-1"
+                      "value": "us-east-2"
                    },
                    {
                       "name": "Asia Pacific 1",
diff --git a/libs/webServerStreamPaths.js b/libs/webServerStreamPaths.js
index 6750f54c..cd2a85f5 100644
--- a/libs/webServerStreamPaths.js
+++ b/libs/webServerStreamPaths.js
@@ -186,6 +186,10 @@ module.exports = function(s,config,lang,app){
                             res.write("\r\n");
                             res.write(content,'binary');
                             res.write("\r\n");
+                            if(!Emitter){
+                                res.end();
+                                return;
+                            }
                             var ip = s.getClientIp(req)
                             s.camera('watch_on',{
                                 id : req.params.id,
diff --git a/package-lock.json b/package-lock.json
index bf2b493b..5e9c67d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
       "dependencies": {
         "@aws-sdk/client-s3": "^3.226.0",
         "async": "^3.2.2",
-        "backblaze-b2": "^0.9.12",
+        "backblaze-b2": "^1.7.0",
         "body-parser": "^1.19.0",
         "bson": "^4.6.1",
         "connection-tester": "^0.2.0",
@@ -35,6 +35,7 @@
         "mysql": "^2.18.1",
         "node-abort-controller": "^3.0.1",
         "node-fetch": "^2.6.7",
+        "shinobi-node-moving-things-tracker": "^0.9.1",
         "node-onvif-events": "^2.0.5",
         "node-ssh": "^12.0.4",
         "node-telegram-bot-api": "^0.58.0",
@@ -1384,6 +1385,17 @@
         "node": ">=14.0.0"
       }
     },
+    "node_modules/@babel/runtime": {
+      "version": "7.21.0",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
+      "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+      "dependencies": {
+        "regenerator-runtime": "^0.13.11"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
     "node_modules/@discordjs/collection": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz",
@@ -1694,15 +1706,34 @@
         "follow-redirects": "^1.14.4"
       }
     },
-    "node_modules/backblaze-b2": {
-      "version": "0.9.12",
-      "resolved": "https://registry.npmjs.org/backblaze-b2/-/backblaze-b2-0.9.12.tgz",
-      "integrity": "sha1-b0IHJs7G0L787ohqS7bfUOnAGco=",
+    "node_modules/axios-retry": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.4.0.tgz",
+      "integrity": "sha512-VdgaP+gHH4iQYCCNUWF2pcqeciVOdGrBBAYUfTY+wPcO5Ltvp/37MLFNCmJKo7Gj3SHvCSdL8ouI1qLYJN3liA==",
       "dependencies": {
-        "node-sha1": "^1.0.1",
-        "q": "^1.4.1",
-        "request": "^2.67.0",
-        "request-progress": "^3.0.0"
+        "@babel/runtime": "^7.15.4",
+        "is-retry-allowed": "^2.2.0"
+      }
+    },
+    "node_modules/backblaze-b2": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/backblaze-b2/-/backblaze-b2-1.7.0.tgz",
+      "integrity": "sha512-8cVsKkXspuM1UeLI8WWSWw2JHfB7/IvqTtzvwhHqqhNyqcYl8iZ2lFpeuXGKcFA1TiSRlgALXWFJ9eKG6+3ZPg==",
+      "dependencies": {
+        "axios": "^0.21.1",
+        "axios-retry": "^3.1.9",
+        "lodash": "^4.17.21"
+      },
+      "engines": {
+        "node": ">=10.0"
+      }
+    },
+    "node_modules/backblaze-b2/node_modules/axios": {
+      "version": "0.21.4",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+      "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+      "dependencies": {
+        "follow-redirects": "^1.14.0"
       }
     },
     "node_modules/backoff": {
@@ -4365,6 +4396,17 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-retry-allowed": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz",
+      "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-shared-array-buffer": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz",
@@ -4735,6 +4777,11 @@
       "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
       "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
     },
+    "node_modules/lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+    },
     "node_modules/lru-cache": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -5035,6 +5082,11 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
     },
+    "node_modules/munkres-js": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/munkres-js/-/munkres-js-1.2.2.tgz",
+      "integrity": "sha512-0oF4tBDvzx20CYzQ44tTJMfwTBJWXe7cE73Sa/u7Mz7X8jRtyOXOGE9kJBhCfX7Akku3Iy/WHa0sRgqLRq2xaQ=="
+    },
     "node_modules/mv": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
@@ -5174,6 +5226,20 @@
         "node": ">= 6.13.0"
       }
     },
+    "node_modules/node-moving-things-tracker": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/node-moving-things-tracker/-/node-moving-things-tracker-0.9.1.tgz",
+      "integrity": "sha512-JVa+DbQRgOsOcfIIxhw3kTUfO407RdXnVqNgPvAlhUHu7PWziUah+MuTcaN4rRktBicj/l2scI64a2crBgrzKw==",
+      "dependencies": {
+        "lodash.isequal": "^4.5.0",
+        "minimist": "^1.2.0",
+        "munkres-js": "^1.2.2",
+        "uuid": "^3.2.1"
+      },
+      "bin": {
+        "shinobi-node-moving-things-tracker": "main.js"
+      }
+    },
     "node_modules/node-onvif-events": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/node-onvif-events/-/node-onvif-events-2.0.5.tgz",
@@ -5182,11 +5248,6 @@
         "onvif": "git+https://github.com/agsh/onvif.git"
       }
     },
-    "node_modules/node-sha1": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/node-sha1/-/node-sha1-1.0.1.tgz",
-      "integrity": "sha1-Mu2EfYUTFXuW3sa3noxHvK63jhw="
-    },
     "node_modules/node-ssh": {
       "version": "12.0.4",
       "resolved": "https://registry.npmjs.org/node-ssh/-/node-ssh-12.0.4.tgz",
@@ -5839,15 +5900,6 @@
         "node": "*"
       }
     },
-    "node_modules/q": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
-      "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
-      "engines": {
-        "node": ">=0.6.0",
-        "teleport": ">=0.2.0"
-      }
-    },
     "node_modules/qs": {
       "version": "6.7.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -5907,6 +5959,11 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+    },
     "node_modules/regex-not": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -5976,14 +6033,6 @@
         "node": ">= 6"
       }
     },
-    "node_modules/request-progress": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
-      "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=",
-      "dependencies": {
-        "throttleit": "^1.0.0"
-      }
-    },
     "node_modules/request-promise": {
       "version": "4.2.6",
       "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
@@ -6946,11 +6995,6 @@
         "node": ">=8.0.0"
       }
     },
-    "node_modules/throttleit": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
-      "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw="
-    },
     "node_modules/tildify": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
@@ -8846,6 +8890,14 @@
         "tslib": "^2.3.1"
       }
     },
+    "@babel/runtime": {
+      "version": "7.21.0",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
+      "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+      "requires": {
+        "regenerator-runtime": "^0.13.11"
+      }
+    },
     "@discordjs/collection": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz",
@@ -9078,15 +9130,33 @@
         "follow-redirects": "^1.14.4"
       }
     },
-    "backblaze-b2": {
-      "version": "0.9.12",
-      "resolved": "https://registry.npmjs.org/backblaze-b2/-/backblaze-b2-0.9.12.tgz",
-      "integrity": "sha1-b0IHJs7G0L787ohqS7bfUOnAGco=",
+    "axios-retry": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.4.0.tgz",
+      "integrity": "sha512-VdgaP+gHH4iQYCCNUWF2pcqeciVOdGrBBAYUfTY+wPcO5Ltvp/37MLFNCmJKo7Gj3SHvCSdL8ouI1qLYJN3liA==",
       "requires": {
-        "node-sha1": "^1.0.1",
-        "q": "^1.4.1",
-        "request": "^2.67.0",
-        "request-progress": "^3.0.0"
+        "@babel/runtime": "^7.15.4",
+        "is-retry-allowed": "^2.2.0"
+      }
+    },
+    "backblaze-b2": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/backblaze-b2/-/backblaze-b2-1.7.0.tgz",
+      "integrity": "sha512-8cVsKkXspuM1UeLI8WWSWw2JHfB7/IvqTtzvwhHqqhNyqcYl8iZ2lFpeuXGKcFA1TiSRlgALXWFJ9eKG6+3ZPg==",
+      "requires": {
+        "axios": "^0.21.1",
+        "axios-retry": "^3.1.9",
+        "lodash": "^4.17.21"
+      },
+      "dependencies": {
+        "axios": {
+          "version": "0.21.4",
+          "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+          "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+          "requires": {
+            "follow-redirects": "^1.14.0"
+          }
+        }
       }
     },
     "backoff": {
@@ -11097,6 +11167,11 @@
         "is-unc-path": "^1.0.0"
       }
     },
+    "is-retry-allowed": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz",
+      "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg=="
+    },
     "is-shared-array-buffer": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz",
@@ -11373,6 +11448,11 @@
       "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
       "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
     },
+    "lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+    },
     "lru-cache": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -11588,6 +11668,11 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
     },
+    "munkres-js": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/munkres-js/-/munkres-js-1.2.2.tgz",
+      "integrity": "sha512-0oF4tBDvzx20CYzQ44tTJMfwTBJWXe7cE73Sa/u7Mz7X8jRtyOXOGE9kJBhCfX7Akku3Iy/WHa0sRgqLRq2xaQ=="
+    },
     "mv": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
@@ -11697,6 +11782,17 @@
       "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
       "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
     },
+    "shinobi-node-moving-things-tracker": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/node-moving-things-tracker/-/node-moving-things-tracker-0.9.1.tgz",
+      "integrity": "sha512-JVa+DbQRgOsOcfIIxhw3kTUfO407RdXnVqNgPvAlhUHu7PWziUah+MuTcaN4rRktBicj/l2scI64a2crBgrzKw==",
+      "requires": {
+        "lodash.isequal": "^4.5.0",
+        "minimist": "^1.2.0",
+        "munkres-js": "^1.2.2",
+        "uuid": "^3.2.1"
+      }
+    },
     "node-onvif-events": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/node-onvif-events/-/node-onvif-events-2.0.5.tgz",
@@ -11705,11 +11801,6 @@
         "onvif": "git+https://github.com/agsh/onvif.git"
       }
     },
-    "node-sha1": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/node-sha1/-/node-sha1-1.0.1.tgz",
-      "integrity": "sha1-Mu2EfYUTFXuW3sa3noxHvK63jhw="
-    },
     "node-ssh": {
       "version": "12.0.4",
       "resolved": "https://registry.npmjs.org/node-ssh/-/node-ssh-12.0.4.tgz",
@@ -12205,11 +12296,6 @@
       "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-1.2.2.tgz",
       "integrity": "sha512-+3Xcj+kiMiouZK1Ws8yGBTyl8WMPZZdELgl/iVxYqNwDdlaObBHMhEGPRC6Zb9t0BE27ikOoOqSIO1cKZOtsDA=="
     },
-    "q": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
-      "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
-    },
     "qs": {
       "version": "6.7.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -12254,6 +12340,11 @@
         "resolve": "^1.1.6"
       }
     },
+    "regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+    },
     "regex-not": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -12327,14 +12418,6 @@
         }
       }
     },
-    "request-progress": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
-      "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=",
-      "requires": {
-        "throttleit": "^1.0.0"
-      }
-    },
     "request-promise": {
       "version": "4.2.6",
       "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
@@ -13071,11 +13154,6 @@
       "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.1.tgz",
       "integrity": "sha512-6usSlV9KyHsspvwu2duKH+FMUhqJnAh6J5J/4MITl8s94iSUQTLkJggdiewKv4RyARQccnigV48Z+khiuVZDJw=="
     },
-    "throttleit": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
-      "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw="
-    },
     "tildify": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
diff --git a/package.json b/package.json
index 9c654a03..1358974a 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
   "dependencies": {
     "@aws-sdk/client-s3": "^3.226.0",
     "async": "^3.2.2",
-    "backblaze-b2": "^0.9.12",
+    "backblaze-b2": "^1.7.0",
     "body-parser": "^1.19.0",
     "bson": "^4.6.1",
     "connection-tester": "^0.2.0",
@@ -41,6 +41,7 @@
     "mysql": "^2.18.1",
     "node-abort-controller": "^3.0.1",
     "node-fetch": "^2.6.7",
+    "shinobi-node-moving-things-tracker": "^0.9.1",
     "node-onvif-events": "^2.0.5",
     "node-ssh": "^12.0.4",
     "node-telegram-bot-api": "^0.58.0",
diff --git a/tools/installAllCustomAutoLoadPackages.js b/tools/installAllCustomAutoLoadPackages.js
new file mode 100644
index 00000000..1c1bbb90
--- /dev/null
+++ b/tools/installAllCustomAutoLoadPackages.js
@@ -0,0 +1,46 @@
+const fsOld = require('fs')
+const fs = fsOld.promises
+const spawn = require('child_process').spawn
+const configLocation = process.argv[2] ? process.argv[2] : `${__dirname}/../conf.json`
+const config = require(configLocation)
+const customAutoLoadPath = `${__dirname}/../libs/customAutoLoad/`
+
+async function getAllCustomAutoLoadModuleFolders(){
+    return (await fs.readdir(customAutoLoadPath)).filter((filename) => {
+        return fsOld.lstatSync(`${customAutoLoadPath}${filename}`).isDirectory()
+    })
+}
+function installModule(filename){
+    return new Promise((resolve) => {
+        console.log(`Installing Module : ${filename}`)
+        const folderPath = `${customAutoLoadPath}${filename}`
+        const tempSh = `${folderPath}/tempSh.sh`
+        fsOld.writeFileSync(tempSh,`cd "${folderPath}" && npm install`)
+        const installProcess = spawn('sh',[tempSh])
+        installProcess.stdout.on('data',function(data){
+            const text = data.toString()
+            console.log(text)
+        })
+        installProcess.stderr.on('data',function(data){
+            const text = data.toString()
+            console.error(text)
+        })
+        installProcess.on('close',function(){
+            resolve()
+            fs.rm(tempSh)
+        })
+    })
+}
+async function run(){
+    const folderList = await getAllCustomAutoLoadModuleFolders()
+    for (let i = 0; i < folderList.length; i++) {
+        const folderName = folderList[i]
+        await installModule(folderName)
+    }
+}
+run().then(() => {
+    console.log('Done!')
+})
+.catch((err) => {
+    console.error('RUN ERROR',err)
+})
diff --git a/web/assets/js/bs5.accountSettings.js b/web/assets/js/bs5.accountSettings.js
index 0afc888c..5d0e601d 100644
--- a/web/assets/js/bs5.accountSettings.js
+++ b/web/assets/js/bs5.accountSettings.js
@@ -7,7 +7,7 @@ $(document).ready(function(){
     var addStorageMaxAmountsField = theForm.find('[detail="addStorage"]')
     function drawAddStorageFields(){
         try{
-            var addStorageData = JSON.parse($user.details.addStorage || '{}')
+            var addStorageData = safeJsonParse($user.details.addStorage || '{}')
             var html = ''
             $.each(addStorage,function(n,storage){
                 var theStorage = addStorageData[storage.path]
@@ -62,7 +62,6 @@ $(document).ready(function(){
         })
         return json
     }
-    $('body')
     theForm.find('[detail]').change(onDetailFieldChange)
     theForm.find('[detail]').change(function(){
         onDetailFieldChange(this)
diff --git a/web/assets/js/bs5.liveGrid.js b/web/assets/js/bs5.liveGrid.js
index 0b478c9f..11f91d58 100644
--- a/web/assets/js/bs5.liveGrid.js
+++ b/web/assets/js/bs5.liveGrid.js
@@ -6,6 +6,8 @@ var liveGrid = $('#monitors_live')
 var liveGridData = null
 var liveGridOpenCountElements = $('.liveGridOpenCount')
 var liveGridOpenCount = 0
+var liveGridPauseScrollTimeout = null;
+var liveGridPlayingNow = {};
 //
 var onLiveStreamInitiateExtensions = []
 function onLiveStreamInitiate(callback){
@@ -337,222 +339,225 @@ function drawLiveGridBlock(monitorConfig,subStreamChannel){
     }
 }
 function initiateLiveGridPlayer(monitor,subStreamChannel){
-    var livePlayerElement = loadedLiveGrids[monitor.mid]
+    var monitorId = monitor.mid
     var details = monitor.details
     var groupKey = monitor.ke
     var monitorId = monitor.mid
+    var livePlayerBlocks = liveGridElements[monitorId]
+    var monitorItem = livePlayerBlocks.monitorItem
     var loadedMonitor = loadedMonitors[monitorId]
-    var loadedPlayer = loadedLiveGrids[monitor.mid]
+    var loadedPlayer = loadedLiveGrids[monitorId]
     var websocketPath = checkCorrectPathEnding(location.pathname) + 'socket.io'
     var containerElement = $(`#monitor_live_${monitor.mid}`)
     var streamType = subStreamChannel ? details.substream ? details.substream.output.stream_type : 'hls' : details.stream_type
-    if(location.search === '?p2p=1'){
-        websocketPath = '/socket.io'
-        // websocketQuery.machineId = machineId
+    var isInView = isScrolledIntoView(monitorItem[0])
+    if(!isInView){
+        return;
     }
+    liveGridPlayingNow[monitorId] = true
     switch(streamType){
-        case'jpeg':
-            startJpegStream(monitorId)
-        break;
-        case'b64':
-            if(loadedPlayer.Base64 && loadedPlayer.Base64.connected){
-                loadedPlayer.Base64.disconnect()
-            }
-            loadedPlayer.Base64 = io(location.origin,{ path: websocketPath, query: websocketQuery, transports: ['websocket'], forceNew: false})
-            var ws = loadedPlayer.Base64
-            var buffer
-            ws.on('diconnect',function(){
-                console.log('Base64 Stream Disconnected')
-            })
-            ws.on('connect',function(){
-                ws.emit('Base64',{
-                    auth: $user.auth_token,
-                    uid: $user.uid,
-                    ke: monitor.ke,
-                    id: monitor.mid,
-                    channel: subStreamChannel
+            case'jpeg':
+                startJpegStream(monitorId)
+            break;
+            case'b64':
+                if(loadedPlayer.Base64 && loadedPlayer.Base64.connected){
+                    loadedPlayer.Base64.disconnect()
+                }
+                loadedPlayer.Base64 = io(location.origin,{ path: websocketPath, query: websocketQuery, transports: ['websocket'], forceNew: false})
+                var ws = loadedPlayer.Base64
+                var buffer
+                ws.on('diconnect',function(){
+                    console.log('Base64 Stream Disconnected')
                 })
-                if(!loadedPlayer.ctx || loadedPlayer.ctx.length === 0){
-                    loadedPlayer.ctx = containerElement.find('canvas');
-                }
-                var ctx = loadedPlayer.ctx[0]
-                var ctx2d = ctx.getContext("2d")
-                loadedPlayer.image = new Image()
-                var image = loadedPlayer.image
-                image.onload = function() {
-                    loadedPlayer.imageLoading = false
-                    var x = 0
-                    var y = 0
-                    ctx.getContext("2d").drawImage(image,x,y,ctx.width,ctx.height)
-                    URL.revokeObjectURL(loadedPlayer.imageUrl)
-                }
-                ws.on('data',function(imageData){
-                    try{
-                        if(loadedPlayer.imageLoading === true)return console.log('drop');
-                        loadedPlayer.imageLoading = true
-                        var arrayBufferView = new Uint8Array(imageData);
-                        var blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } );
-                        loadedPlayer.imageUrl = URL.createObjectURL( blob );
-                        loadedPlayer.image.src = loadedPlayer.imageUrl
-                        loadedPlayer.last_frame = 'data:image/jpeg;base64,'+base64ArrayBuffer(imageData)
-                    }catch(er){
-                        debugLog('base64 frame')
+                ws.on('connect',function(){
+                    ws.emit('Base64',{
+                        auth: $user.auth_token,
+                        uid: $user.uid,
+                        ke: monitor.ke,
+                        id: monitor.mid,
+                        channel: subStreamChannel
+                    })
+                    if(!loadedPlayer.ctx || loadedPlayer.ctx.length === 0){
+                        loadedPlayer.ctx = containerElement.find('canvas');
                     }
-                    // $.ccio.init('signal',d);
+                    var ctx = loadedPlayer.ctx[0]
+                    var ctx2d = ctx.getContext("2d")
+                    loadedPlayer.image = new Image()
+                    var image = loadedPlayer.image
+                    image.onload = function() {
+                        loadedPlayer.imageLoading = false
+                        var x = 0
+                        var y = 0
+                        ctx.getContext("2d").drawImage(image,x,y,ctx.width,ctx.height)
+                        URL.revokeObjectURL(loadedPlayer.imageUrl)
+                    }
+                    ws.on('data',function(imageData){
+                        try{
+                            if(loadedPlayer.imageLoading === true)return console.log('drop');
+                            loadedPlayer.imageLoading = true
+                            var arrayBufferView = new Uint8Array(imageData);
+                            var blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } );
+                            loadedPlayer.imageUrl = URL.createObjectURL( blob );
+                            loadedPlayer.image.src = loadedPlayer.imageUrl
+                            loadedPlayer.last_frame = 'data:image/jpeg;base64,'+base64ArrayBuffer(imageData)
+                        }catch(er){
+                            debugLog('base64 frame')
+                        }
+                        // $.ccio.init('signal',d);
+                    })
                 })
-            })
-        break;
-        case'mp4':
-            setTimeout(function(){
-                var stream = containerElement.find('.stream-element');
-                var onPoseidonError = function(){
-                    // setTimeout(function(){
-                    //     mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
-                    // },2000)
-                }
-                if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
-                if(loadedPlayer.PoseidonErrorCount >= 5)return
-                if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
-                    if(loadedPlayer.Poseidon){
-                        loadedPlayer.Poseidon.stop()
+            break;
+            case'mp4':
+                setTimeout(function(){
+                    var stream = containerElement.find('.stream-element');
+                    var onPoseidonError = function(){
+                        // setTimeout(function(){
+                        //     mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
+                        // },2000)
+                    }
+                    if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
+                    if(loadedPlayer.PoseidonErrorCount >= 5)return
+                    if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
+                        if(loadedPlayer.Poseidon){
+                            loadedPlayer.Poseidon.stop()
+                            revokeVideoPlayerUrl(monitorId)
+                        }
+                        try{
+                            loadedPlayer.Poseidon = new Poseidon({
+                                video: stream[0],
+                                auth_token: $user.auth_token,
+                                ke: monitor.ke,
+                                uid: $user.uid,
+                                id: monitor.mid,
+                                url: location.origin,
+                                path: websocketPath,
+                                query: websocketQuery,
+                                onError : onPoseidonError,
+                                channel : subStreamChannel
+                            })
+                            loadedPlayer.Poseidon.start();
+                        }catch(err){
+                            // onPoseidonError()
+                            console.log('onTryPoseidonError',err)
+                        }
+                    }else{
+                        stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
+                        stream[0].onerror = function(err){
+                            console.error(err)
+                        }
+                    }
+                },1000)
+            break;
+            case'flv':
+                if (flvjs.isSupported()) {
+                    if(loadedPlayer.flv){
+                        loadedPlayer.flv.destroy()
                         revokeVideoPlayerUrl(monitorId)
                     }
-                    try{
-                        loadedPlayer.Poseidon = new Poseidon({
-                            video: stream[0],
+                    var options = {};
+                    if(monitor.details.stream_flv_type==='ws'){
+                        if(monitor.details.stream_flv_maxLatency&&monitor.details.stream_flv_maxLatency!==''){
+                            monitor.details.stream_flv_maxLatency = parseInt(monitor.details.stream_flv_maxLatency)
+                        }else{
+                            monitor.details.stream_flv_maxLatency = 20000;
+                        }
+                        options = {
+                            type: 'flv',
+                            isLive: true,
                             auth_token: $user.auth_token,
                             ke: monitor.ke,
                             uid: $user.uid,
                             id: monitor.mid,
+                            maxLatency: monitor.details.stream_flv_maxLatency,
+                            hasAudio:false,
                             url: location.origin,
                             path: websocketPath,
-                            query: websocketQuery,
-                            onError : onPoseidonError,
-                            channel : subStreamChannel
-                        })
-                        loadedPlayer.Poseidon.start();
-                    }catch(err){
-                        // onPoseidonError()
-                        console.log('onTryPoseidonError',err)
-                    }
-                }else{
-                    stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
-                    stream[0].onerror = function(err){
-                        console.error(err)
-                    }
-                }
-            },1000)
-        break;
-        case'flv':
-            if (flvjs.isSupported()) {
-                if(loadedPlayer.flv){
-                    loadedPlayer.flv.destroy()
-                    revokeVideoPlayerUrl(monitorId)
-                }
-                var options = {};
-                if(monitor.details.stream_flv_type==='ws'){
-                    if(monitor.details.stream_flv_maxLatency&&monitor.details.stream_flv_maxLatency!==''){
-                        monitor.details.stream_flv_maxLatency = parseInt(monitor.details.stream_flv_maxLatency)
+                            channel : subStreamChannel,
+                            query: websocketQuery
+                        }
                     }else{
-                        monitor.details.stream_flv_maxLatency = 20000;
-                    }
-                    options = {
-                        type: 'flv',
-                        isLive: true,
-                        auth_token: $user.auth_token,
-                        ke: monitor.ke,
-                        uid: $user.uid,
-                        id: monitor.mid,
-                        maxLatency: monitor.details.stream_flv_maxLatency,
-                        hasAudio:false,
-                        url: location.origin,
-                        path: websocketPath,
-                        channel : subStreamChannel,
-                        query: websocketQuery
-                    }
-                }else{
-                    options = {
-                        type: 'flv',
-                        isLive: true,
-                        url: getApiPrefix(`flv`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.flv'
-                    }
-                }
-                loadedPlayer.flv = flvjs.createPlayer(options);
-                loadedPlayer.flv.attachMediaElement(containerElement.find('.stream-element')[0]);
-                loadedPlayer.flv.on('error',function(err){
-                    console.log(err)
-                });
-                loadedPlayer.flv.load();
-                loadedPlayer.flv.play();
-            }else{
-                new PNotify({title:'Stream cannot be started',text:'FLV.js is not supported on this browser. Try another stream type.',type:'error'});
-            }
-        break;
-        case'hls':
-            function createSteamNow(){
-                clearTimeout(loadedPlayer.m3uCheck)
-                var url = getApiPrefix(`hls`) + '/' + monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '') + '/s.m3u8'
-                $.get(url,function(m3u){
-                    if(m3u == 'File Not Found'){
-                        loadedPlayer.m3uCheck = setTimeout(function(){
-                            createSteamNow()
-                        },2000)
-                    }else{
-                        var video = containerElement.find('.stream-element')[0]
-                        if (isAppleDevice) {
-                            video.src = url;
-                            video.addEventListener('loadedmetadata', function() {
-                              setTimeout(function(){
-                                video.play();
-                              },3000)
-                            }, false);
-                        }else{
-                            var hlsOptions = safeJsonParse(dashboardOptions().hlsOptions) || {}
-                            if(hlsOptions instanceof String){
-                                hlsOptions = {}
-                                new PNotify({
-                                    title: lang['Invalid JSON'],
-                                    text: lang.hlsOptionsInvalid,
-                                    type: `warning`,
-                                })
-                            }
-                            if(loadedPlayer.hls){
-                                loadedPlayer.hls.destroy()
-                                revokeVideoPlayerUrl(monitorId)
-                            }
-                            loadedPlayer.hls = new Hls(hlsOptions)
-                            loadedPlayer.hls.loadSource(url)
-                            loadedPlayer.hls.attachMedia(video)
-                            loadedPlayer.hls.on(Hls.Events.MANIFEST_PARSED,function() {
-                                if (video.paused) {
-                                    video.play();
-                                }
-                            });
+                        options = {
+                            type: 'flv',
+                            isLive: true,
+                            url: getApiPrefix(`flv`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.flv'
                         }
                     }
-                })
-            }
-            createSteamNow()
-        break;
-        case'mjpeg':
-            var liveStreamElement = containerElement.find('.stream-element')
-            var setSource = function(){
-                liveStreamElement.attr('src',getApiPrefix(`mjpeg`)+'/'+monitorId + (subStreamChannel ? `/${subStreamChannel}` : ''))
-                liveStreamElement.unbind('ready')
-                liveStreamElement.ready(function(){
+                    loadedPlayer.flv = flvjs.createPlayer(options);
+                    loadedPlayer.flv.attachMediaElement(containerElement.find('.stream-element')[0]);
+                    loadedPlayer.flv.on('error',function(err){
+                        console.log(err)
+                    });
+                    loadedPlayer.flv.load();
+                    loadedPlayer.flv.play();
+                }else{
+                    new PNotify({title:'Stream cannot be started',text:'FLV.js is not supported on this browser. Try another stream type.',type:'error'});
+                }
+            break;
+            case'hls':
+                function createSteamNow(){
+                    clearTimeout(loadedPlayer.m3uCheck)
+                    var url = getApiPrefix(`hls`) + '/' + monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '') + '/s.m3u8'
+                    $.get(url,function(m3u){
+                        if(m3u == 'File Not Found'){
+                            loadedPlayer.m3uCheck = setTimeout(function(){
+                                createSteamNow()
+                            },2000)
+                        }else{
+                            var video = containerElement.find('.stream-element')[0]
+                            if (isAppleDevice) {
+                                video.src = url;
+                                video.addEventListener('loadedmetadata', function() {
+                                  setTimeout(function(){
+                                    video.play();
+                                  },3000)
+                                }, false);
+                            }else{
+                                var hlsOptions = safeJsonParse(dashboardOptions().hlsOptions) || {}
+                                if(hlsOptions instanceof String){
+                                    hlsOptions = {}
+                                    new PNotify({
+                                        title: lang['Invalid JSON'],
+                                        text: lang.hlsOptionsInvalid,
+                                        type: `warning`,
+                                    })
+                                }
+                                if(loadedPlayer.hls){
+                                    loadedPlayer.hls.destroy()
+                                    revokeVideoPlayerUrl(monitorId)
+                                }
+                                loadedPlayer.hls = new Hls(hlsOptions)
+                                loadedPlayer.hls.loadSource(url)
+                                loadedPlayer.hls.attachMedia(video)
+                                loadedPlayer.hls.on(Hls.Events.MANIFEST_PARSED,function() {
+                                    if (video.paused) {
+                                        video.play();
+                                    }
+                                });
+                            }
+                        }
+                    })
+                }
+                createSteamNow()
+            break;
+            case'mjpeg':
+                var liveStreamElement = containerElement.find('.stream-element')
+                var setSource = function(){
+                    liveStreamElement.attr('src',getApiPrefix(`mjpeg`)+'/'+monitorId + (subStreamChannel ? `/${subStreamChannel}` : ''))
+                    liveStreamElement.unbind('ready')
+                    liveStreamElement.ready(function(){
+                        setTimeout(function(){
+                            liveStreamElement.contents().find("body").append('<style>img{width:100%;height:100%}</style>')
+                        },1000)
+                    })
+                }
+                setSource()
+                liveStreamElement.on('error',function(err){
                     setTimeout(function(){
-                        liveStreamElement.contents().find("body").append('<style>img{width:100%;height:100%}</style>')
-                    },1000)
+                        setSource()
+                    },4000)
                 })
-            }
-            setSource()
-            liveStreamElement.on('error',function(err){
-                setTimeout(function(){
-                    setSource()
-                },4000)
-            })
-        break;
-    }
+            break;
+        }
     $.each(onLiveStreamInitiateExtensions,function(n,extender){
         extender(streamType,monitor,loadedPlayer,subStreamChannel)
     })
@@ -662,13 +667,15 @@ function loadPreviouslyOpenedLiveGridBlocks(){
     })
 }
 function closeAllLiveGridPlayers(rememberClose){
-    var watchedOn = dashboardOptions().watch_on || {}
-    $.each(watchedOn,function(n,groupOfMons){
-        $.each(groupOfMons,function(monitorId,monitor){
-            if(monitor === 1){
-                mainSocket.f({f:'monitor',ff:'watch_off',id: monitorId})
-            }
+    $.each(loadedMonitors,function(monitorId,monitor){
+        mainSocket.f({
+            f: 'monitor',
+            ff: 'watch_off',
+            id: monitor.mid
         })
+        setTimeout(function(){
+            saveLiveGridBlockOpenState(monitorId,$user.ke,0)
+        },1000)
     })
 }
 function saveLiveGridBlockOpenState(monitorId,groupKey,state){
@@ -870,6 +877,70 @@ function signalCheckLiveStream(options){
         delete(liveGridData.signal);
     }
 }
+
+function pauseMonitorItem(monitorId){
+    liveGridPlayingNow[monitorId] = false
+    closeLiveGridPlayer(monitorId,false)
+}
+function resumeMonitorItem(monitorId){
+    // needs to know about substream
+    liveGridPlayingNow[monitorId] = true
+    resetMonitorCanvas(monitorId,true,null)
+}
+function isScrolledIntoView(elem){
+    var el = $(elem)
+    var theWindow = $(window)
+    var docViewTop = theWindow.scrollTop();
+    var docViewBottom = docViewTop + theWindow.height();
+
+    var elemTop = el.offset().top;
+    var elemBottom = elemTop + el.height();
+
+    return (
+        elemTop >= docViewTop && elemTop <= docViewBottom ||
+        elemBottom >= docViewTop && elemBottom <= docViewBottom
+    );
+}
+function pauseAllLiveGridPlayers(unpause){
+    $('.monitor_item').each(function(n,el){
+        var monitorId = $(el).attr('data-mid')
+        if(!unpause){
+            pauseMonitorItem(monitorId)
+        }else{
+            resumeMonitorItem(monitorId)
+        }
+    })
+}
+function setPauseStatusForMonitorItems(){
+    $('.monitor_item').each(function(n,el){
+        var monitorId = $(el).attr('data-mid')
+        var isVisible = isScrolledIntoView(el)
+        console.log(monitorId,isVisible)
+        if(isVisible){
+            if(!liveGridPlayingNow[monitorId])resumeMonitorItem(monitorId);
+        }else{
+            pauseMonitorItem(monitorId)
+        }
+    })
+}
+function setPauseScrollTimeout(){
+    clearTimeout(liveGridPauseScrollTimeout)
+    if(tabTree.name === 'liveGrid'){
+        liveGridPauseScrollTimeout = setTimeout(function(){
+            setPauseStatusForMonitorItems()
+        },700)
+    }
+}
+function openAllLiveGridPlayers(){
+    $.each(loadedMonitors,function(monitorId,monitor){
+        mainSocket.f({
+            f: 'monitor',
+            ff: 'watch_on',
+            id: monitor.mid
+        })
+        openLiveGrid()
+    })
+}
 $(document).ready(function(e){
     liveGrid
     .on('dblclick','.stream-block',function(){
@@ -882,16 +953,16 @@ $(document).ready(function(e){
     })
     .on('click','.launch-live-grid-monitor',function(){
         var monitorId = $(this).parents('[data-mid]').attr('data-mid')
-        // if(isMobile){
-        //     createLivePlayerTab(loadedMonitors[monitorId])
-        // }else{
+        if(isMobile){
+            createLivePlayerTab(loadedMonitors[monitorId])
+        }else{
             mainSocket.f({
                 f: 'monitor',
                 ff: 'watch_on',
                 id: monitorId
             })
             openLiveGrid()
-        // }
+        }
     })
     .on('click','.monitor-live-group-open',function(){
         var monitorIds = $(this).attr('monitor-ids').split(',')
@@ -1012,26 +1083,10 @@ $(document).ready(function(e){
         });
     })
     $('.open-all-monitors').click(function(){
-        $.each(loadedMonitors,function(monitorId,monitor){
-            mainSocket.f({
-                f: 'monitor',
-                ff: 'watch_on',
-                id: monitor.mid
-            })
-            openLiveGrid()
-        })
+        openAllLiveGridPlayers()
     })
     $('.close-all-monitors').click(function(){
-        $.each(loadedMonitors,function(monitorId,monitor){
-            mainSocket.f({
-                f: 'monitor',
-                ff: 'watch_off',
-                id: monitor.mid
-            })
-            setTimeout(function(){
-                saveLiveGridBlockOpenState(monitorId,$user.ke,0)
-            },1000)
-        })
+        closeAllLiveGridPlayers()
     });
     var dontShowDetectionSelectionOnStart = dashboardOptions().dontShowDetection != 1
     liveGridData = GridStack.init();
@@ -1042,14 +1097,14 @@ $(document).ready(function(e){
         },700)
     })
     .on('resizestop', function(){
-        // resetAllLiveGridDimensionsInMemory()
+        resetAllLiveGridDimensionsInMemory()
         saveLiveGridBlockPositions()
     });
     addOnTabReopen('liveGrid', function () {
-        loadPreviouslyOpenedLiveGridBlocks()
+        pauseAllLiveGridPlayers(true)
     })
     addOnTabAway('liveGrid', function () {
-        closeAllLiveGridPlayers()
+        pauseAllLiveGridPlayers(false)
     })
     onInitWebsocket(function (d){
         loadPreviouslyOpenedLiveGridBlocks()
@@ -1059,9 +1114,6 @@ $(document).ready(function(e){
     })
     onWebSocketEvent(function (d){
         switch(d.f){
-            case'init_success':
-                // loadPreviouslyOpenedLiveGridBlocks()
-            break;
             case'video_build_success':
                 d.status = 1
                 d.mid = d.id || d.mid
@@ -1185,12 +1237,14 @@ $(document).ready(function(e){
     })
     $(window).focus(function(){
         if(canBackgroundStream()){
-            loadPreviouslyOpenedLiveGridBlocks()
+            pauseAllLiveGridPlayers(true)
         }
     }).blur(function(){
         if(canBackgroundStream()){
-            closeAllLiveGridPlayers()
+            pauseAllLiveGridPlayers(false)
         }
+    }).scroll(function(){
+        setPauseScrollTimeout()
     })
     dashboardSwitchCallbacks.monitorOrder = function(toggleState){
         if(toggleState !== 1){
diff --git a/web/assets/js/bs5.monitorSettings.js b/web/assets/js/bs5.monitorSettings.js
index 91bcab67..8ceb2315 100644
--- a/web/assets/js/bs5.monitorSettings.js
+++ b/web/assets/js/bs5.monitorSettings.js
@@ -618,7 +618,7 @@ function importIntoMonitorEditor(options){
             input_map_choices = monitorDetails.input_map_choices;
         }
         $.each(input_map_choices,function(n,v){
-            $.each(v,function(m,b){
+            $.each(safeJsonParse(v),function(m,b){
                 var parent = $('[input-mapping="'+n+'"] .choices')
                 drawInputMapSelectorHtml(b,parent)
             })
diff --git a/web/assets/js/bs5.monitorsUtils.js b/web/assets/js/bs5.monitorsUtils.js
index 706a9e00..8c473a18 100644
--- a/web/assets/js/bs5.monitorsUtils.js
+++ b/web/assets/js/bs5.monitorsUtils.js
@@ -647,7 +647,7 @@ function drawMatrices(event,options){
     var html = ''
     $.each(event.details.matrices,function(n,matrix){
         html += `<div class="stream-detected-object" name="${objectTagGroup}" style="height:${heightRatio * matrix.height}px;width:${widthRatio * matrix.width}px;top:${heightRatio * matrix.y}px;left:${widthRatio * matrix.x}px;">`
-        if(matrix.tag)html += `<span class="tag">${matrix.tag}</span>`
+        if(matrix.tag)html += `<span class="tag">${matrix.tag}${!isNaN(matrix.id) ? ` <small class="label label-default">${matrix.id}</small>`: ''}</span>`
         html += '</div>'
     })
     theContainer.append(html)
diff --git a/web/assets/js/bs5.powerVideo.js b/web/assets/js/bs5.powerVideo.js
index c2d34829..71ee360f 100644
--- a/web/assets/js/bs5.powerVideo.js
+++ b/web/assets/js/bs5.powerVideo.js
@@ -322,7 +322,7 @@ $(document).ready(function(e){
         var html = ''
         $.each(event.details.matrices,function(n,matrix){
             html += `<div class="stream-detected-object" name="${event.details.name}" style="height:${heightRatio * matrix.height}px;width:${widthRatio * matrix.width}px;top:${heightRatio * matrix.y}px;left:${widthRatio * matrix.x}px;">`
-            if(matrix.tag)html += `<span class="tag">${matrix.tag}</span>`
+            if(matrix.tag)html += `<span class="tag">${matrix.tag}${!isNaN(matrix.id) ? ` <small class="label label-default">${matrix.id}</small>`: ''}</span>`
             html += '</div>'
         })
         streamObjectsContainer.append(html)