diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue
index e277966e..9777f28e 100644
--- a/client/components/app/StreamContainer.vue
+++ b/client/components/app/StreamContainer.vue
@@ -378,7 +378,7 @@ export default {
       }
     },
     streamReady() {
-      console.log(`[STREAM-CONTAINER] Stream Ready`)
+      console.log(`[StreamContainer] Stream Ready`)
       if (this.$refs.audioPlayer) {
         this.$refs.audioPlayer.setStreamReady()
       } else {
diff --git a/client/components/tables/BackupsTable.vue b/client/components/tables/BackupsTable.vue
index eb16dadb..98f017ad 100644
--- a/client/components/tables/BackupsTable.vue
+++ b/client/components/tables/BackupsTable.vue
@@ -64,13 +64,11 @@ export default {
       showConfirmApply: false,
       selectedBackup: null,
       isBackingUp: false,
-      processing: false
+      processing: false,
+      backups: []
     }
   },
   computed: {
-    backups() {
-      return this.$store.state.backups || []
-    },
     userToken() {
       return this.$store.getters['user/getToken']
     }
@@ -96,9 +94,8 @@ export default {
         this.processing = true
         this.$axios
           .$delete(`/api/backups/${backup.id}`)
-          .then((backups) => {
-            console.log('Backup deleted', backups)
-            this.$store.commit('setBackups', backups)
+          .then((data) => {
+            this.setBackups(data.backups || [])
             this.$toast.success(this.$strings.ToastBackupDeleteSuccess)
             this.processing = false
           })
@@ -117,10 +114,10 @@ export default {
       this.isBackingUp = true
       this.$axios
         .$post('/api/backups')
-        .then((backups) => {
+        .then((data) => {
           this.isBackingUp = false
           this.$toast.success(this.$strings.ToastBackupCreateSuccess)
-          this.$store.commit('setBackups', backups)
+          this.setBackups(data.backups || [])
         })
         .catch((error) => {
           this.isBackingUp = false
@@ -136,9 +133,8 @@ export default {
 
       this.$axios
         .$post('/api/backups/upload', form)
-        .then((result) => {
-          console.log('Upload backup result', result)
-          this.$store.commit('setBackups', result)
+        .then((data) => {
+          this.setBackups(data.backups || [])
           this.$toast.success(this.$strings.ToastBackupUploadSuccess)
           this.processing = false
         })
@@ -148,9 +144,29 @@ export default {
           this.$toast.error(errorMessage)
           this.processing = false
         })
+    },
+    setBackups(backups) {
+      backups.sort((a, b) => b.createdAt - a.createdAt)
+      this.backups = backups
+    },
+    loadBackups() {
+      this.processing = true
+      this.$axios
+        .$get('/api/backups')
+        .then((data) => {
+          this.setBackups(data.backups || [])
+        })
+        .catch((error) => {
+          console.error('Failed to load backups', error)
+          this.$toast.error('Failed to load backups')
+        })
+        .finally(() => {
+          this.processing = false
+        })
     }
   },
   mounted() {
+    this.loadBackups()
     if (this.$route.query.backup) {
       this.$toast.success('Backup applied successfully')
       this.$router.replace('/config')
diff --git a/client/components/tables/UsersTable.vue b/client/components/tables/UsersTable.vue
index c4459b0d..8fb644ea 100644
--- a/client/components/tables/UsersTable.vue
+++ b/client/components/tables/UsersTable.vue
@@ -26,11 +26,9 @@
           </td>
           <td class="text-sm">{{ user.type }}</td>
           <td class="hidden lg:table-cell">
-            <div v-if="usersOnline[user.id] && usersOnline[user.id].session && usersOnline[user.id].session.libraryItem && usersOnline[user.id].session.libraryItem.media">
-              <p class="truncate text-xs">Listening: {{ usersOnline[user.id].session.libraryItem.media.metadata.title || '' }}</p>
-            </div>
-            <div v-else-if="user.mostRecent">
-              <p class="truncate text-xs">Last: {{ user.mostRecent.metadata.title }}</p>
+            <div v-if="usersOnline[user.id]">
+              <p v-if="usersOnline[user.id].session && usersOnline[user.id].session.libraryItem" class="truncate text-xs">Listening: {{ usersOnline[user.id].session.libraryItem.media.metadata.title || '' }}</p>
+              <p v-else-if="usersOnline[user.id].mostRecent && usersOnline[user.id].mostRecent.media" class="truncate text-xs">Last: {{ usersOnline[user.id].mostRecent.media.metadata.title }}</p>
             </div>
           </td>
           <td class="text-xs font-mono hidden sm:table-cell">
@@ -81,7 +79,7 @@ export default {
     },
     usersOnline() {
       var usermap = {}
-      this.$store.state.users.users.forEach((u) => (usermap[u.id] = { online: true, session: u.session }))
+      this.$store.state.users.usersOnline.forEach((u) => (usermap[u.id] = u))
       return usermap
     }
   },
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index 4d9d1438..bb93a054 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -132,14 +132,8 @@ export default {
         }
       })
 
-      if (payload.backups && payload.backups.length) {
-        this.$store.commit('setBackups', payload.backups)
-      }
       if (payload.usersOnline) {
-        this.$store.commit('users/resetUsers')
-        payload.usersOnline.forEach((user) => {
-          this.$store.commit('users/updateUser', user)
-        })
+        this.$store.commit('users/setUsersOnline', payload.usersOnline)
       }
 
       this.$eventBus.$emit('socket_init')
@@ -286,13 +280,13 @@ export default {
       }
     },
     userOnline(user) {
-      this.$store.commit('users/updateUser', user)
+      this.$store.commit('users/updateUserOnline', user)
     },
     userOffline(user) {
-      this.$store.commit('users/removeUser', user)
+      this.$store.commit('users/removeUserOnline', user)
     },
     userStreamUpdate(user) {
-      this.$store.commit('users/updateUser', user)
+      this.$store.commit('users/updateUserOnline', user)
     },
     userMediaProgressUpdate(payload) {
       this.$store.commit('user/updateMediaProgress', payload)
@@ -333,6 +327,9 @@ export default {
         this.$toast.info(toast)
       }
     },
+    adminMessageEvt(message) {
+      this.$toast.info(message)
+    },
     initializeSocket() {
       this.socket = this.$nuxtSocket({
         name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
@@ -345,6 +342,7 @@ export default {
       this.$root.socket = this.socket
       console.log('Socket initialized')
 
+      // Pre-defined socket events
       this.socket.on('connect', this.connect)
       this.socket.on('connect_error', this.connectError)
       this.socket.on('disconnect', this.disconnect)
@@ -353,6 +351,7 @@ export default {
       this.socket.io.on('reconnect_error', this.reconnectError)
       this.socket.io.on('reconnect_failed', this.reconnectFailed)
 
+      // Event received after authorizing socket
       this.socket.on('init', this.init)
 
       // Stream Listeners
@@ -403,6 +402,8 @@ export default {
       this.socket.on('backup_applied', this.backupApplied)
 
       this.socket.on('batch_quickmatch_complete', this.batchQuickMatchComplete)
+
+      this.socket.on('admin_message', this.adminMessageEvt)
     },
     showUpdateToast(versionData) {
       var ignoreVersion = localStorage.getItem('ignoreVersion')
diff --git a/client/store/index.js b/client/store/index.js
index 4ddb5a3c..9b27abd4 100644
--- a/client/store/index.js
+++ b/client/store/index.js
@@ -21,7 +21,6 @@ export const state = () => ({
   processingBatch: false,
   previousPath: '/',
   showExperimentalFeatures: false,
-  backups: [],
   bookshelfBookIds: [],
   openModal: null,
   innerModalOpen: false,
@@ -245,9 +244,6 @@ export const mutations = {
     state.showExperimentalFeatures = val
     localStorage.setItem('experimental', val ? 1 : 0)
   },
-  setBackups(state, val) {
-    state.backups = val.sort((a, b) => b.createdAt - a.createdAt)
-  },
   setOpenModal(state, val) {
     state.openModal = val
   },
diff --git a/client/store/users.js b/client/store/users.js
index 131b7d8c..2439ee31 100644
--- a/client/store/users.js
+++ b/client/store/users.js
@@ -1,11 +1,11 @@
 
 export const state = () => ({
-  users: []
+  usersOnline: []
 })
 
 export const getters = {
   getIsUserOnline: state => id => {
-    return state.users.find(u => u.id === id)
+    return state.usersOnline.find(u => u.id === id)
   }
 }
 
@@ -14,18 +14,18 @@ export const actions = {
 }
 
 export const mutations = {
-  resetUsers(state) {
-    state.users = []
+  setUsersOnline(state, usersOnline) {
+    state.usersOnline = usersOnline
   },
-  updateUser(state, user) {
-    var index = state.users.findIndex(u => u.id === user.id)
+  updateUserOnline(state, user) {
+    var index = state.usersOnline.findIndex(u => u.id === user.id)
     if (index >= 0) {
-      state.users.splice(index, 1, user)
+      state.usersOnline.splice(index, 1, user)
     } else {
-      state.users.push(user)
+      state.usersOnline.push(user)
     }
   },
-  removeUser(state, user) {
-    state.users = state.users.filter(u => u.id !== user.id)
+  removeUserOnline(state, user) {
+    state.usersOnline = state.usersOnline.filter(u => u.id !== user.id)
   }
 }
\ No newline at end of file
diff --git a/client/strings/pl.json b/client/strings/pl.json
index 05b3d17d..a9fdffb0 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -13,7 +13,7 @@
   "ButtonCheckAndDownloadNewEpisodes": "Sprawdź i pobierz nowe odcinki",
   "ButtonChooseAFolder": "Wybierz folder",
   "ButtonChooseFiles": "Wybierz pliki",
-  "ButtonClearFilter": "Clear Filter",
+  "ButtonClearFilter": "Wyczyść filtr",
   "ButtonCloseFeed": "Zamknij kanał",
   "ButtonCollections": "Kolekcje",
   "ButtonConfigureScanner": "Configure Scanner",
@@ -44,8 +44,8 @@
   "ButtonPurgeAllCache": "Wyczyść dane tymczasowe",
   "ButtonPurgeItemsCache": "Wyczyść dane tymczasowe pozycji",
   "ButtonPurgeMediaProgress": "Wyczyść postęp",
-  "ButtonQueueAddItem": "Add to queue",
-  "ButtonQueueRemoveItem": "Remove from queue",
+  "ButtonQueueAddItem": "Dodaj do kolejki",
+  "ButtonQueueRemoveItem": "Usuń z kolejki",
   "ButtonQuickMatch": "Szybkie dopasowanie",
   "ButtonRead": "Czytaj",
   "ButtonRemove": "Usuń",
@@ -68,7 +68,7 @@
   "ButtonShow": "Pokaż",
   "ButtonStartM4BEncode": "Eksportuj jako plik M4B",
   "ButtonStartMetadataEmbed": "Osadź metadane",
-  "ButtonSubmit": "Zgłoś",
+  "ButtonSubmit": "Zaloguj",
   "ButtonUpload": "Wgraj",
   "ButtonUploadBackup": "Wgraj kopię zapasową",
   "ButtonUploadCover": "Wgraj okładkę",
@@ -96,7 +96,7 @@
   "HeaderLastListeningSession": "Ostatnio odtwarzana sesja",
   "HeaderLatestEpisodes": "Najnowsze odcinki",
   "HeaderLibraries": "Biblioteki",
-  "HeaderLibraryFiles": "Library Files",
+  "HeaderLibraryFiles": "Pliki w bibliotece",
   "HeaderLibraryStats": "Statystyki biblioteki",
   "HeaderListeningSessions": "Sesje słuchania",
   "HeaderListeningStats": "Statystyki odtwarzania",
@@ -144,14 +144,14 @@
   "LabelAccountTypeGuest": "Gość",
   "LabelAccountTypeUser": "Użytkownik",
   "LabelActivity": "Aktywność",
-  "LabelAddedAt": "Added At",
+  "LabelAddedAt": "Dodano",
   "LabelAddToCollection": "Dodaj do kolekcji",
   "LabelAddToCollectionBatch": "Dodaj {0} książki do kolekcji",
   "LabelAll": "All",
   "LabelAllUsers": "Wszyscy użytkownicy",
   "LabelAuthor": "Autor",
-  "LabelAuthorFirstLast": "Author (First Last)",
-  "LabelAuthorLastFirst": "Author (Last, First)",
+  "LabelAuthorFirstLast": "Autor (Rosnąco)",
+  "LabelAuthorLastFirst": "Author (Malejąco)",
   "LabelAuthors": "Autorzy",
   "LabelAutoDownloadEpisodes": "Automatyczne pobieranie odcinków",
   "LabelBackToUser": "Powrót",
@@ -165,7 +165,7 @@
   "LabelChangePassword": "Zmień hasło",
   "LabelChaptersFound": "Znalezione rozdziały",
   "LabelChapterTitle": "Tytuł rozdziału",
-  "LabelClosePlayer": "Close player",
+  "LabelClosePlayer": "Zamknij odtwarzacz",
   "LabelCollapseSeries": "Podsumuj serię",
   "LabelCollections": "Kolekcje",
   "LabelComplete": "Ukończone",
@@ -198,15 +198,15 @@
   "LabelExplicit": "Nieprzyzwoite",
   "LabelFeedURL": "URL kanału",
   "LabelFile": "Plik",
-  "LabelFileBirthtime": "File Birthtime",
-  "LabelFileModified": "File Modified",
+  "LabelFileBirthtime": "Data utworzenia pliku",
+  "LabelFileModified": "Data modyfikacji pliku",
   "LabelFilename": "Nazwa pliku",
   "LabelFilterByUser": "Filtruj według danego użytkownika",
   "LabelFindEpisodes": "Znajdź odcinki",
   "LabelFinished": "Zakończone",
   "LabelFolder": "Folder",
   "LabelFolders": "Foldery",
-  "LabelGenre": "Genre",
+  "LabelGenre": "Gatunek",
   "LabelGenres": "Gatunki",
   "LabelHardDeleteFile": "Usuń trwale plik",
   "LabelHour": "Godzina",
@@ -215,14 +215,14 @@
   "LabelIncomplete": "Nieukończone",
   "LabelInProgress": "W trakcie",
   "LabelInterval": "Interwał",
-  "LabelIntervalCustomDailyWeekly": "Custom daily/weekly",
-  "LabelIntervalEvery12Hours": "Every 12 hours",
-  "LabelIntervalEvery15Minutes": "Every 15 minutes",
-  "LabelIntervalEvery2Hours": "Every 2 hours",
-  "LabelIntervalEvery30Minutes": "Every 30 minutes",
-  "LabelIntervalEvery6Hours": "Every 6 hours",
-  "LabelIntervalEveryDay": "Every day",
-  "LabelIntervalEveryHour": "Every hour",
+  "LabelIntervalCustomDailyWeekly": "Niestandardowy dzienny/tygodniowy",
+  "LabelIntervalEvery12Hours": "Co 12 godzin",
+  "LabelIntervalEvery15Minutes": "Co 15 minut",
+  "LabelIntervalEvery2Hours": "Co 2 godziny",
+  "LabelIntervalEvery30Minutes": "Co 30 minut",
+  "LabelIntervalEvery6Hours": "Co 6 godzin",
+  "LabelIntervalEveryDay": "Każdego dnia",
+  "LabelIntervalEveryHour": "Każdej godziny",
   "LabelInvalidParts": "Nieprawidłowe części",
   "LabelItem": "Pozycja",
   "LabelLanguage": "Język",
@@ -238,8 +238,8 @@
   "LabelLimit": "Limit",
   "LabelListenAgain": "Słuchaj ponownie",
   "LabelLogLevelDebug": "Debug",
-  "LabelLogLevelInfo": "Info",
-  "LabelLogLevelWarn": "Warn",
+  "LabelLogLevelInfo": "Informacja",
+  "LabelLogLevelWarn": "Ostrzeżenie",
   "LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie",
   "LabelMarkSeries": "Oznacz serię",
   "LabelMediaPlayer": "Odtwarzacz",
@@ -268,9 +268,9 @@
   "LabelNotificationsMaxQueueSize": "Maksymalny rozmiar kolejki dla powiadomień",
   "LabelNotificationsMaxQueueSizeHelp": "Zdarzenia są ograniczone do 1 na sekundę. Zdarzenia będą ignorowane jeśli kolejka ma maksymalny rozmiar. Zapobiega to spamowaniu powiadomieniami.",
   "LabelNotificationTitleTemplate": "Szablon tytułu powiadmienia",
-  "LabelNotStarted": "Not Started",
-  "LabelNumberOfBooks": "Number of Books",
-  "LabelNumberOfEpisodes": "# of Episodes",
+  "LabelNotStarted": "Nie rozpoęczto",
+  "LabelNumberOfBooks": "Liczba książek",
+  "LabelNumberOfEpisodes": "# odcinków",
   "LabelOpenRSSFeed": "Otwórz kanał RSS",
   "LabelPassword": "Hasło",
   "LabelPath": "Ścieżka",
@@ -296,7 +296,7 @@
   "LabelRegion": "Region",
   "LabelReleaseDate": "Data wydania",
   "LabelRemoveCover": "Remove cover",
-  "LabelRSSFeedOpen": "RSS Feed Open",
+  "LabelRSSFeedOpen": "RSS Feed otwarty",
   "LabelRSSFeedSlug": "RSS Feed Slug",
   "LabelRSSFeedURL": "URL kanały RSS",
   "LabelSearchTerm": "Wyszukiwanie frazy",
@@ -306,7 +306,7 @@
   "LabelSequence": "Kolejność",
   "LabelSeries": "Serie",
   "LabelSeriesName": "Nazwy serii",
-  "LabelSeriesProgress": "Series Progress",
+  "LabelSeriesProgress": "Postęp w serii",
   "LabelSettingsBookshelfViewHelp": "Widok półki z ksiązkami",
   "LabelSettingsChromecastSupport": "Wsparcie Chromecast",
   "LabelSettingsDateFormat": "Format daty",
@@ -343,7 +343,7 @@
   "LabelSettingsStoreMetadataWithItemHelp": "Domyślnie metadane są przechowywane w folderze /metadata/items, włączenie tej opcji spowoduje, że okładka będzie przechowywana w folderze ksiązki. Tylko jedna okładka o nazwie pliku \"cover\" będzie przechowywana. Rozszerzenie pliku metadanych: .abs",
   "LabelShowAll": "Pokaż wszystko",
   "LabelSize": "Rozmiar",
-  "LabelSleepTimer": "Sleep timer",
+  "LabelSleepTimer": "Wyłącznik czasowy",
   "LabelStart": "Rozpocznij",
   "LabelStarted": "Rozpoczęty",
   "LabelStartedAt": "Rozpoczęto",
@@ -360,8 +360,8 @@
   "LabelStatsItemsInLibrary": "Pozycje w bibliotece",
   "LabelStatsMinutes": "Minuty",
   "LabelStatsMinutesListening": "Minuty odtwarzania",
-  "LabelStatsOverallDays": "Overall Days",
-  "LabelStatsOverallHours": "Overall Hours",
+  "LabelStatsOverallDays": "Całkowity czas (dni)",
+  "LabelStatsOverallHours": "Całkowity czas (godziny)",
   "LabelStatsWeekListening": "Tydzień odtwarzania",
   "LabelSubtitle": "Podtytuł",
   "LabelSupportedFileTypes": "Obsługiwane typy plików",
@@ -373,13 +373,13 @@
   "LabelTimeRemaining": "Pozostało {0}",
   "LabelTimeToShift": "Czas do przesunięcia w sekundach",
   "LabelTitle": "Tytuł",
-  "LabelToolsEmbedMetadata": "Embed Metadata",
-  "LabelToolsEmbedMetadataDescription": "Embed metadata into audio files including cover image and chapters.",
-  "LabelToolsMakeM4b": "Make M4B Audiobook File",
-  "LabelToolsMakeM4bDescription": "Generate a .M4B audiobook file with embedded metadata, cover image, and chapters.",
-  "LabelToolsSplitM4b": "Split M4B to MP3's",
-  "LabelToolsSplitM4bDescription": "Create MP3's from an M4B split by chapters with embedded metadata, cover image, and chapters.",
-  "LabelTotalDuration": "Total Duration",
+  "LabelToolsEmbedMetadata": "Załącz metadane",
+  "LabelToolsEmbedMetadataDescription": "Załącz metadane do plików audio (okładkę oraz znaczniki rozdziałów)",
+  "LabelToolsMakeM4b": "Generuj plik M4B",
+  "LabelToolsMakeM4bDescription": "Tworzy plik w formacie .M4B, który zawiera metadane, okładkę oraz rozdziały.",
+  "LabelToolsSplitM4b": "Podziel plik .M4B na pliki .MP3",
+  "LabelToolsSplitM4bDescription": "Podziel plik .M4B na pliki .MP3 na rozdziały z załączonymi metadanymi oraz okładką.",
+  "LabelTotalDuration": "TCałkowita długość",
   "LabelTotalTimeListened": "Całkowity czas odtwarzania",
   "LabelTrackFromFilename": "Ścieżka z nazwy pliku",
   "LabelTrackFromMetadata": "Ścieżka z metadanych",
@@ -387,33 +387,33 @@
   "LabelUnknown": "Nieznany",
   "LabelUpdateCover": "Zaktalizuj odkładkę",
   "LabelUpdateCoverHelp": "Umożliwienie nadpisania istniejących okładek dla wybranych książek w przypadku znalezienia dopasowania",
-  "LabelUpdatedAt": "Zaktualizaowano",
+  "LabelUpdatedAt": "Zaktualizowano",
   "LabelUpdateDetails": "Zaktualizuj szczegóły",
   "LabelUpdateDetailsHelp": "Umożliwienie nadpisania istniejących szczegółów dla wybranych książek w przypadku znalezienia dopasowania",
   "LabelUploaderDragAndDrop": "Przeciągnij i puść foldery lub pliki",
   "LabelUploaderDropFiles": "Puść pliki",
-  "LabelUseChapterTrack": "Use chapter track",
+  "LabelUseChapterTrack": "Użyj ścieżki rozdziału",
   "LabelUseFullTrack": "Użycie ścieżki rozdziału",
   "LabelUser": "Użytkownik",
   "LabelUsername": "Nazwa użytkownika",
   "LabelValue": "Wartość",
   "LabelVersion": "Wersja",
-  "LabelViewBookmarks": "View bookmarks",
-  "LabelViewChapters": "View chapters",
-  "LabelViewQueue": "View player queue",
-  "LabelVolume": "Volume",
+  "LabelViewBookmarks": "Wyświetlaj zakładki",
+  "LabelViewChapters": "Wyświetlaj rozdziały",
+  "LabelViewQueue": "Wyświetlaj kolejkę odtwarzania",
+  "LabelVolume": "Głośność",
   "LabelWeekdaysToRun": "Dni tygodnia",
   "LabelYourAudiobookDuration": "Czas trwania audiobooka",
   "LabelYourBookmarks": "Twoje zakładki",
   "LabelYourProgress": "Twój postęp",
-  "MessageAppriseDescription": "To use this feature you will need to have an instance of <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> running or an api that will handle those same requests. <br />The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at <code>http://192.168.1.1:8337</code> then you would put <code>http://192.168.1.1:8337/notify</code>.",
+  "MessageAppriseDescription": "Aby użyć tej funkcji, konieczne jest posiadanie instancji <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> albo innego rozwiązania, które obsługuje schemat zapytań Apprise. <br />URL do interfejsu API powinno być całkowitą ścieżką, np., jeśli Twoje API do powiadomień jest dostępne pod adresem <code>http://192.168.1.1:8337</code> to wpisany tutaj URL powinien mieć postać: <code>http://192.168.1.1:8337/notify</code>.",
   "MessageBackupsDescription": "Kopie zapasowe obejmują użytkowników, postępy użytkowników, szczegóły pozycji biblioteki, ustawienia serwera i obrazy przechowywane w",
   "MessageBackupsNote": "Kopie zapasowe nie obejmują żadnych plików przechowywanych w folderach biblioteki.",
   "MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.",
-  "MessageBookshelfNoCollections": "You haven't made any collections yet",
-  "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"",
-  "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
-  "MessageBookshelfNoSeries": "You have no series",
+  "MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji",
+  "MessageBookshelfNoResultsForFilter": "Nie znaleziono żadnych pozycji przy aktualnym filtrowaniu \"{0}: {1}\"",
+  "MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS",
+  "MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii",
   "MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka",
   "MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka",
   "MessageCheckingCron": "Sprawdzanie cron...",
@@ -456,7 +456,7 @@
   "MessageNoEpisodes": "Brak odcinków",
   "MessageNoFoldersAvailable": "Brak dostępnych folderów",
   "MessageNoGenres": "Brak gatunków",
-  "MessageNoIssues": "No Issues",
+  "MessageNoIssues": "Brak problemów",
   "MessageNoItems": "Brak elementów",
   "MessageNoItemsFound": "Nie znaleziono żadnych elemntów",
   "MessageNoListeningSessions": "Brak sesji odtwarzania",
@@ -466,16 +466,16 @@
   "MessageNoPodcastsFound": "Nie znaleziono podcastów",
   "MessageNoResults": "Brak wyników",
   "MessageNoSearchResultsFor": "Brak wyników wyszukiwania dla \"{0}\"",
-  "MessageNotYetImplemented": "Not yet implemented",
+  "MessageNotYetImplemented": "Jeszcze nie zaimplementowane",
   "MessageNoUpdateNecessary": "Brak konieczności aktualizacji",
   "MessageNoUpdatesWereNecessary": "Brak aktualizacji",
-  "MessageOr": "or",
-  "MessagePauseChapter": "Pause chapter playback",
-  "MessagePlayChapter": "Listen to beginning of chapter",
+  "MessageOr": "lub",
+  "MessagePauseChapter": "Zatrzymaj odtwarzanie rozdziały",
+  "MessagePlayChapter": "Rozpocznij odtwarzanie od początku rozdziału",
   "MessagePodcastHasNoRSSFeedForMatching": "Podcast nie ma adresu url kanału RSS, który mógłby zostać użyty do dopasowania",
   "MessageQuickMatchDescription": "Wypełnij puste informacje i okładkę pierwszym wynikiem dopasowania z '{0}'. Nie nadpisuje szczegółów, chyba że włączone jest ustawienie serwera 'Preferuj dopasowane metadane'.",
   "MessageRemoveAllItemsWarning": "UWAGA! Ta akcja usunie wszystkie elementy biblioteki z bazy danych, w tym wszystkie aktualizacje lub dopasowania, które zostały wykonane. Pliki pozostaną niezmienione. Czy jesteś pewien?",
-  "MessageRemoveChapter": "Remove chapter",
+  "MessageRemoveChapter": "Usuń rozdział",
   "MessageRemoveEpisodes": "Usuń {0} odcinków",
   "MessageRemoveUserWarning": "Czy na pewno chcesz trwale usunąć użytkownika \"{0}\"?",
   "MessageReportBugsAndContribute": "Zgłoś błędy, pomysły i pomóż rozwijać aplikację na",
@@ -490,12 +490,12 @@
   "MessageUploading": "Przesyłanie...",
   "MessageValidCronExpression": "Sprawdź wyrażenie CRON",
   "MessageWatcherIsDisabledGlobally": "Watcher jest wyłączony globalnie w ustawieniach serwera",
-  "MessageXLibraryIsEmpty": "{0} Library is empty!",
+  "MessageXLibraryIsEmpty": "{0} Biblioteka jest pusta!",
   "MessageYourAudiobookDurationIsLonger": "Czas trwania Twojego audiobooka jest dłuższy niż znaleziony czas trwania",
   "MessageYourAudiobookDurationIsShorter": "Czas trwania Twojego audiobooka jest krótszy niż znaleziony czas trwania",
   "NoteChangeRootPassword": "Tylko użytkownik root, może posiadać puste hasło",
   "NoteChapterEditorTimes": "Uwaga: Czas rozpoczęcia pierwszego rozdziału musi pozostać na poziomie 0:00, a czas rozpoczęcia ostatniego rozdziału nie może przekroczyć czasu trwania audiobooka.",
-  "NoteFolderPicker": "Note: folders already mapped will not be shown",
+  "NoteFolderPicker": "Uwaga: dotychczas zmapowane foldery nie zostaną wyświetlone",
   "NoteFolderPickerDebian": "Uwaga: Wybór folderu w instalcji opartej o system debian nie jest w pełni zaimplementowany. Powinieneś wprowadzić ścieżkę do swojej biblioteki bezpośrednio.",
   "NoteRSSFeedPodcastAppsHttps": "Ostrzeżenie: Większość aplikacji do obsługi podcastów wymaga, aby adres URL kanału RSS korzystał z protokołu HTTPS.",
   "NoteRSSFeedPodcastAppsPubDate": "Ostrzeżenie: 1 lub więcej odcinków nie ma daty publikacji. Niektóre aplikacje do słuchania podcastów tego wymagają.",
@@ -515,8 +515,8 @@
   "ToastAuthorUpdateSuccessNoImageFound": "Autor zaktualizowany (nie znaleziono obrazu)",
   "ToastBackupCreateFailed": "Nie udało się utworzyć kopii zapasowej",
   "ToastBackupCreateSuccess": "Utworzono kopię zapasową",
-  "ToastBackupDeleteFailed": "Failed to delete backup",
-  "ToastBackupDeleteSuccess": "Nie udało się usunąć kopii zapasowej",
+  "ToastBackupDeleteFailed": "Nie udało się usunąć kopii zapasowej",
+  "ToastBackupDeleteSuccess": "Udało się usunąć kopie zapasowej",
   "ToastBackupRestoreFailed": "Nie udało się przywrócić kopii zapasowej",
   "ToastBackupUploadFailed": "Nie udało się przesłać kopii zapasowej",
   "ToastBackupUploadSuccess": "Kopia zapasowa została przesłana",
@@ -559,9 +559,9 @@
   "ToastRSSFeedCloseSuccess": "Zamknięcie kanału RSS powiodło się",
   "ToastSessionDeleteFailed": "Nie udało się usunąć sesji",
   "ToastSessionDeleteSuccess": "Sesja usunięta",
-  "ToastSocketConnected": "Socket connected",
-  "ToastSocketDisconnected": "Socket disconnected",
-  "ToastSocketFailedToConnect": "Socket failed to connect",
+  "ToastSocketConnected": "Nawiązano połączenie z serwerem",
+  "ToastSocketDisconnected": "Połączenie z serwerem zostało zamknięte",
+  "ToastSocketFailedToConnect": "Poączenie z serwerem nie powiodło się",
   "ToastUserDeleteFailed": "Nie udało się usunąć użytkownika",
   "ToastUserDeleteSuccess": "Użytkownik usunięty",
   "WeekdayFriday": "Piątek",
diff --git a/server/Db.js b/server/Db.js
index 040d3c72..2ebbee2e 100644
--- a/server/Db.js
+++ b/server/Db.js
@@ -270,23 +270,6 @@ class Db {
     })
   }
 
-  updateUserStream(userId, streamId) {
-    return this.usersDb.update((record) => record.id === userId, (user) => {
-      user.stream = streamId
-      return user
-    }).then((results) => {
-      Logger.debug(`[DB] Updated user ${results.updated}`)
-      this.users = this.users.map(u => {
-        if (u.id === userId) {
-          u.stream = streamId
-        }
-        return u
-      })
-    }).catch((error) => {
-      Logger.error(`[DB] Update user Failed ${error}`)
-    })
-  }
-
   updateServerSettings() {
     global.ServerSettings = this.serverSettings.toJSON()
     return this.updateEntity('settings', this.serverSettings)
diff --git a/server/Server.js b/server/Server.js
index 7983809f..ef343f17 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -1,7 +1,6 @@
 const Path = require('path')
 const express = require('express')
 const http = require('http')
-const SocketIO = require('socket.io')
 const fs = require('./libs/fsExtra')
 const fileUpload = require('./libs/expressFileupload')
 const rateLimit = require('./libs/expressRateLimit')
@@ -13,11 +12,11 @@ const dbMigration = require('./utils/dbMigration')
 const filePerms = require('./utils/filePerms')
 const Logger = require('./Logger')
 
-// Classes
 const Auth = require('./Auth')
 const Watcher = require('./Watcher')
 const Scanner = require('./scanner/Scanner')
 const Db = require('./Db')
+const SocketAuthority = require('./SocketAuthority')
 
 const ApiRouter = require('./routers/ApiRouter')
 const HlsRouter = require('./routers/HlsRouter')
@@ -67,59 +66,30 @@ class Server {
     this.auth = new Auth(this.db)
 
     // Managers
-    this.taskManager = new TaskManager(this.emitter.bind(this))
-    this.notificationManager = new NotificationManager(this.db, this.emitter.bind(this))
-    this.backupManager = new BackupManager(this.db, this.emitter.bind(this))
+    this.taskManager = new TaskManager()
+    this.notificationManager = new NotificationManager(this.db)
+    this.backupManager = new BackupManager(this.db)
     this.logManager = new LogManager(this.db)
     this.cacheManager = new CacheManager()
-    this.abMergeManager = new AbMergeManager(this.db, this.taskManager, this.clientEmitter.bind(this))
-    this.playbackSessionManager = new PlaybackSessionManager(this.db, this.emitter.bind(this), this.clientEmitter.bind(this))
+    this.abMergeManager = new AbMergeManager(this.db, this.taskManager)
+    this.playbackSessionManager = new PlaybackSessionManager(this.db)
     this.coverManager = new CoverManager(this.db, this.cacheManager)
-    this.podcastManager = new PodcastManager(this.db, this.watcher, this.emitter.bind(this), this.notificationManager)
-    this.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager, this.emitter.bind(this), this.clientEmitter.bind(this))
-    this.rssFeedManager = new RssFeedManager(this.db, this.emitter.bind(this))
+    this.podcastManager = new PodcastManager(this.db, this.watcher, this.notificationManager)
+    this.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager)
+    this.rssFeedManager = new RssFeedManager(this.db)
 
-    this.scanner = new Scanner(this.db, this.coverManager, this.emitter.bind(this))
+    this.scanner = new Scanner(this.db, this.coverManager)
     this.cronManager = new CronManager(this.db, this.scanner, this.podcastManager)
 
     // Routers
-    this.apiRouter = new ApiRouter(this.db, this.auth, this.scanner, this.playbackSessionManager, this.abMergeManager, this.coverManager, this.backupManager, this.watcher, this.cacheManager, this.podcastManager, this.audioMetadataManager, this.rssFeedManager, this.cronManager, this.notificationManager, this.taskManager, this.getUsersOnline.bind(this), this.emitter.bind(this), this.clientEmitter.bind(this))
-    this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager, this.emitter.bind(this))
+    this.apiRouter = new ApiRouter(this)
+    this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager)
     this.staticRouter = new StaticRouter(this.db)
 
     Logger.logManager = this.logManager
 
     this.server = null
     this.io = null
-
-    this.clients = {}
-  }
-
-  getUsersOnline() {
-    return Object.values(this.clients).filter(c => c.user).map(client => {
-      return client.user.toJSONForPublic(this.playbackSessionManager.sessions, this.db.libraryItems)
-    })
-  }
-
-  getClientsForUser(userId) {
-    return Object.values(this.clients).filter(c => c.user && c.user.id === userId)
-  }
-
-  emitter(ev, data) {
-    // Logger.debug('EMITTER', ev)
-    this.io.emit(ev, data)
-  }
-
-  clientEmitter(userId, ev, data) {
-    var clients = this.getClientsForUser(userId)
-    if (!clients.length) {
-      return Logger.debug(`[Server] clientEmitter - no clients found for user ${userId}`)
-    }
-    clients.forEach((client) => {
-      if (client.socket) {
-        client.socket.emit(ev, data)
-      }
-    })
   }
 
   authMiddleware(req, res, next) {
@@ -130,7 +100,7 @@ class Server {
     Logger.info('[Server] Init v' + version)
     await this.playbackSessionManager.removeOrphanStreams()
 
-    var previousVersion = await this.db.checkPreviousVersion() // Returns null if same server version
+    const previousVersion = await this.db.checkPreviousVersion() // Returns null if same server version
     if (previousVersion) {
       Logger.debug(`[Server] Upgraded from previous version ${previousVersion}`)
     }
@@ -197,13 +167,13 @@ class Server {
 
     // EBook static file routes
     router.get('/ebook/:library/:folder/*', (req, res) => {
-      var library = this.db.libraries.find(lib => lib.id === req.params.library)
+      const library = this.db.libraries.find(lib => lib.id === req.params.library)
       if (!library) return res.sendStatus(404)
-      var folder = library.folders.find(fol => fol.id === req.params.folder)
+      const folder = library.folders.find(fol => fol.id === req.params.folder)
       if (!folder) return res.status(404).send('Folder not found')
 
-      var remainingPath = req.params['0']
-      var fullPath = Path.join(folder.fullPath, remainingPath)
+      const remainingPath = req.params['0']
+      const fullPath = Path.join(folder.fullPath, remainingPath)
       res.sendFile(fullPath)
     })
 
@@ -272,58 +242,8 @@ class Server {
       Logger.info(`Listening on http://${this.Host}:${this.Port}`)
     })
 
-    this.io = new SocketIO.Server(this.server, {
-      cors: {
-        origin: '*',
-        methods: ["GET", "POST"]
-      }
-    })
-    this.io.on('connection', (socket) => {
-      this.clients[socket.id] = {
-        id: socket.id,
-        socket,
-        connected_at: Date.now()
-      }
-      socket.sheepClient = this.clients[socket.id]
-
-      Logger.info('[Server] Socket Connected', socket.id)
-
-      socket.on('auth', (token) => this.authenticateSocket(socket, token))
-
-      // Scanning
-      socket.on('cancel_scan', this.cancelScan.bind(this))
-
-      // Logs
-      socket.on('set_log_listener', (level) => Logger.addSocketListener(socket, level))
-      socket.on('remove_log_listener', () => Logger.removeSocketListener(socket.id))
-      socket.on('fetch_daily_logs', () => this.logManager.socketRequestDailyLogs(socket))
-
-      socket.on('ping', () => {
-        var client = this.clients[socket.id] || {}
-        var user = client.user || {}
-        Logger.debug(`[Server] Received ping from socket ${user.username || 'No User'}`)
-        socket.emit('pong')
-      })
-
-      socket.on('disconnect', (reason) => {
-        Logger.removeSocketListener(socket.id)
-
-        var _client = this.clients[socket.id]
-        if (!_client) {
-          Logger.warn(`[Server] Socket ${socket.id} disconnect, no client (Reason: ${reason})`)
-        } else if (!_client.user) {
-          Logger.info(`[Server] Unauth socket ${socket.id} disconnected (Reason: ${reason})`)
-          delete this.clients[socket.id]
-        } else {
-          Logger.debug('[Server] User Offline ' + _client.user.username)
-          this.io.emit('user_offline', _client.user.toJSONForPublic(this.playbackSessionManager.sessions, this.db.libraryItems))
-
-          const disconnectTime = Date.now() - _client.connected_at
-          Logger.info(`[Server] Socket ${socket.id} disconnected from client "${_client.user.username}" after ${disconnectTime}ms (Reason: ${reason})`)
-          delete this.clients[socket.id]
-        }
-      })
-    })
+    // Start listening for socket connections
+    SocketAuthority.initialize(this)
   }
 
   async initializeServer(req, res) {
@@ -342,22 +262,17 @@ class Server {
     await this.scanner.scanFilesChanged(fileUpdates)
   }
 
-  cancelScan(id) {
-    Logger.debug('[Server] Cancel scan', id)
-    this.scanner.setCancelLibraryScan(id)
-  }
-
   // Remove unused /metadata/items/{id} folders
   async purgeMetadata() {
-    var itemsMetadata = Path.join(global.MetadataPath, 'items')
+    const itemsMetadata = Path.join(global.MetadataPath, 'items')
     if (!(await fs.pathExists(itemsMetadata))) return
-    var foldersInItemsMetadata = await fs.readdir(itemsMetadata)
+    const foldersInItemsMetadata = await fs.readdir(itemsMetadata)
 
-    var purged = 0
+    let purged = 0
     await Promise.all(foldersInItemsMetadata.map(async foldername => {
-      var hasMatchingItem = this.db.libraryItems.find(ab => ab.id === foldername)
+      const hasMatchingItem = this.db.libraryItems.find(ab => ab.id === foldername)
       if (!hasMatchingItem) {
-        var folderPath = Path.join(itemsMetadata, foldername)
+        const folderPath = Path.join(itemsMetadata, foldername)
         Logger.debug(`[Server] Purging unused metadata ${folderPath}`)
 
         await fs.remove(folderPath).then(() => {
@@ -376,8 +291,8 @@ class Server {
   // Remove user media progress with items that no longer exist & remove seriesHideFrom that no longer exist
   async cleanUserData() {
     for (let i = 0; i < this.db.users.length; i++) {
-      var _user = this.db.users[i]
-      var hasUpdated = false
+      const _user = this.db.users[i]
+      let hasUpdated = false
       if (_user.mediaProgress.length) {
         const lengthBefore = _user.mediaProgress.length
         _user.mediaProgress = _user.mediaProgress.filter(mp => {
@@ -423,68 +338,14 @@ class Server {
   }
 
   logout(req, res) {
-    var { socketId } = req.body
-    Logger.info(`[Server] User ${req.user ? req.user.username : 'Unknown'} is logging out with socket ${socketId}`)
-
-    // Strip user and client from client and client socket
-    if (socketId && this.clients[socketId]) {
-      var client = this.clients[socketId]
-      var clientSocket = client.socket
-      Logger.debug(`[Server] Found user client ${clientSocket.id}, Has user: ${!!client.user}, Socket has client: ${!!clientSocket.sheepClient}`)
-
-      if (client.user) {
-        Logger.debug('[Server] User Offline ' + client.user.username)
-        this.io.emit('user_offline', client.user.toJSONForPublic(null, this.db.libraryItems))
-      }
-
-      delete this.clients[socketId].user
-      if (clientSocket && clientSocket.sheepClient) delete this.clients[socketId].socket.sheepClient
-    } else if (socketId) {
-      Logger.warn(`[Server] No client for socket ${socketId}`)
+    if (req.body.socketId) {
+      Logger.info(`[Server] User ${req.user ? req.user.username : 'Unknown'} is logging out with socket ${req.body.socketId}`)
+      SocketAuthority.logout(req.body.socketId)
     }
 
     res.sendStatus(200)
   }
 
-  async authenticateSocket(socket, token) {
-    var user = await this.auth.authenticateUser(token)
-    if (!user) {
-      Logger.error('Cannot validate socket - invalid token')
-      return socket.emit('invalid_token')
-    }
-    var client = this.clients[socket.id]
-
-    if (client.user !== undefined) {
-      Logger.debug(`[Server] Authenticating socket client already has user`, client.user.username)
-    }
-
-    client.user = user
-
-    if (!client.user.toJSONForBrowser) {
-      Logger.error('Invalid user...', client.user)
-      return
-    }
-
-    Logger.debug(`[Server] User Online ${client.user.username}`)
-
-    this.io.emit('user_online', client.user.toJSONForPublic(this.playbackSessionManager.sessions, this.db.libraryItems))
-
-    user.lastSeen = Date.now()
-    await this.db.updateEntity('user', user)
-
-    const initialPayload = {
-      metadataPath: global.MetadataPath,
-      configPath: global.ConfigPath,
-      user: client.user.toJSONForBrowser(),
-      librariesScanning: this.scanner.librariesScanning,
-      backups: (this.backupManager.backups || []).map(b => b.toJSON())
-    }
-    if (user.type === 'root') {
-      initialPayload.usersOnline = this.getUsersOnline()
-    }
-    client.socket.emit('init', initialPayload)
-  }
-
   async stop() {
     await this.watcher.close()
     Logger.info('Watcher Closed')
diff --git a/server/SocketAuthority.js b/server/SocketAuthority.js
new file mode 100644
index 00000000..5488cf74
--- /dev/null
+++ b/server/SocketAuthority.js
@@ -0,0 +1,200 @@
+const SocketIO = require('socket.io')
+const Logger = require('./Logger')
+
+class SocketAuthority {
+  constructor() {
+    this.Server = null
+    this.io = null
+
+    this.clients = {}
+  }
+
+  // returns an array of User.toJSONForPublic with `connections` for the # of socket connections
+  //  a user can have many socket connections
+  getUsersOnline() {
+    const onlineUsersMap = {}
+    Object.values(this.clients).filter(c => c.user).forEach(client => {
+      if (onlineUsersMap[client.user.id]) {
+        onlineUsersMap[client.user.id].connections++
+      } else {
+        onlineUsersMap[client.user.id] = {
+          ...client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions, this.Server.db.libraryItems),
+          connections: 1
+        }
+      }
+    })
+    return Object.values(onlineUsersMap)
+  }
+
+  getClientsForUser(userId) {
+    return Object.values(this.clients).filter(c => c.user && c.user.id === userId)
+  }
+
+  // Emits event to all authorized clients
+  emitter(evt, data) {
+    for (const socketId in this.clients) {
+      if (this.clients[socketId].user) {
+        this.clients[socketId].socket.emit(evt, data)
+      }
+    }
+  }
+
+  // Emits event to all clients for a specific user
+  clientEmitter(userId, evt, data) {
+    const clients = this.getClientsForUser(userId)
+    if (!clients.length) {
+      return Logger.debug(`[Server] clientEmitter - no clients found for user ${userId}`)
+    }
+    clients.forEach((client) => {
+      if (client.socket) {
+        client.socket.emit(evt, data)
+      }
+    })
+  }
+
+  // Emits event to all admin user clients
+  adminEmitter(evt, data) {
+    for (const socketId in this.clients) {
+      if (this.clients[socketId].user && this.clients[socketId].user.isAdminOrUp) {
+        this.clients[socketId].socket.emit(evt, data)
+      }
+    }
+  }
+
+  initialize(Server) {
+    this.Server = Server
+
+    this.io = new SocketIO.Server(this.Server.server, {
+      cors: {
+        origin: '*',
+        methods: ["GET", "POST"]
+      }
+    })
+    this.io.on('connection', (socket) => {
+      this.clients[socket.id] = {
+        id: socket.id,
+        socket,
+        connected_at: Date.now()
+      }
+      socket.sheepClient = this.clients[socket.id]
+
+      Logger.info('[Server] Socket Connected', socket.id)
+
+      // Required for associating a User with a socket
+      socket.on('auth', (token) => this.authenticateSocket(socket, token))
+
+      // Scanning
+      socket.on('cancel_scan', this.cancelScan.bind(this))
+
+      // Logs
+      socket.on('set_log_listener', (level) => Logger.addSocketListener(socket, level))
+      socket.on('remove_log_listener', () => Logger.removeSocketListener(socket.id))
+      socket.on('fetch_daily_logs', () => this.Server.logManager.socketRequestDailyLogs(socket))
+
+      // Sent automatically from socket.io clients
+      socket.on('disconnect', (reason) => {
+        Logger.removeSocketListener(socket.id)
+
+        const _client = this.clients[socket.id]
+        if (!_client) {
+          Logger.warn(`[Server] Socket ${socket.id} disconnect, no client (Reason: ${reason})`)
+        } else if (!_client.user) {
+          Logger.info(`[Server] Unauth socket ${socket.id} disconnected (Reason: ${reason})`)
+          delete this.clients[socket.id]
+        } else {
+          Logger.debug('[Server] User Offline ' + _client.user.username)
+          this.adminEmitter('user_offline', _client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions, this.Server.db.libraryItems))
+
+          const disconnectTime = Date.now() - _client.connected_at
+          Logger.info(`[Server] Socket ${socket.id} disconnected from client "${_client.user.username}" after ${disconnectTime}ms (Reason: ${reason})`)
+          delete this.clients[socket.id]
+        }
+      })
+
+      //
+      // Events for testing
+      //
+      socket.on('message_all_users', (payload) => {
+        // admin user can send a message to all authenticated users
+        //   displays on the web app as a toast
+        const client = this.clients[socket.id] || {}
+        if (client.user && client.user.isAdminOrUp) {
+          this.emitter('admin_message', payload.message || '')
+        } else {
+          Logger.error(`[Server] Non-admin user sent the message_all_users event`)
+        }
+      })
+      socket.on('ping', () => {
+        const client = this.clients[socket.id] || {}
+        const user = client.user || {}
+        Logger.debug(`[Server] Received ping from socket ${user.username || 'No User'}`)
+        socket.emit('pong')
+      })
+    })
+  }
+
+  // When setting up a socket connection the user needs to be associated with a socket id
+  //  for this the client will send a 'auth' event that includes the users API token
+  async authenticateSocket(socket, token) {
+    const user = await this.Server.auth.authenticateUser(token)
+    if (!user) {
+      Logger.error('Cannot validate socket - invalid token')
+      return socket.emit('invalid_token')
+    }
+    const client = this.clients[socket.id]
+
+    if (client.user !== undefined) {
+      Logger.debug(`[Server] Authenticating socket client already has user`, client.user.username)
+    }
+
+    client.user = user
+
+    if (!client.user.toJSONForBrowser) {
+      Logger.error('Invalid user...', client.user)
+      return
+    }
+
+    Logger.debug(`[Server] User Online ${client.user.username}`)
+
+    this.adminEmitter('user_online', client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions, this.Server.db.libraryItems))
+
+    // Update user lastSeen
+    user.lastSeen = Date.now()
+    await this.Server.db.updateEntity('user', user)
+
+    const initialPayload = {
+      userId: client.user.id,
+      username: client.user.username,
+      librariesScanning: this.Server.scanner.librariesScanning
+    }
+    if (user.isAdminOrUp) {
+      initialPayload.usersOnline = this.getUsersOnline()
+    }
+    client.socket.emit('init', initialPayload)
+  }
+
+  logout(socketId) {
+    // Strip user and client from client and client socket
+    if (socketId && this.clients[socketId]) {
+      const client = this.clients[socketId]
+      const clientSocket = client.socket
+      Logger.debug(`[Server] Found user client ${clientSocket.id}, Has user: ${!!client.user}, Socket has client: ${!!clientSocket.sheepClient}`)
+
+      if (client.user) {
+        Logger.debug('[Server] User Offline ' + client.user.username)
+        this.adminEmitter('user_offline', client.user.toJSONForPublic(null, this.Server.db.libraryItems))
+      }
+
+      delete this.clients[socketId].user
+      if (clientSocket && clientSocket.sheepClient) delete this.clients[socketId].socket.sheepClient
+    } else if (socketId) {
+      Logger.warn(`[Server] No client for socket ${socketId}`)
+    }
+  }
+
+  cancelScan(id) {
+    Logger.debug('[Server] Cancel scan', id)
+    this.Server.scanner.setCancelLibraryScan(id)
+  }
+}
+module.exports = new SocketAuthority()
\ No newline at end of file
diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js
index 318538b9..96daefe8 100644
--- a/server/controllers/AuthorController.js
+++ b/server/controllers/AuthorController.js
@@ -1,4 +1,6 @@
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
 const { reqSupportsWebp } = require('../utils/index')
 const { createNewSortInstance } = require('../libs/fastSort')
 
@@ -93,18 +95,18 @@ class AuthorController {
       })
       if (itemsWithAuthor.length) {
         await this.db.updateLibraryItems(itemsWithAuthor)
-        this.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded()))
+        SocketAuthority.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded()))
       }
 
       // Remove old author
       await this.db.removeEntity('author', req.author.id)
-      this.emitter('author_removed', req.author.toJSON())
+      SocketAuthority.emitter('author_removed', req.author.toJSON())
 
       // Send updated num books for merged author
       var numBooks = this.db.libraryItems.filter(li => {
         return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(existingAuthor.id)
       }).length
-      this.emitter('author_updated', existingAuthor.toJSONExpanded(numBooks))
+      SocketAuthority.emitter('author_updated', existingAuthor.toJSONExpanded(numBooks))
 
       res.json({
         author: existingAuthor.toJSON(),
@@ -121,7 +123,7 @@ class AuthorController {
           })
           if (itemsWithAuthor.length) {
             await this.db.updateLibraryItems(itemsWithAuthor)
-            this.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded()))
+            SocketAuthority.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded()))
           }
         }
 
@@ -129,7 +131,7 @@ class AuthorController {
         var numBooks = this.db.libraryItems.filter(li => {
           return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id)
         }).length
-        this.emitter('author_updated', req.author.toJSONExpanded(numBooks))
+        SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
       }
 
       res.json({
@@ -190,7 +192,7 @@ class AuthorController {
       var numBooks = this.db.libraryItems.filter(li => {
         return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id)
       }).length
-      this.emitter('author_updated', req.author.toJSONExpanded(numBooks))
+      SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
     }
 
     res.json({
diff --git a/server/controllers/BackupController.js b/server/controllers/BackupController.js
index 79b07fd1..7ee482a9 100644
--- a/server/controllers/BackupController.js
+++ b/server/controllers/BackupController.js
@@ -3,32 +3,29 @@ const Logger = require('../Logger')
 class BackupController {
   constructor() { }
 
-  async create(req, res) {
-    if (!req.user.isAdminOrUp) {
-      Logger.error(`[BackupController] Non-admin user attempting to craete backup`, req.user)
-      return res.sendStatus(403)
-    }
+  getAll(req, res) {
+    res.json({
+      backups: this.backupManager.backups.map(b => b.toJSON())
+    })
+  }
+
+  create(req, res) {
     this.backupManager.requestCreateBackup(res)
   }
 
   async delete(req, res) {
-    if (!req.user.isAdminOrUp) {
-      Logger.error(`[BackupController] Non-admin user attempting to delete backup`, req.user)
-      return res.sendStatus(403)
-    }
     var backup = this.backupManager.backups.find(b => b.id === req.params.id)
     if (!backup) {
       return res.sendStatus(404)
     }
     await this.backupManager.removeBackup(backup)
-    res.json(this.backupManager.backups.map(b => b.toJSON()))
+
+    res.json({
+      backups: this.backupManager.backups.map(b => b.toJSON())
+    })
   }
 
   async upload(req, res) {
-    if (!req.user.isAdminOrUp) {
-      Logger.error(`[BackupController] Non-admin user attempting to upload backup`, req.user)
-      return res.sendStatus(403)
-    }
     if (!req.files.file) {
       Logger.error('[BackupController] Upload backup invalid')
       return res.sendStatus(500)
@@ -37,10 +34,6 @@ class BackupController {
   }
 
   async apply(req, res) {
-    if (!req.user.isAdminOrUp) {
-      Logger.error(`[BackupController] Non-admin user attempting to apply backup`, req.user)
-      return res.sendStatus(403)
-    }
     var backup = this.backupManager.backups.find(b => b.id === req.params.id)
     if (!backup) {
       return res.sendStatus(404)
@@ -48,5 +41,13 @@ class BackupController {
     await this.backupManager.requestApplyBackup(backup)
     res.sendStatus(200)
   }
+
+  middleware(req, res, next) {
+    if (!req.user.isAdminOrUp) {
+      Logger.error(`[BackupController] Non-admin user attempting to access backups`, req.user)
+      return res.sendStatus(403)
+    }
+    next()
+  }
 }
 module.exports = new BackupController()
\ No newline at end of file
diff --git a/server/controllers/CollectionController.js b/server/controllers/CollectionController.js
index 8508eb73..9234abee 100644
--- a/server/controllers/CollectionController.js
+++ b/server/controllers/CollectionController.js
@@ -1,4 +1,6 @@
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
 const Collection = require('../objects/Collection')
 
 class CollectionController {
@@ -13,7 +15,7 @@ class CollectionController {
     }
     var jsonExpanded = newCollection.toJSONExpanded(this.db.libraryItems)
     await this.db.insertEntity('collection', newCollection)
-    this.emitter('collection_added', jsonExpanded)
+    SocketAuthority.emitter('collection_added', jsonExpanded)
     res.json(jsonExpanded)
   }
 
@@ -32,7 +34,7 @@ class CollectionController {
     var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
     if (wasUpdated) {
       await this.db.updateEntity('collection', collection)
-      this.emitter('collection_updated', jsonExpanded)
+      SocketAuthority.emitter('collection_updated', jsonExpanded)
     }
     res.json(jsonExpanded)
   }
@@ -41,7 +43,7 @@ class CollectionController {
     const collection = req.collection
     var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
     await this.db.removeEntity('collection', collection.id)
-    this.emitter('collection_removed', jsonExpanded)
+    SocketAuthority.emitter('collection_removed', jsonExpanded)
     res.sendStatus(200)
   }
 
@@ -60,7 +62,7 @@ class CollectionController {
     collection.addBook(req.body.id)
     var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
     await this.db.updateEntity('collection', collection)
-    this.emitter('collection_updated', jsonExpanded)
+    SocketAuthority.emitter('collection_updated', jsonExpanded)
     res.json(jsonExpanded)
   }
 
@@ -71,7 +73,7 @@ class CollectionController {
       collection.removeBook(req.params.bookId)
       var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
       await this.db.updateEntity('collection', collection)
-      this.emitter('collection_updated', jsonExpanded)
+      SocketAuthority.emitter('collection_updated', jsonExpanded)
     }
     res.json(collection.toJSONExpanded(this.db.libraryItems))
   }
@@ -92,7 +94,7 @@ class CollectionController {
     }
     if (hasUpdated) {
       await this.db.updateEntity('collection', collection)
-      this.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems))
+      SocketAuthority.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems))
     }
     res.json(collection.toJSONExpanded(this.db.libraryItems))
   }
@@ -113,7 +115,7 @@ class CollectionController {
     }
     if (hasUpdated) {
       await this.db.updateEntity('collection', collection)
-      this.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems))
+      SocketAuthority.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems))
     }
     res.json(collection.toJSONExpanded(this.db.libraryItems))
   }
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index dbc29ba3..b724ed0c 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -2,6 +2,7 @@ const Path = require('path')
 const fs = require('../libs/fsExtra')
 const filePerms = require('../utils/filePerms')
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
 const Library = require('../objects/Library')
 const libraryHelpers = require('../utils/libraryHelpers')
 const { sort, createNewSortInstance } = require('../libs/fastSort')
@@ -43,7 +44,7 @@ class LibraryController {
     library.setData(newLibraryPayload)
     await this.db.insertEntity('library', library)
     // TODO: Only emit to users that have access
-    this.emitter('library_added', library.toJSON())
+    SocketAuthority.emitter('library_added', library.toJSON())
 
     // Add library watcher
     this.watcher.addLibrary(library)
@@ -120,7 +121,7 @@ class LibraryController {
         }
       }
       await this.db.updateEntity('library', library)
-      this.emitter('library_updated', library.toJSON())
+      SocketAuthority.emitter('library_updated', library.toJSON())
     }
     return res.json(library.toJSON())
   }
@@ -147,7 +148,7 @@ class LibraryController {
 
     var libraryJson = library.toJSON()
     await this.db.removeEntity('library', library.id)
-    this.emitter('library_removed', libraryJson)
+    SocketAuthority.emitter('library_removed', libraryJson)
     return res.json(libraryJson)
   }
 
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index 20df9dad..8f573312 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -1,4 +1,6 @@
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
 const { reqSupportsWebp, isNullOrNaN } = require('../utils/index')
 const { ScanResult } = require('../utils/constants')
 
@@ -53,7 +55,7 @@ class LibraryItemController {
     if (hasUpdates) {
       Logger.debug(`[LibraryItemController] Updated now saving`)
       await this.db.updateLibraryItem(libraryItem)
-      this.emitter('item_updated', libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     }
     res.json(libraryItem.toJSON())
   }
@@ -97,7 +99,7 @@ class LibraryItemController {
 
       Logger.debug(`[LibraryItemController] Updated library item media ${libraryItem.media.metadata.title}`)
       await this.db.updateLibraryItem(libraryItem)
-      this.emitter('item_updated', libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     }
     res.json({
       updated: hasUpdates,
@@ -132,7 +134,7 @@ class LibraryItemController {
     }
 
     await this.db.updateLibraryItem(libraryItem)
-    this.emitter('item_updated', libraryItem.toJSONExpanded())
+    SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     res.json({
       success: true,
       cover: result.cover
@@ -152,7 +154,7 @@ class LibraryItemController {
     }
     if (validationResult.updated) {
       await this.db.updateLibraryItem(libraryItem)
-      this.emitter('item_updated', libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     }
     res.json({
       success: true,
@@ -168,7 +170,7 @@ class LibraryItemController {
       libraryItem.updateMediaCover('')
       await this.cacheManager.purgeCoverCache(libraryItem.id)
       await this.db.updateLibraryItem(libraryItem)
-      this.emitter('item_updated', libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     }
 
     res.sendStatus(200)
@@ -228,7 +230,7 @@ class LibraryItemController {
     }
     libraryItem.media.updateAudioTracks(orderedFileData)
     await this.db.updateLibraryItem(libraryItem)
-    this.emitter('item_updated', libraryItem.toJSONExpanded())
+    SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     res.json(libraryItem.toJSON())
   }
 
@@ -284,7 +286,7 @@ class LibraryItemController {
       if (hasUpdates) {
         Logger.debug(`[LibraryItemController] Updated library item media ${libraryItem.media.metadata.title}`)
         await this.db.updateLibraryItem(libraryItem)
-        this.emitter('item_updated', libraryItem.toJSONExpanded())
+        SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
         itemsUpdated++
       }
     }
@@ -342,7 +344,7 @@ class LibraryItemController {
       updates: itemsUpdated,
       unmatched: itemsUnmatched
     }
-    this.clientEmitter(req.user.id, 'batch_quickmatch_complete', result)
+    SocketAuthority.clientEmitter(req.user.id, 'batch_quickmatch_complete', result)
   }
 
   // DELETE: api/items/all
@@ -410,7 +412,7 @@ class LibraryItemController {
     const wasUpdated = req.libraryItem.media.updateChapters(chapters)
     if (wasUpdated) {
       await this.db.updateLibraryItem(req.libraryItem)
-      this.emitter('item_updated', req.libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', req.libraryItem.toJSONExpanded())
     }
 
     res.json({
diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js
index 8a4848ee..3029a170 100644
--- a/server/controllers/MeController.js
+++ b/server/controllers/MeController.js
@@ -1,4 +1,5 @@
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
 const { sort } = require('../libs/fastSort')
 const { isObject, toNumber } = require('../utils/index')
 
@@ -48,7 +49,7 @@ class MeController {
       return res.sendStatus(200)
     }
     await this.db.updateEntity('user', req.user)
-    this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     res.sendStatus(200)
   }
 
@@ -62,7 +63,7 @@ class MeController {
     var wasUpdated = req.user.createUpdateMediaProgress(libraryItem, req.body)
     if (wasUpdated) {
       await this.db.updateEntity('user', req.user)
-      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     }
     res.sendStatus(200)
   }
@@ -82,7 +83,7 @@ class MeController {
     var wasUpdated = req.user.createUpdateMediaProgress(libraryItem, req.body, episodeId)
     if (wasUpdated) {
       await this.db.updateEntity('user', req.user)
-      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     }
     res.sendStatus(200)
   }
@@ -91,7 +92,7 @@ class MeController {
   async batchUpdateMediaProgress(req, res) {
     var itemProgressPayloads = req.body
     if (!itemProgressPayloads || !itemProgressPayloads.length) {
-      return res.sendStatus(500)
+      return res.status(400).send('Missing request payload')
     }
 
     var shouldUpdate = false
@@ -107,7 +108,7 @@ class MeController {
 
     if (shouldUpdate) {
       await this.db.updateEntity('user', req.user)
-      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     }
 
     res.sendStatus(200)
@@ -120,7 +121,7 @@ class MeController {
     const { time, title } = req.body
     var bookmark = req.user.createBookmark(libraryItem.id, time, title)
     await this.db.updateEntity('user', req.user)
-    this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     res.json(bookmark)
   }
 
@@ -136,7 +137,7 @@ class MeController {
     var bookmark = req.user.updateBookmark(libraryItem.id, time, title)
     if (!bookmark) return res.sendStatus(500)
     await this.db.updateEntity('user', req.user)
-    this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     res.json(bookmark)
   }
 
@@ -153,7 +154,7 @@ class MeController {
     }
     req.user.removeBookmark(libraryItem.id, time)
     await this.db.updateEntity('user', req.user)
-    this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+    SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     res.sendStatus(200)
   }
 
@@ -233,7 +234,7 @@ class MeController {
     Logger.debug(`[MeController] syncLocalMediaProgress server updates = ${numServerProgressUpdates}, local updates = ${updatedLocalMediaProgress.length}`)
     if (numServerProgressUpdates > 0) {
       await this.db.updateEntity('user', req.user)
-      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     }
 
     res.json({
@@ -288,7 +289,7 @@ class MeController {
     const hasUpdated = req.user.addSeriesToHideFromContinueListening(req.params.id)
     if (hasUpdated) {
       await this.db.updateEntity('user', req.user)
-      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     }
     res.json(req.user.toJSONForBrowser())
   }
@@ -304,7 +305,7 @@ class MeController {
     const hasUpdated = req.user.removeSeriesFromHideFromContinueListening(req.params.id)
     if (hasUpdated) {
       await this.db.updateEntity('user', req.user)
-      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     }
     res.json(req.user.toJSONForBrowser())
   }
@@ -314,7 +315,7 @@ class MeController {
     const hasUpdated = req.user.removeProgressFromContinueListening(req.params.id)
     if (hasUpdated) {
       await this.db.updateEntity('user', req.user)
-      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
     }
     res.json(req.user.toJSONForBrowser())
   }
diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js
index 5c0b5e1e..2c5c7b48 100644
--- a/server/controllers/PodcastController.js
+++ b/server/controllers/PodcastController.js
@@ -1,11 +1,14 @@
-const axios = require('axios')
-const fs = require('../libs/fsExtra')
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
+const fs = require('../libs/fsExtra')
+
 const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils')
-const LibraryItem = require('../objects/LibraryItem')
 const { getFileTimestampsWithIno } = require('../utils/fileUtils')
 const filePerms = require('../utils/filePerms')
 
+const LibraryItem = require('../objects/LibraryItem')
+
 class PodcastController {
 
   async create(req, res) {
@@ -75,7 +78,7 @@ class PodcastController {
     }
 
     await this.db.insertLibraryItem(libraryItem)
-    this.emitter('item_added', libraryItem.toJSONExpanded())
+    SocketAuthority.emitter('item_added', libraryItem.toJSONExpanded())
 
     res.json(libraryItem.toJSONExpanded())
 
@@ -194,7 +197,7 @@ class PodcastController {
     var wasUpdated = libraryItem.media.updateEpisode(episodeId, req.body)
     if (wasUpdated) {
       await this.db.updateLibraryItem(libraryItem)
-      this.emitter('item_updated', libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     }
 
     res.json(libraryItem.toJSONExpanded())
@@ -229,7 +232,7 @@ class PodcastController {
     }
 
     await this.db.updateLibraryItem(libraryItem)
-    this.emitter('item_updated', libraryItem.toJSONExpanded())
+    SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     res.json(libraryItem.toJSON())
   }
 
diff --git a/server/controllers/SeriesController.js b/server/controllers/SeriesController.js
index 9f11ac51..678f30e0 100644
--- a/server/controllers/SeriesController.js
+++ b/server/controllers/SeriesController.js
@@ -1,4 +1,5 @@
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
 
 class SeriesController {
   constructor() { }
@@ -38,7 +39,7 @@ class SeriesController {
     const hasUpdated = req.series.update(req.body)
     if (hasUpdated) {
       await this.db.updateEntity('series', req.series)
-      this.emitter('series_updated', req.series)
+      SocketAuthority.emitter('series_updated', req.series)
     }
     res.json(req.series)
   }
diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js
index 2c2caf21..2d460a9c 100644
--- a/server/controllers/UserController.js
+++ b/server/controllers/UserController.js
@@ -1,4 +1,6 @@
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
 const User = require('../objects/user/User')
 
 const { getId, toNumber } = require('../utils/index')
@@ -44,7 +46,7 @@ class UserController {
     var newUser = new User(account)
     var success = await this.db.insertEntity('user', newUser)
     if (success) {
-      this.clientEmitter(req.user.id, 'user_added', newUser)
+      SocketAuthority.clientEmitter(req.user.id, 'user_added', newUser)
       res.json({
         user: newUser.toJSONForBrowser()
       })
@@ -85,7 +87,7 @@ class UserController {
         Logger.info(`[UserController] User ${user.username} was generated a new api token`)
       }
       await this.db.updateEntity('user', user)
-      this.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser())
     }
 
     res.json({
@@ -109,7 +111,7 @@ class UserController {
 
     var userJson = user.toJSONForBrowser()
     await this.db.removeEntity('user', user.id)
-    this.clientEmitter(req.user.id, 'user_removed', userJson)
+    SocketAuthority.clientEmitter(req.user.id, 'user_removed', userJson)
     res.json({
       success: true
     })
@@ -170,7 +172,7 @@ class UserController {
     if (progressPurged) {
       Logger.info(`[UserController] Purged ${progressPurged} media progress for user ${user.username}`)
       await this.db.updateEntity('user', user)
-      this.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser())
+      SocketAuthority.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser())
     }
 
     res.json(this.userJsonWithItemProgressDetails(user, !req.user.isRoot))
@@ -181,10 +183,9 @@ class UserController {
     if (!req.user.isAdminOrUp) {
       return res.sendStatus(403)
     }
-    const usersOnline = this.getUsersOnline()
 
     res.json({
-      usersOnline,
+      usersOnline: SocketAuthority.getUsersOnline(),
       openSessions: this.playbackSessionManager.sessions
     })
   }
diff --git a/server/managers/AbMergeManager.js b/server/managers/AbMergeManager.js
index 6fbce262..d5c33970 100644
--- a/server/managers/AbMergeManager.js
+++ b/server/managers/AbMergeManager.js
@@ -10,10 +10,9 @@ const { writeConcatFile } = require('../utils/ffmpegHelpers')
 const toneHelpers = require('../utils/toneHelpers')
 
 class AbMergeManager {
-  constructor(db, taskManager, clientEmitter) {
+  constructor(db, taskManager) {
     this.db = db
     this.taskManager = taskManager
-    this.clientEmitter = clientEmitter
 
     this.itemsCacheDir = Path.join(global.MetadataPath, 'cache/items')
     this.downloadDirPath = Path.join(global.MetadataPath, 'downloads')
diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js
index 25a93583..d4d17211 100644
--- a/server/managers/AudioMetadataManager.js
+++ b/server/managers/AudioMetadataManager.js
@@ -1,18 +1,20 @@
 const Path = require('path')
-const fs = require('../libs/fsExtra')
 const workerThreads = require('worker_threads')
+
+const SocketAuthority = require('../SocketAuthority')
 const Logger = require('../Logger')
+
+const fs = require('../libs/fsExtra')
+
 const filePerms = require('../utils/filePerms')
 const { secondsToTimestamp } = require('../utils/index')
 const { writeMetadataFile } = require('../utils/ffmpegHelpers')
 const toneHelpers = require('../utils/toneHelpers')
 
 class AudioMetadataMangaer {
-  constructor(db, taskManager, emitter, clientEmitter) {
+  constructor(db, taskManager) {
     this.db = db
     this.taskManager = taskManager
-    this.emitter = emitter
-    this.clientEmitter = clientEmitter
   }
 
   updateMetadataForItem(user, libraryItem, useTone, forceEmbedChapters) {
@@ -40,7 +42,7 @@ class AudioMetadataMangaer {
       audioFiles: audioFiles.map(af => ({ index: af.index, ino: af.ino, filename: af.metadata.filename }))
     }
 
-    this.emitter('audio_metadata_started', itemAudioMetadataPayload)
+    SocketAuthority.emitter('audio_metadata_started', itemAudioMetadataPayload)
 
     // Write chapters file
     var toneJsonPath = null
@@ -67,7 +69,7 @@ class AudioMetadataMangaer {
     itemAudioMetadataPayload.results = results
     itemAudioMetadataPayload.elapsed = elapsed
     itemAudioMetadataPayload.finishedAt = Date.now()
-    this.emitter('audio_metadata_finished', itemAudioMetadataPayload)
+    SocketAuthority.emitter('audio_metadata_finished', itemAudioMetadataPayload)
   }
 
   async updateAudioFileMetadataWithTone(libraryItemId, audioFile, toneJsonPath, itemCacheDir) {
@@ -77,7 +79,7 @@ class AudioMetadataMangaer {
       ino: audioFile.ino,
       filename: audioFile.metadata.filename
     }
-    this.emitter('audiofile_metadata_started', resultPayload)
+    SocketAuthority.emitter('audiofile_metadata_started', resultPayload)
 
     // Backup audio file
     try {
@@ -98,7 +100,7 @@ class AudioMetadataMangaer {
       Logger.info(`[AudioMetadataManager] Successfully tagged audio file "${audioFile.metadata.path}"`)
     }
 
-    this.emitter('audiofile_metadata_finished', resultPayload)
+    SocketAuthority.emitter('audiofile_metadata_finished', resultPayload)
     return resultPayload
   }
 
@@ -115,7 +117,7 @@ class AudioMetadataMangaer {
       audioFiles: audioFiles.map(af => ({ index: af.index, ino: af.ino, filename: af.metadata.filename }))
     }
 
-    this.emitter('audio_metadata_started', itemAudioMetadataPayload)
+    SocketAuthority.emitter('audio_metadata_started', itemAudioMetadataPayload)
 
     var downloadsPath = Path.join(global.MetadataPath, 'downloads')
     var outputDir = Path.join(downloadsPath, libraryItem.id)
@@ -143,7 +145,7 @@ class AudioMetadataMangaer {
     itemAudioMetadataPayload.results = results
     itemAudioMetadataPayload.elapsed = elapsed
     itemAudioMetadataPayload.finishedAt = Date.now()
-    this.emitter('audio_metadata_finished', itemAudioMetadataPayload)
+    SocketAuthority.emitter('audio_metadata_finished', itemAudioMetadataPayload)
   }
 
   updateAudioFileMetadataWithFfmpeg(libraryItemId, audioFile, outputDir, metadataFilePath, coverPath = '') {
@@ -154,7 +156,7 @@ class AudioMetadataMangaer {
         ino: audioFile.ino,
         filename: audioFile.metadata.filename
       }
-      this.emitter('audiofile_metadata_started', resultPayload)
+      SocketAuthority.emitter('audiofile_metadata_started', resultPayload)
 
       Logger.debug(`[AudioFileMetadataManager] Starting audio file metadata encode for "${audioFile.metadata.filename}"`)
 
@@ -229,19 +231,19 @@ class AudioMetadataMangaer {
                 Logger.debug(`[AudioFileMetadataManager] Audio file replaced successfully "${inputPath}"`)
 
                 resultPayload.success = true
-                this.emitter('audiofile_metadata_finished', resultPayload)
+                SocketAuthority.emitter('audiofile_metadata_finished', resultPayload)
                 resolve(resultPayload)
               }).catch((error) => {
                 Logger.error(`[AudioFileMetadataManager] Audio file failed to move "${inputPath}"`, error)
                 resultPayload.success = false
-                this.emitter('audiofile_metadata_finished', resultPayload)
+                SocketAuthority.emitter('audiofile_metadata_finished', resultPayload)
                 resolve(resultPayload)
               })
             } else {
               Logger.debug(`[AudioFileMetadataManager] Metadata encode FAILED for "${audioFile.metadata.filename}"`)
 
               resultPayload.success = false
-              this.emitter('audiofile_metadata_finished', resultPayload)
+              SocketAuthority.emitter('audiofile_metadata_finished', resultPayload)
               resolve(resultPayload)
             }
           } else if (message.type === 'FFMPEG') {
diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js
index 01f05b90..4e387fa8 100644
--- a/server/managers/BackupManager.js
+++ b/server/managers/BackupManager.js
@@ -1,4 +1,6 @@
 const Path = require('path')
+const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
 
 const cron = require('../libs/nodeCron')
 const fs = require('../libs/fsExtra')
@@ -8,18 +10,16 @@ const StreamZip = require('../libs/nodeStreamZip')
 // Utils
 const { getFileSize } = require('../utils/fileUtils')
 const filePerms = require('../utils/filePerms')
-const Logger = require('../Logger')
 
 const Backup = require('../objects/Backup')
 
 class BackupManager {
-  constructor(db, emitter) {
+  constructor(db) {
     this.BackupPath = Path.join(global.MetadataPath, 'backups')
     this.ItemsMetadataPath = Path.join(global.MetadataPath, 'items')
     this.AuthorsMetadataPath = Path.join(global.MetadataPath, 'authors')
 
     this.db = db
-    this.emitter = emitter
 
     this.scheduleTask = null
 
@@ -106,13 +106,20 @@ class BackupManager {
       this.backups.push(backup)
     }
 
-    return res.json(this.backups.map(b => b.toJSON()))
+    res.json({
+      backups: this.backups.map(b => b.toJSON())
+    })
   }
 
   async requestCreateBackup(res) {
     var backupSuccess = await this.runBackup()
-    if (backupSuccess) res.json(this.backups.map(b => b.toJSON()))
-    else res.sendStatus(500)
+    if (backupSuccess) {
+      res.json({
+        backups: this.backups.map(b => b.toJSON())
+      })
+    } else {
+      res.sendStatus(500)
+    }
   }
 
   async requestApplyBackup(backup) {
@@ -123,7 +130,7 @@ class BackupManager {
       await zip.extract('metadata-authors/', this.AuthorsMetadataPath)
     }
     await this.db.reinit()
-    this.emitter('backup_applied')
+    SocketAuthority.emitter('backup_applied')
   }
 
   async loadBackups() {
diff --git a/server/managers/NotificationManager.js b/server/managers/NotificationManager.js
index cf7a6042..33d13ab5 100644
--- a/server/managers/NotificationManager.js
+++ b/server/managers/NotificationManager.js
@@ -1,11 +1,11 @@
 const axios = require('axios')
 const Logger = require("../Logger")
+const SocketAuthority = require('../SocketAuthority')
 const { notificationData } = require('../utils/notifications')
 
 class NotificationManager {
-  constructor(db, emitter) {
+  constructor(db) {
     this.db = db
-    this.emitter = emitter
 
     this.sendingNotification = false
     this.notificationQueue = []
@@ -58,7 +58,7 @@ class NotificationManager {
     }
 
     await this.db.updateEntity('settings', this.db.notificationSettings)
-    this.emitter('notifications_updated', this.db.notificationSettings)
+    SocketAuthority.emitter('notifications_updated', this.db.notificationSettings)
 
     this.notificationFinished()
   }
diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js
index 38693c97..23f991ad 100644
--- a/server/managers/PlaybackSessionManager.js
+++ b/server/managers/PlaybackSessionManager.js
@@ -1,22 +1,24 @@
 const Path = require('path')
-const date = require('../libs/dateAndTime')
 const serverVersion = require('../../package.json').version
-const { PlayMethod } = require('../utils/constants')
-const PlaybackSession = require('../objects/PlaybackSession')
-const DeviceInfo = require('../objects/DeviceInfo')
-const Stream = require('../objects/Stream')
 const Logger = require('../Logger')
-const fs = require('../libs/fsExtra')
+const SocketAuthority = require('../SocketAuthority')
 
+const date = require('../libs/dateAndTime')
+const fs = require('../libs/fsExtra')
 const uaParserJs = require('../libs/uaParser')
 const requestIp = require('../libs/requestIp')
 
+const { PlayMethod } = require('../utils/constants')
+
+const PlaybackSession = require('../objects/PlaybackSession')
+const DeviceInfo = require('../objects/DeviceInfo')
+const Stream = require('../objects/Stream')
+
+
 class PlaybackSessionManager {
-  constructor(db, emitter, clientEmitter) {
+  constructor(db) {
     this.db = db
     this.StreamsPath = Path.join(global.MetadataPath, 'streams')
-    this.emitter = emitter
-    this.clientEmitter = clientEmitter
 
     this.sessions = []
     this.localSessionLock = {}
@@ -98,7 +100,7 @@ class PlaybackSessionManager {
     if (wasUpdated) {
       await this.db.updateEntity('user', user)
       var itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId)
-      this.clientEmitter(user.id, 'user_item_progress_updated', {
+      SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
         id: itemProgress.id,
         data: itemProgress.toJSON()
       })
@@ -147,7 +149,7 @@ class PlaybackSessionManager {
         newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
       } else {
         Logger.debug(`[PlaybackSessionManager] "${user.username}" starting stream session for item "${libraryItem.id}"`)
-        var stream = new Stream(newPlaybackSession.id, this.StreamsPath, user, libraryItem, episodeId, userStartTime, this.clientEmitter.bind(this))
+        var stream = new Stream(newPlaybackSession.id, this.StreamsPath, user, libraryItem, episodeId, userStartTime)
         await stream.generatePlaylist()
         stream.start() // Start transcode
 
@@ -167,7 +169,7 @@ class PlaybackSessionManager {
     user.currentSessionId = newPlaybackSession.id
 
     this.sessions.push(newPlaybackSession)
-    this.emitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems))
+    SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems))
 
     return newPlaybackSession
   }
@@ -193,7 +195,7 @@ class PlaybackSessionManager {
 
       await this.db.updateEntity('user', user)
       var itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId)
-      this.clientEmitter(user.id, 'user_item_progress_updated', {
+      SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
         id: itemProgress.id,
         data: itemProgress.toJSON()
       })
@@ -211,7 +213,7 @@ class PlaybackSessionManager {
       await this.saveSession(session)
     }
     Logger.debug(`[PlaybackSessionManager] closeSession "${session.id}"`)
-    this.emitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems))
+    SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems))
     return this.removeSession(session.id)
   }
 
diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js
index 59bc7ea4..74751d45 100644
--- a/server/managers/PodcastManager.js
+++ b/server/managers/PodcastManager.js
@@ -1,23 +1,24 @@
+const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
 const fs = require('../libs/fsExtra')
 
 const { getPodcastFeed } = require('../utils/podcastUtils')
-const Logger = require('../Logger')
-
 const { downloadFile, removeFile } = require('../utils/fileUtils')
 const filePerms = require('../utils/filePerms')
 const { levenshteinDistance } = require('../utils/index')
 const opmlParser = require('../utils/parsers/parseOPML')
 const prober = require('../utils/prober')
+
 const LibraryFile = require('../objects/files/LibraryFile')
 const PodcastEpisodeDownload = require('../objects/PodcastEpisodeDownload')
 const PodcastEpisode = require('../objects/entities/PodcastEpisode')
 const AudioFile = require('../objects/files/AudioFile')
 
 class PodcastManager {
-  constructor(db, watcher, emitter, notificationManager) {
+  constructor(db, watcher, notificationManager) {
     this.db = db
     this.watcher = watcher
-    this.emitter = emitter
     this.notificationManager = notificationManager
 
     this.downloadQueue = []
@@ -63,11 +64,11 @@ class PodcastManager {
   async startPodcastEpisodeDownload(podcastEpisodeDownload) {
     if (this.currentDownload) {
       this.downloadQueue.push(podcastEpisodeDownload)
-      this.emitter('episode_download_queued', podcastEpisodeDownload.toJSONForClient())
+      SocketAuthority.emitter('episode_download_queued', podcastEpisodeDownload.toJSONForClient())
       return
     }
 
-    this.emitter('episode_download_started', podcastEpisodeDownload.toJSONForClient())
+    SocketAuthority.emitter('episode_download_started', podcastEpisodeDownload.toJSONForClient())
     this.currentDownload = podcastEpisodeDownload
 
     // Ignores all added files to this dir
@@ -97,7 +98,7 @@ class PodcastManager {
       this.currentDownload.setFinished(false)
     }
 
-    this.emitter('episode_download_finished', this.currentDownload.toJSONForClient())
+    SocketAuthority.emitter('episode_download_finished', this.currentDownload.toJSONForClient())
 
     this.watcher.removeIgnoreDir(this.currentDownload.libraryItem.path)
     this.currentDownload = null
@@ -141,7 +142,7 @@ class PodcastManager {
 
     libraryItem.updatedAt = Date.now()
     await this.db.updateLibraryItem(libraryItem)
-    this.emitter('item_updated', libraryItem.toJSONExpanded())
+    SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
 
     if (this.currentDownload.isAutoDownload) { // Notifications only for auto downloaded episodes
       this.notificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode)
@@ -230,7 +231,7 @@ class PodcastManager {
     libraryItem.media.lastEpisodeCheck = Date.now()
     libraryItem.updatedAt = Date.now()
     await this.db.updateLibraryItem(libraryItem)
-    this.emitter('item_updated', libraryItem.toJSONExpanded())
+    SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     return libraryItem.media.autoDownloadEpisodes
   }
 
@@ -269,7 +270,7 @@ class PodcastManager {
     libraryItem.media.lastEpisodeCheck = Date.now()
     libraryItem.updatedAt = Date.now()
     await this.db.updateLibraryItem(libraryItem)
-    this.emitter('item_updated', libraryItem.toJSONExpanded())
+    SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
 
     return newEpisodes
   }
diff --git a/server/managers/RssFeedManager.js b/server/managers/RssFeedManager.js
index ce15bbea..bd90b96c 100644
--- a/server/managers/RssFeedManager.js
+++ b/server/managers/RssFeedManager.js
@@ -1,13 +1,15 @@
 const Path = require('path')
+
+const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
 const fs = require('../libs/fsExtra')
 const Feed = require('../objects/Feed')
-const Logger = require('../Logger')
 
-// Not functional at the moment
 class RssFeedManager {
-  constructor(db, emitter) {
+  constructor(db) {
     this.db = db
-    this.emitter = emitter
+
     this.feeds = {}
   }
 
@@ -104,7 +106,7 @@ class RssFeedManager {
 
     Logger.debug(`[RssFeedManager] Opened RSS feed ${feed.feedUrl}`)
     await this.db.insertEntity('feed', feed)
-    this.emitter('rss_feed_open', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl })
+    SocketAuthority.emitter('rss_feed_open', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl })
     return feed
   }
 
@@ -118,7 +120,7 @@ class RssFeedManager {
     if (!this.feeds[id]) return
     var feed = this.feeds[id]
     await this.db.removeEntity('feed', id)
-    this.emitter('rss_feed_closed', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl })
+    SocketAuthority.emitter('rss_feed_closed', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl })
     delete this.feeds[id]
     Logger.info(`[RssFeedManager] Closed RSS feed "${feed.feedUrl}"`)
   }
diff --git a/server/managers/TaskManager.js b/server/managers/TaskManager.js
index d962d0c8..38e8b580 100644
--- a/server/managers/TaskManager.js
+++ b/server/managers/TaskManager.js
@@ -1,19 +1,19 @@
-class TaskManager {
-  constructor(emitter) {
-    this.emitter = emitter
+const SocketAuthority = require('../SocketAuthority')
 
+class TaskManager {
+  constructor() {
     this.tasks = []
   }
 
   addTask(task) {
     this.tasks.push(task)
-    this.emitter('task_started', task.toJSON())
+    SocketAuthority.emitter('task_started', task.toJSON())
   }
 
   taskFinished(task) {
     if (this.tasks.some(t => t.id === task.id)) {
       this.tasks = this.tasks.filter(t => t.id !== task.id)
-      this.emitter('task_finished', task.toJSON())
+      SocketAuthority.emitter('task_finished', task.toJSON())
     }
   }
 }
diff --git a/server/objects/FeedEpisode.js b/server/objects/FeedEpisode.js
index 28a60e78..ed3a7e21 100644
--- a/server/objects/FeedEpisode.js
+++ b/server/objects/FeedEpisode.js
@@ -92,11 +92,15 @@ class FeedEpisode {
     const media = libraryItem.media
     const mediaMetadata = media.metadata
 
-    var title = audioTrack.title
-    if (libraryItem.media.chapters.length) {
-      // If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
-      var matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
-      if (matchingChapter && matchingChapter.title) title = matchingChapter.title
+    let title = audioTrack.title
+    if (libraryItem.media.tracks.length == 1) { // If audiobook is a single file, use book title instead of chapter/file title
+      title = libraryItem.media.metadata.title
+    } else {
+      if (libraryItem.media.chapters.length) {
+        // If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
+        var matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
+        if (matchingChapter && matchingChapter.title) title = matchingChapter.title
+      }
     }
 
     this.id = String(audioTrack.index)
diff --git a/server/objects/Stream.js b/server/objects/Stream.js
index 19ea46e0..90403ea3 100644
--- a/server/objects/Stream.js
+++ b/server/objects/Stream.js
@@ -1,8 +1,12 @@
-const Ffmpeg = require('../libs/fluentFfmpeg')
+
 const EventEmitter = require('events')
 const Path = require('path')
-const fs = require('../libs/fsExtra')
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
+const fs = require('../libs/fsExtra')
+const Ffmpeg = require('../libs/fluentFfmpeg')
+
 const { secondsToTimestamp } = require('../utils/index')
 const { writeConcatFile } = require('../utils/ffmpegHelpers')
 const { AudioMimeType } = require('../utils/constants')
@@ -10,14 +14,13 @@ const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator')
 const AudioTrack = require('./files/AudioTrack')
 
 class Stream extends EventEmitter {
-  constructor(sessionId, streamPath, user, libraryItem, episodeId, startTime, clientEmitter, transcodeOptions = {}) {
+  constructor(sessionId, streamPath, user, libraryItem, episodeId, startTime, transcodeOptions = {}) {
     super()
 
     this.id = sessionId
     this.user = user
     this.libraryItem = libraryItem
     this.episodeId = episodeId
-    this.clientEmitter = clientEmitter
 
     this.transcodeOptions = transcodeOptions
 
@@ -408,7 +411,7 @@ class Stream extends EventEmitter {
   }
 
   clientEmit(evtName, data) {
-    if (this.clientEmitter) this.clientEmitter(this.user.id, evtName, data)
+    SocketAuthority.clientEmitter(this.user.id, evtName, data)
   }
 
   getAudioTrack() {
diff --git a/server/objects/user/MediaProgress.js b/server/objects/user/MediaProgress.js
index 70b2d678..3c4b4ca3 100644
--- a/server/objects/user/MediaProgress.js
+++ b/server/objects/user/MediaProgress.js
@@ -63,12 +63,12 @@ class MediaProgress {
     this.isFinished = !!progress.isFinished || this.progress == 1
     this.hideFromContinueListening = !!progress.hideFromContinueListening
     this.lastUpdate = Date.now()
-    this.startedAt = Date.now()
     this.finishedAt = null
     if (this.isFinished) {
-      this.finishedAt = Date.now()
+      this.finishedAt = progress.finishedAt || Date.now()
       this.progress = 1
     }
+    this.startedAt = progress.startedAt || this.finishedAt || Date.now()
   }
 
   update(payload) {
@@ -95,7 +95,7 @@ class MediaProgress {
     // If time remaining is less than 5 seconds then mark as finished
     if ((this.progress >= 1 || (this.duration && !isNaN(timeRemaining) && timeRemaining < 5))) {
       this.isFinished = true
-      this.finishedAt = Date.now()
+      this.finishedAt = payload.finishedAt || Date.now()
       this.progress = 1
     } else if (this.progress < 1 && this.isFinished) {
       this.isFinished = false
@@ -103,7 +103,7 @@ class MediaProgress {
     }
 
     if (!this.startedAt) {
-      this.startedAt = Date.now()
+      this.startedAt = this.finishedAt || Date.now()
     }
     if (hasUpdates) {
       if (payload.hideFromContinueListening === undefined) {
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index d296cf50..febcb4f3 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -1,8 +1,11 @@
 const express = require('express')
 const Path = require('path')
+
+const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
 const fs = require('../libs/fsExtra')
 const date = require('../libs/dateAndTime')
-const Logger = require('../Logger')
 
 const LibraryController = require('../controllers/LibraryController')
 const UserController = require('../controllers/UserController')
@@ -29,25 +32,22 @@ const Author = require('../objects/entities/Author')
 const Series = require('../objects/entities/Series')
 
 class ApiRouter {
-  constructor(db, auth, scanner, playbackSessionManager, abMergeManager, coverManager, backupManager, watcher, cacheManager, podcastManager, audioMetadataManager, rssFeedManager, cronManager, notificationManager, taskManager, getUsersOnline, emitter, clientEmitter) {
-    this.db = db
-    this.auth = auth
-    this.scanner = scanner
-    this.playbackSessionManager = playbackSessionManager
-    this.abMergeManager = abMergeManager
-    this.backupManager = backupManager
-    this.coverManager = coverManager
-    this.watcher = watcher
-    this.cacheManager = cacheManager
-    this.podcastManager = podcastManager
-    this.audioMetadataManager = audioMetadataManager
-    this.rssFeedManager = rssFeedManager
-    this.cronManager = cronManager
-    this.notificationManager = notificationManager
-    this.taskManager = taskManager
-    this.getUsersOnline = getUsersOnline
-    this.emitter = emitter
-    this.clientEmitter = clientEmitter
+  constructor(Server) {
+    this.db = Server.db
+    this.auth = Server.auth
+    this.scanner = Server.scanner
+    this.playbackSessionManager = Server.playbackSessionManager
+    this.abMergeManager = Server.abMergeManager
+    this.backupManager = Server.backupManager
+    this.coverManager = Server.coverManager
+    this.watcher = Server.watcher
+    this.cacheManager = Server.cacheManager
+    this.podcastManager = Server.podcastManager
+    this.audioMetadataManager = Server.audioMetadataManager
+    this.rssFeedManager = Server.rssFeedManager
+    this.cronManager = Server.cronManager
+    this.notificationManager = Server.notificationManager
+    this.taskManager = Server.taskManager
 
     this.bookFinder = new BookFinder()
     this.authorFinder = new AuthorFinder()
@@ -163,10 +163,11 @@ class ApiRouter {
     //
     // Backup Routes
     //
-    this.router.post('/backups', BackupController.create.bind(this))
-    this.router.delete('/backups/:id', BackupController.delete.bind(this))
-    this.router.get('/backups/:id/apply', BackupController.apply.bind(this))
-    this.router.post('/backups/upload', BackupController.upload.bind(this))
+    this.router.get('/backups', BackupController.middleware.bind(this), BackupController.getAll.bind(this))
+    this.router.post('/backups', BackupController.middleware.bind(this), BackupController.create.bind(this))
+    this.router.delete('/backups/:id', BackupController.middleware.bind(this), BackupController.delete.bind(this))
+    this.router.get('/backups/:id/apply', BackupController.middleware.bind(this), BackupController.apply.bind(this))
+    this.router.post('/backups/upload', BackupController.middleware.bind(this), BackupController.upload.bind(this))
 
     //
     // File System Routes
@@ -261,13 +262,13 @@ class ApiRouter {
 
   async getDirectories(dir, relpath, excludedDirs, level = 0) {
     try {
-      var paths = await fs.readdir(dir)
+      const paths = await fs.readdir(dir)
 
-      var dirs = await Promise.all(paths.map(async dirname => {
-        var fullPath = Path.join(dir, dirname)
-        var path = Path.join(relpath, dirname)
+      let dirs = await Promise.all(paths.map(async dirname => {
+        const fullPath = Path.join(dir, dirname)
+        const path = Path.join(relpath, dirname)
 
-        var isDir = (await fs.lstat(fullPath)).isDirectory()
+        const isDir = (await fs.lstat(fullPath)).isDirectory()
         if (isDir && !excludedDirs.includes(path) && dirname !== 'node_modules') {
           return {
             path,
@@ -292,13 +293,13 @@ class ApiRouter {
   // Helper Methods
   //
   userJsonWithItemProgressDetails(user, hideRootToken = false) {
-    var json = user.toJSONForBrowser()
+    const json = user.toJSONForBrowser()
     if (json.type === 'root' && hideRootToken) {
       json.token = ''
     }
 
     json.mediaProgress = json.mediaProgress.map(lip => {
-      var libraryItem = this.db.libraryItems.find(li => li.id === lip.libraryItemId)
+      const libraryItem = this.db.libraryItems.find(li => li.id === lip.libraryItemId)
       if (!libraryItem) {
         Logger.warn('[ApiRouter] Library item not found for users progress ' + lip.libraryItemId)
         lip.media = null
@@ -325,34 +326,21 @@ class ApiRouter {
   async handleDeleteLibraryItem(libraryItem) {
     // Remove libraryItem from users
     for (let i = 0; i < this.db.users.length; i++) {
-      var user = this.db.users[i]
-      var madeUpdates = user.removeMediaProgressForLibraryItem(libraryItem.id)
-      if (madeUpdates) {
+      const user = this.db.users[i]
+      if (user.removeMediaProgressForLibraryItem(libraryItem.id)) {
         await this.db.updateEntity('user', user)
       }
     }
 
-    // remove any streams open for this audiobook
-    // TODO: Change to PlaybackSessionManager to remove open sessions for user
-    // var streams = this.streamManager.streams.filter(stream => stream.audiobookId === libraryItem.id)
-    // for (let i = 0; i < streams.length; i++) {
-    //   var stream = streams[i]
-    //   var client = stream.client
-    //   await stream.close()
-    //   if (client && client.user) {
-    //     client.user.stream = null
-    //     client.stream = null
-    //     this.db.updateUserStream(client.user.id, null)
-    //   }
-    // }
+    // TODO: Remove open sessions for library item
 
     // remove book from collections
-    var collectionsWithBook = this.db.collections.filter(c => c.books.includes(libraryItem.id))
+    const collectionsWithBook = this.db.collections.filter(c => c.books.includes(libraryItem.id))
     for (let i = 0; i < collectionsWithBook.length; i++) {
-      var collection = collectionsWithBook[i]
+      const collection = collectionsWithBook[i]
       collection.removeBook(libraryItem.id)
       await this.db.updateEntity('collection', collection)
-      this.clientEmitter(collection.userId, 'collection_updated', collection.toJSONExpanded(this.db.libraryItems))
+      SocketAuthority.clientEmitter(collection.userId, 'collection_updated', collection.toJSONExpanded(this.db.libraryItems))
     }
 
     // purge cover cache
@@ -360,34 +348,32 @@ class ApiRouter {
       await this.cacheManager.purgeCoverCache(libraryItem.id)
     }
 
-    var json = libraryItem.toJSONExpanded()
     await this.db.removeLibraryItem(libraryItem.id)
-    this.emitter('item_removed', json)
+    SocketAuthority.emitter('item_removed', libraryItem.toJSONExpanded())
   }
 
   async getUserListeningSessionsHelper(userId) {
-    var userSessions = await this.db.selectUserSessions(userId)
+    const userSessions = await this.db.selectUserSessions(userId)
     return userSessions.sort((a, b) => b.updatedAt - a.updatedAt)
   }
 
   async getAllSessionsWithUserData() {
-    var sessions = await this.db.getAllSessions()
+    const sessions = await this.db.getAllSessions()
     sessions.sort((a, b) => b.updatedAt - a.updatedAt)
     return sessions.map(se => {
-      var user = this.db.users.find(u => u.id === se.userId)
-      var _se = {
+      const user = this.db.users.find(u => u.id === se.userId)
+      return {
         ...se,
         user: user ? { id: user.id, username: user.username } : null
       }
-      return _se
     })
   }
 
   async getUserListeningStatsHelpers(userId) {
     const today = date.format(new Date(), 'YYYY-MM-DD')
 
-    var listeningSessions = await this.getUserListeningSessionsHelper(userId)
-    var listeningStats = {
+    const listeningSessions = await this.getUserListeningSessionsHelper(userId)
+    const listeningStats = {
       totalTime: 0,
       items: {},
       days: {},
@@ -396,7 +382,7 @@ class ApiRouter {
       recentSessions: listeningSessions.slice(0, 10)
     }
     listeningSessions.forEach((s) => {
-      var sessionTimeListening = s.timeListening
+      let sessionTimeListening = s.timeListening
       if (typeof sessionTimeListening == 'string') {
         sessionTimeListening = Number(sessionTimeListening)
       }
@@ -431,15 +417,15 @@ class ApiRouter {
 
   async createAuthorsAndSeriesForItemUpdate(mediaPayload) {
     if (mediaPayload.metadata) {
-      var mediaMetadata = mediaPayload.metadata
+      const mediaMetadata = mediaPayload.metadata
 
       // Create new authors if in payload
       if (mediaMetadata.authors && mediaMetadata.authors.length) {
         // TODO: validate authors
-        var newAuthors = []
+        const newAuthors = []
         for (let i = 0; i < mediaMetadata.authors.length; i++) {
           if (mediaMetadata.authors[i].id.startsWith('new')) {
-            var author = this.db.authors.find(au => au.checkNameEquals(mediaMetadata.authors[i].name))
+            let author = this.db.authors.find(au => au.checkNameEquals(mediaMetadata.authors[i].name))
             if (!author) {
               author = new Author()
               author.setData(mediaMetadata.authors[i])
@@ -453,17 +439,17 @@ class ApiRouter {
         }
         if (newAuthors.length) {
           await this.db.insertEntities('author', newAuthors)
-          this.emitter('authors_added', newAuthors)
+          SocketAuthority.emitter('authors_added', newAuthors)
         }
       }
 
       // Create new series if in payload
       if (mediaMetadata.series && mediaMetadata.series.length) {
         // TODO: validate series
-        var newSeries = []
+        const newSeries = []
         for (let i = 0; i < mediaMetadata.series.length; i++) {
           if (mediaMetadata.series[i].id.startsWith('new')) {
-            var seriesItem = this.db.series.find(se => se.checkNameEquals(mediaMetadata.series[i].name))
+            let seriesItem = this.db.series.find(se => se.checkNameEquals(mediaMetadata.series[i].name))
             if (!seriesItem) {
               seriesItem = new Series()
               seriesItem.setData(mediaMetadata.series[i])
@@ -477,7 +463,7 @@ class ApiRouter {
         }
         if (newSeries.length) {
           await this.db.insertEntities('series', newSeries)
-          this.emitter('authors_added', newSeries)
+          SocketAuthority.emitter('authors_added', newSeries)
         }
       }
     }
diff --git a/server/routers/HlsRouter.js b/server/routers/HlsRouter.js
index 80a0913e..58896097 100644
--- a/server/routers/HlsRouter.js
+++ b/server/routers/HlsRouter.js
@@ -1,14 +1,17 @@
 const express = require('express')
 const Path = require('path')
-const fs = require('../libs/fsExtra')
+
 const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
+
+const fs = require('../libs/fsExtra')
+
 
 class HlsRouter {
-  constructor(db, auth, playbackSessionManager, emitter) {
+  constructor(db, auth, playbackSessionManager) {
     this.db = db
     this.auth = auth
     this.playbackSessionManager = playbackSessionManager
-    this.emitter = emitter
 
     this.router = express()
     this.init()
@@ -49,7 +52,7 @@ class HlsRouter {
           if (startTimeForReset) {
             // HLS.js will restart the stream at the new time
             Logger.info(`[HlsRouter] Resetting Stream - notify client @${startTimeForReset}s`)
-            this.emitter('stream_reset', {
+            SocketAuthority.emitter('stream_reset', {
               startTime: startTimeForReset,
               streamId: stream.id
             })
diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js
index 16d1355a..68dfdd8f 100644
--- a/server/scanner/Scanner.js
+++ b/server/scanner/Scanner.js
@@ -1,8 +1,9 @@
 const fs = require('../libs/fsExtra')
 const Path = require('path')
+const Logger = require('../Logger')
+const SocketAuthority = require('../SocketAuthority')
 
 // Utils
-const Logger = require('../Logger')
 const { groupFilesIntoLibraryItemPaths, getLibraryItemFileData, scanFolder } = require('../utils/scandir')
 const { comparePaths } = require('../utils/index')
 const { getIno } = require('../utils/fileUtils')
@@ -20,12 +21,11 @@ const Author = require('../objects/entities/Author')
 const Series = require('../objects/entities/Series')
 
 class Scanner {
-  constructor(db, coverManager, emitter) {
+  constructor(db, coverManager) {
     this.ScanLogPath = Path.posix.join(global.MetadataPath, 'logs', 'scans')
 
     this.db = db
     this.coverManager = coverManager
-    this.emitter = emitter
 
     this.cancelLibraryScan = {}
     this.librariesScanning = []
@@ -113,7 +113,7 @@ class Scanner {
     }
 
     if (hasUpdated) {
-      this.emitter('item_updated', libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
       await this.db.updateLibraryItem(libraryItem)
       return ScanResult.UPDATED
     }
@@ -139,7 +139,7 @@ class Scanner {
     libraryScan.verbose = false
     this.librariesScanning.push(libraryScan.getScanEmitData)
 
-    this.emitter('scan_start', libraryScan.getScanEmitData)
+    SocketAuthority.emitter('scan_start', libraryScan.getScanEmitData)
 
     Logger.info(`[Scanner] Starting library scan ${libraryScan.id} for ${libraryScan.libraryName}`)
 
@@ -158,11 +158,11 @@ class Scanner {
     if (canceled && !libraryScan.totalResults) {
       var emitData = libraryScan.getScanEmitData
       emitData.results = null
-      this.emitter('scan_complete', emitData)
+      SocketAuthority.emitter('scan_complete', emitData)
       return
     }
 
-    this.emitter('scan_complete', libraryScan.getScanEmitData)
+    SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData)
 
     if (libraryScan.totalResults) {
       libraryScan.saveLog(this.ScanLogPath)
@@ -302,7 +302,7 @@ class Scanner {
 
   async updateLibraryItemChunk(itemsToUpdate) {
     await this.db.updateLibraryItems(itemsToUpdate)
-    this.emitter('items_updated', itemsToUpdate.map(li => li.toJSONExpanded()))
+    SocketAuthority.emitter('items_updated', itemsToUpdate.map(li => li.toJSONExpanded()))
   }
 
   async rescanLibraryItemDataChunk(itemDataToRescan, libraryScan) {
@@ -320,7 +320,7 @@ class Scanner {
     if (itemsUpdated.length) {
       libraryScan.resultsUpdated += itemsUpdated.length
       await this.db.updateLibraryItems(itemsUpdated)
-      this.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded()))
+      SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded()))
     }
   }
 
@@ -337,7 +337,7 @@ class Scanner {
 
     libraryScan.resultsAdded += newLibraryItems.length
     await this.db.insertLibraryItems(newLibraryItems)
-    this.emitter('items_added', newLibraryItems.map(li => li.toJSONExpanded()))
+    SocketAuthority.emitter('items_added', newLibraryItems.map(li => li.toJSONExpanded()))
   }
 
   async rescanLibraryItem(libraryItemCheckData, libraryScan) {
@@ -458,7 +458,7 @@ class Scanner {
       })
       if (newAuthors.length) {
         await this.db.insertEntities('author', newAuthors)
-        this.emitter('authors_added', newAuthors.map(au => au.toJSON()))
+        SocketAuthority.emitter('authors_added', newAuthors.map(au => au.toJSON()))
       }
     }
     if (libraryItem.media.metadata.series.some(se => se.id.startsWith('new'))) {
@@ -479,7 +479,7 @@ class Scanner {
       })
       if (newSeries.length) {
         await this.db.insertEntities('series', newSeries)
-        this.emitter('series_added', newSeries.map(se => se.toJSON()))
+        SocketAuthority.emitter('series_added', newSeries.map(se => se.toJSON()))
       }
     }
   }
@@ -602,7 +602,7 @@ class Scanner {
             Logger.info(`[Scanner] Scanning file update group and library item was deleted "${existingLibraryItem.media.metadata.title}" - marking as missing`)
             existingLibraryItem.setMissing()
             await this.db.updateLibraryItem(existingLibraryItem)
-            this.emitter('item_updated', existingLibraryItem.toJSONExpanded())
+            SocketAuthority.emitter('item_updated', existingLibraryItem.toJSONExpanded())
 
             itemGroupingResults[itemDir] = ScanResult.REMOVED
             continue;
@@ -629,7 +629,7 @@ class Scanner {
       if (newLibraryItem) {
         await this.createNewAuthorsAndSeries(newLibraryItem)
         await this.db.insertLibraryItem(newLibraryItem)
-        this.emitter('item_added', newLibraryItem.toJSONExpanded())
+        SocketAuthority.emitter('item_added', newLibraryItem.toJSONExpanded())
       }
       itemGroupingResults[itemDir] = newLibraryItem ? ScanResult.ADDED : ScanResult.NOTHING
     }
@@ -747,7 +747,7 @@ class Scanner {
       }
 
       await this.db.updateLibraryItem(libraryItem)
-      this.emitter('item_updated', libraryItem.toJSONExpanded())
+      SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
     }
 
     return {
@@ -846,7 +846,7 @@ class Scanner {
           author = new Author()
           author.setData({ name: authorName })
           await this.db.insertEntity('author', author)
-          this.emitter('author_added', author)
+          SocketAuthority.emitter('author_added', author)
         }
         authorPayload.push(author.toJSONMinimal())
       }
@@ -864,7 +864,7 @@ class Scanner {
           seriesItem = new Series()
           seriesItem.setData({ name: seriesMatchItem.series })
           await this.db.insertEntity('series', seriesItem)
-          this.emitter('series_added', seriesItem)
+          SocketAuthority.emitter('series_added', seriesItem)
         }
         seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
       }
@@ -955,7 +955,7 @@ class Scanner {
     var libraryScan = new LibraryScan()
     libraryScan.setData(library, null, 'match')
     this.librariesScanning.push(libraryScan.getScanEmitData)
-    this.emitter('scan_start', libraryScan.getScanEmitData)
+    SocketAuthority.emitter('scan_start', libraryScan.getScanEmitData)
 
     Logger.info(`[Scanner] matchLibraryItems: Starting library match scan ${libraryScan.id} for ${libraryScan.libraryName}`)
 
@@ -987,14 +987,14 @@ class Scanner {
         delete this.cancelLibraryScan[libraryScan.libraryId]
         var scanData = libraryScan.getScanEmitData
         scanData.results = false
-        this.emitter('scan_complete', scanData)
+        SocketAuthority.emitter('scan_complete', scanData)
         this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id)
         return
       }
     }
 
     this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id)
-    this.emitter('scan_complete', libraryScan.getScanEmitData)
+    SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData)
   }
 
   probeAudioFileWithTone(audioFile) {