mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:User listening sessions page, Update:Listening sessions to save media times and device info
This commit is contained in:
		
							parent
							
								
									54663f0f01
								
							
						
					
					
						commit
						f002532c1e
					
				@ -32,6 +32,7 @@ export default {
 | 
				
			|||||||
      default: ''
 | 
					      default: ''
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    paddingX: Number,
 | 
					    paddingX: Number,
 | 
				
			||||||
 | 
					    paddingY: Number,
 | 
				
			||||||
    small: Boolean,
 | 
					    small: Boolean,
 | 
				
			||||||
    loading: Boolean,
 | 
					    loading: Boolean,
 | 
				
			||||||
    disabled: Boolean
 | 
					    disabled: Boolean
 | 
				
			||||||
@ -48,14 +49,17 @@ export default {
 | 
				
			|||||||
      if (this.small) {
 | 
					      if (this.small) {
 | 
				
			||||||
        list.push('text-sm')
 | 
					        list.push('text-sm')
 | 
				
			||||||
        if (this.paddingX === undefined) list.push('px-4')
 | 
					        if (this.paddingX === undefined) list.push('px-4')
 | 
				
			||||||
        list.push('py-1')
 | 
					        if (this.paddingY === undefined) list.push('py-1')
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        if (this.paddingX === undefined) list.push('px-8')
 | 
					        if (this.paddingX === undefined) list.push('px-8')
 | 
				
			||||||
        list.push('py-2')
 | 
					        if (this.paddingY === undefined) list.push('py-2')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (this.paddingX !== undefined) {
 | 
					      if (this.paddingX !== undefined) {
 | 
				
			||||||
        list.push(`px-${this.paddingX}`)
 | 
					        list.push(`px-${this.paddingX}`)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.paddingY !== undefined) {
 | 
				
			||||||
 | 
					        list.push(`py-${this.paddingY}`)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      if (this.disabled) {
 | 
					      if (this.disabled) {
 | 
				
			||||||
        list.push('cursor-not-allowed')
 | 
					        list.push('cursor-not-allowed')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,10 @@
 | 
				
			|||||||
      <div class="w-full h-px bg-white bg-opacity-10 my-2" />
 | 
					      <div class="w-full h-px bg-white bg-opacity-10 my-2" />
 | 
				
			||||||
      <div class="py-2">
 | 
					      <div class="py-2">
 | 
				
			||||||
        <h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">Listening Stats</h1>
 | 
					        <h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">Listening Stats</h1>
 | 
				
			||||||
        <p class="text-sm text-gray-300">{{ listeningSessions.length }} Listening Sessions</p>
 | 
					        <div class="flex items-center">
 | 
				
			||||||
 | 
					          <p class="text-sm text-gray-300">{{ listeningSessions.length }} Listening Sessions</p>
 | 
				
			||||||
 | 
					          <ui-btn :to="`/config/users/${user.id}/sessions`" class="text-xs mx-2" :padding-x="1.5" :padding-y="1">View All</ui-btn>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        <p class="text-sm text-gray-300">
 | 
					        <p class="text-sm text-gray-300">
 | 
				
			||||||
          Total Time Listened: 
 | 
					          Total Time Listened: 
 | 
				
			||||||
          <span class="font-mono text-base">{{ listeningTimePretty }}</span>
 | 
					          <span class="font-mono text-base">{{ listeningTimePretty }}</span>
 | 
				
			||||||
@ -35,7 +38,7 @@
 | 
				
			|||||||
        <div v-if="latestSession" class="mt-4">
 | 
					        <div v-if="latestSession" class="mt-4">
 | 
				
			||||||
          <h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">Last Listening Session</h1>
 | 
					          <h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">Last Listening Session</h1>
 | 
				
			||||||
          <p class="text-sm text-gray-300">
 | 
					          <p class="text-sm text-gray-300">
 | 
				
			||||||
            <strong>{{ latestSession.displayTitle }}</strong> {{ $dateDistanceFromNow(latestSession.updatedAt) }} for  <span class="font-mono text-base">{{ $elapsedPrettyExtended(this.latestSession.timeListening) }}</span>
 | 
					            <strong>{{ latestSession.displayTitle }}</strong> {{ $dateDistanceFromNow(latestSession.updatedAt) }} for <span class="font-mono text-base">{{ $elapsedPrettyExtended(this.latestSession.timeListening) }}</span>
 | 
				
			||||||
          </p>
 | 
					          </p>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@ -73,7 +76,7 @@
 | 
				
			|||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
        </table>
 | 
					        </table>
 | 
				
			||||||
        <p v-else class="text-white text-opacity-50">Nothing read yet...</p>
 | 
					        <p v-else class="text-white text-opacity-50">Nothing listened to yet...</p>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
							
								
								
									
										146
									
								
								client/pages/config/users/_id/sessions.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								client/pages/config/users/_id/sessions.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="w-full h-full">
 | 
				
			||||||
 | 
					    <div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-0 sm:p-4 mb-8">
 | 
				
			||||||
 | 
					      <nuxt-link :to="`/config/users/${user.id}`" class="text-white text-opacity-70 hover:text-opacity-100 hover:bg-white hover:bg-opacity-5 cursor-pointer rounded-full px-2 sm:px-0">
 | 
				
			||||||
 | 
					        <div class="flex items-center">
 | 
				
			||||||
 | 
					          <div class="h-10 w-10 flex items-center justify-center">
 | 
				
			||||||
 | 
					            <span class="material-icons text-2xl">arrow_back</span>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <p class="pl-1">Back to User</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </nuxt-link>
 | 
				
			||||||
 | 
					      <div class="flex items-center mb-2 mt-4 px-2 sm:px-0">
 | 
				
			||||||
 | 
					        <widgets-online-indicator :value="!!userOnline" />
 | 
				
			||||||
 | 
					        <h1 class="text-xl pl-2">{{ username }}</h1>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div class="w-full h-px bg-white bg-opacity-10 my-2" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div class="py-2">
 | 
				
			||||||
 | 
					        <h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">Listening Sessions</h1>
 | 
				
			||||||
 | 
					        <table v-if="listeningSessions.length" class="userSessionsTable">
 | 
				
			||||||
 | 
					          <tr class="bg-primary bg-opacity-40">
 | 
				
			||||||
 | 
					            <th class="flex-grow text-left">Item</th>
 | 
				
			||||||
 | 
					            <th class="w-40 text-left hidden md:table-cell">Play Method</th>
 | 
				
			||||||
 | 
					            <th class="w-40 text-left hidden sm:table-cell">Device Info</th>
 | 
				
			||||||
 | 
					            <th class="w-20">Listening Time</th>
 | 
				
			||||||
 | 
					            <th class="w-20">Last Time</th>
 | 
				
			||||||
 | 
					            <!-- <th class="w-40 hidden sm:table-cell">Started At</th> -->
 | 
				
			||||||
 | 
					            <th class="w-40 hidden sm:table-cell">Last Update</th>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					          <tr v-for="session in listeningSessions" :key="session.id">
 | 
				
			||||||
 | 
					            <td class="py-1">
 | 
				
			||||||
 | 
					              <p class="text-sm text-gray-200">{{ session.displayTitle }}</p>
 | 
				
			||||||
 | 
					              <p class="text-xs text-gray-400">{{ session.displayAuthor }}</p>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td class="hidden md:table-cell">
 | 
				
			||||||
 | 
					              <p class="text-xs">{{ getPlayMethodName(session.playMethod) }} with {{ session.mediaPlayer }}</p>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td class="hidden sm:table-cell">
 | 
				
			||||||
 | 
					              <p class="text-xs" v-html="getDeviceInfoString(session.deviceInfo)" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td class="text-center">
 | 
				
			||||||
 | 
					              <p class="text-xs font-mono">{{ $elapsedPretty(session.timeListening) }}</p>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td class="text-center">
 | 
				
			||||||
 | 
					              <p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <!-- <td class="text-center hidden sm:table-cell">
 | 
				
			||||||
 | 
					              <ui-tooltip v-if="session.startedAt" direction="top" :text="$formatDate(session.startedAt, 'MMMM do, yyyy HH:mm')">
 | 
				
			||||||
 | 
					                <p class="text-xs">{{ $dateDistanceFromNow(session.startedAt) }}</p>
 | 
				
			||||||
 | 
					              </ui-tooltip>
 | 
				
			||||||
 | 
					            </td> -->
 | 
				
			||||||
 | 
					            <td class="text-center hidden sm:table-cell">
 | 
				
			||||||
 | 
					              <ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDate(session.updatedAt, 'MMMM do, yyyy HH:mm')">
 | 
				
			||||||
 | 
					                <p class="text-xs">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
 | 
				
			||||||
 | 
					              </ui-tooltip>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					        <p v-else class="text-white text-opacity-50">No sessions yet...</p>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  async asyncData({ params, redirect, app }) {
 | 
				
			||||||
 | 
					    var user = await app.$axios.$get(`/api/users/${params.id}`).catch((error) => {
 | 
				
			||||||
 | 
					      console.error('Failed to get user', error)
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    if (!user) return redirect('/config/users')
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      user
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      listeningSessions: []
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    username() {
 | 
				
			||||||
 | 
					      return this.user.username
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userOnline() {
 | 
				
			||||||
 | 
					      return this.$store.getters['users/getIsUserOnline'](this.user.id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    getDeviceInfoString(deviceInfo) {
 | 
				
			||||||
 | 
					      if (!deviceInfo) return ''
 | 
				
			||||||
 | 
					      var lines = []
 | 
				
			||||||
 | 
					      if (deviceInfo.osName) lines.push(`${deviceInfo.osName} ${deviceInfo.osVersion}`)
 | 
				
			||||||
 | 
					      if (deviceInfo.browserName) lines.push(deviceInfo.browserName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (deviceInfo.manufacturer && deviceInfo.model) lines.push(`${deviceInfo.manufacturer} ${deviceInfo.model}`)
 | 
				
			||||||
 | 
					      if (deviceInfo.sdkVersion) lines.push(`SDK Version: ${deviceInfo.sdkVersion}`)
 | 
				
			||||||
 | 
					      return lines.join('<br>')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    getPlayMethodName(playMethod) {
 | 
				
			||||||
 | 
					      if (playMethod === this.$constants.PlayMethod.DIRECTPLAY) return 'Direct Play'
 | 
				
			||||||
 | 
					      else if (playMethod === this.$constants.PlayMethod.TRANSCODE) return 'Transcode'
 | 
				
			||||||
 | 
					      else if (playMethod === this.$constants.PlayMethod.DIRECTSTREAM) return 'Direct Stream'
 | 
				
			||||||
 | 
					      else if (playMethod === this.$constants.PlayMethod.LOCAL) return 'Local'
 | 
				
			||||||
 | 
					      return 'Unknown'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    async init() {
 | 
				
			||||||
 | 
					      console.log(navigator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.listeningSessions = await this.$axios.$get(`/api/users/${this.user.id}/listening-sessions`).catch((err) => {
 | 
				
			||||||
 | 
					        console.error('Failed to load listening sesions', err)
 | 
				
			||||||
 | 
					        return []
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    this.init()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					.userSessionsTable {
 | 
				
			||||||
 | 
					  border-collapse: collapse;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  border: 1px solid #474747;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.userSessionsTable tr:nth-child(even) {
 | 
				
			||||||
 | 
					  background-color: #2e2e2e;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.userSessionsTable tr:not(:first-child) {
 | 
				
			||||||
 | 
					  background-color: #373838;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.userSessionsTable tr:hover:not(:first-child) {
 | 
				
			||||||
 | 
					  background-color: #474747;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.userSessionsTable td {
 | 
				
			||||||
 | 
					  padding: 4px 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.userSessionsTable th {
 | 
				
			||||||
 | 
					  padding: 4px 8px;
 | 
				
			||||||
 | 
					  font-size: 0.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@ -28,7 +28,8 @@ const BookshelfView = {
 | 
				
			|||||||
const PlayMethod = {
 | 
					const PlayMethod = {
 | 
				
			||||||
  DIRECTPLAY: 0,
 | 
					  DIRECTPLAY: 0,
 | 
				
			||||||
  DIRECTSTREAM: 1,
 | 
					  DIRECTSTREAM: 1,
 | 
				
			||||||
  TRANSCODE: 2
 | 
					  TRANSCODE: 2,
 | 
				
			||||||
 | 
					  LOCAL: 3
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Constants = {
 | 
					const Constants = {
 | 
				
			||||||
 | 
				
			|||||||
@ -57,6 +57,7 @@ Vue.prototype.$elapsedPretty = (seconds, useFullNames = false) => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.prototype.$secondsToTimestamp = (seconds) => {
 | 
					Vue.prototype.$secondsToTimestamp = (seconds) => {
 | 
				
			||||||
 | 
					  if (!seconds) return '0:00'
 | 
				
			||||||
  var _seconds = seconds
 | 
					  var _seconds = seconds
 | 
				
			||||||
  var _minutes = Math.floor(seconds / 60)
 | 
					  var _minutes = Math.floor(seconds / 60)
 | 
				
			||||||
  _seconds -= _minutes * 60
 | 
					  _seconds -= _minutes * 60
 | 
				
			||||||
 | 
				
			|||||||
@ -189,8 +189,8 @@ class LibraryItemController {
 | 
				
			|||||||
      Logger.error(`[LibraryItemController] startPlaybackSession cannot playback ${req.libraryItem.id}`)
 | 
					      Logger.error(`[LibraryItemController] startPlaybackSession cannot playback ${req.libraryItem.id}`)
 | 
				
			||||||
      return res.sendStatus(404)
 | 
					      return res.sendStatus(404)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const options = req.body || {}
 | 
					
 | 
				
			||||||
    this.playbackSessionManager.startSessionRequest(req.user, req.libraryItem, null, options, res)
 | 
					    this.playbackSessionManager.startSessionRequest(req, res, null)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // POST: api/items/:id/play/:episodeId
 | 
					  // POST: api/items/:id/play/:episodeId
 | 
				
			||||||
@ -206,8 +206,7 @@ class LibraryItemController {
 | 
				
			|||||||
      return res.sendStatus(404)
 | 
					      return res.sendStatus(404)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const options = req.body || {}
 | 
					    this.playbackSessionManager.startSessionRequest(req, res, episodeId)
 | 
				
			||||||
    this.playbackSessionManager.startSessionRequest(req.user, libraryItem, episodeId, options, res)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // PATCH: api/items/:id/tracks
 | 
					  // PATCH: api/items/:id/tracks
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								server/libs/isJs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								server/libs/isJs.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										174
									
								
								server/libs/requestIp.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								server/libs/requestIp.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,174 @@
 | 
				
			|||||||
 | 
					// SOURCE: https://github.com/pbojinov/request-ip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var is = require('./isJs');
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Parse x-forwarded-for headers.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {string} value - The value to be parsed.
 | 
				
			||||||
 | 
					 * @return {string|null} First known IP address, if any.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getClientIpFromXForwardedFor(value) {
 | 
				
			||||||
 | 
					  if (!is.existy(value)) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (is.not.string(value)) {
 | 
				
			||||||
 | 
					    throw new TypeError("Expected a string, got \"".concat(_typeof(value), "\""));
 | 
				
			||||||
 | 
					  } // x-forwarded-for may return multiple IP addresses in the format:
 | 
				
			||||||
 | 
					  // "client IP, proxy 1 IP, proxy 2 IP"
 | 
				
			||||||
 | 
					  // Therefore, the right-most IP address is the IP address of the most recent proxy
 | 
				
			||||||
 | 
					  // and the left-most IP address is the IP address of the originating client.
 | 
				
			||||||
 | 
					  // source: http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html
 | 
				
			||||||
 | 
					  // Azure Web App's also adds a port for some reason, so we'll only use the first part (the IP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var forwardedIps = value.split(',').map(function (e) {
 | 
				
			||||||
 | 
					    var ip = e.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ip.includes(':')) {
 | 
				
			||||||
 | 
					      var splitted = ip.split(':'); // make sure we only use this if it's ipv4 (ip:port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (splitted.length === 2) {
 | 
				
			||||||
 | 
					        return splitted[0];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ip;
 | 
				
			||||||
 | 
					  }); // Sometimes IP addresses in this header can be 'unknown' (http://stackoverflow.com/a/11285650).
 | 
				
			||||||
 | 
					  // Therefore taking the left-most IP address that is not unknown
 | 
				
			||||||
 | 
					  // A Squid configuration directive can also set the value to "unknown" (http://www.squid-cache.org/Doc/config/forwarded_for/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return forwardedIps.find(is.ip);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Determine client IP address.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param req
 | 
				
			||||||
 | 
					 * @returns {string} ip - The IP address if known, defaulting to empty string if unknown.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getClientIp(req) {
 | 
				
			||||||
 | 
					  // Server is probably behind a proxy.
 | 
				
			||||||
 | 
					  if (req.headers) {
 | 
				
			||||||
 | 
					    // Standard headers used by Amazon EC2, Heroku, and others.
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['x-client-ip'])) {
 | 
				
			||||||
 | 
					      return req.headers['x-client-ip'];
 | 
				
			||||||
 | 
					    } // Load-balancers (AWS ELB) or proxies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var xForwardedFor = getClientIpFromXForwardedFor(req.headers['x-forwarded-for']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(xForwardedFor)) {
 | 
				
			||||||
 | 
					      return xForwardedFor;
 | 
				
			||||||
 | 
					    } // Cloudflare.
 | 
				
			||||||
 | 
					    // @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
 | 
				
			||||||
 | 
					    // CF-Connecting-IP - applied to every request to the origin.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['cf-connecting-ip'])) {
 | 
				
			||||||
 | 
					      return req.headers['cf-connecting-ip'];
 | 
				
			||||||
 | 
					    } // Fastly and Firebase hosting header (When forwared to cloud function)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['fastly-client-ip'])) {
 | 
				
			||||||
 | 
					      return req.headers['fastly-client-ip'];
 | 
				
			||||||
 | 
					    } // Akamai and Cloudflare: True-Client-IP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['true-client-ip'])) {
 | 
				
			||||||
 | 
					      return req.headers['true-client-ip'];
 | 
				
			||||||
 | 
					    } // Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['x-real-ip'])) {
 | 
				
			||||||
 | 
					      return req.headers['x-real-ip'];
 | 
				
			||||||
 | 
					    } // (Rackspace LB and Riverbed's Stingray)
 | 
				
			||||||
 | 
					    // http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
 | 
				
			||||||
 | 
					    // https://splash.riverbed.com/docs/DOC-1926
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['x-cluster-client-ip'])) {
 | 
				
			||||||
 | 
					      return req.headers['x-cluster-client-ip'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['x-forwarded'])) {
 | 
				
			||||||
 | 
					      return req.headers['x-forwarded'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers['forwarded-for'])) {
 | 
				
			||||||
 | 
					      return req.headers['forwarded-for'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.ip(req.headers.forwarded)) {
 | 
				
			||||||
 | 
					      return req.headers.forwarded;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } // Remote address checks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (is.existy(req.connection)) {
 | 
				
			||||||
 | 
					    if (is.ip(req.connection.remoteAddress)) {
 | 
				
			||||||
 | 
					      return req.connection.remoteAddress;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is.existy(req.connection.socket) && is.ip(req.connection.socket.remoteAddress)) {
 | 
				
			||||||
 | 
					      return req.connection.socket.remoteAddress;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (is.existy(req.socket) && is.ip(req.socket.remoteAddress)) {
 | 
				
			||||||
 | 
					    return req.socket.remoteAddress;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (is.existy(req.info) && is.ip(req.info.remoteAddress)) {
 | 
				
			||||||
 | 
					    return req.info.remoteAddress;
 | 
				
			||||||
 | 
					  } // AWS Api Gateway + Lambda
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) {
 | 
				
			||||||
 | 
					    return req.requestContext.identity.sourceIp;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Expose request IP as a middleware.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {object} [options] - Configuration.
 | 
				
			||||||
 | 
					 * @param {string} [options.attributeName] - Name of attribute to augment request object with.
 | 
				
			||||||
 | 
					 * @return {*}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function mw(options) {
 | 
				
			||||||
 | 
					  // Defaults.
 | 
				
			||||||
 | 
					  var configuration = is.not.existy(options) ? {} : options; // Validation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (is.not.object(configuration)) {
 | 
				
			||||||
 | 
					    throw new TypeError('Options must be an object!');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var attributeName = configuration.attributeName || 'clientIp';
 | 
				
			||||||
 | 
					  return function (req, res, next) {
 | 
				
			||||||
 | 
					    var ip = getClientIp(req);
 | 
				
			||||||
 | 
					    Object.defineProperty(req, attributeName, {
 | 
				
			||||||
 | 
					      get: function get() {
 | 
				
			||||||
 | 
					        return ip;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      configurable: true
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    next();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  getClientIpFromXForwardedFor: getClientIpFromXForwardedFor,
 | 
				
			||||||
 | 
					  getClientIp: getClientIp,
 | 
				
			||||||
 | 
					  mw: mw
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										4
									
								
								server/libs/uaParserJs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								server/libs/uaParserJs.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -1,11 +1,16 @@
 | 
				
			|||||||
const Path = require('path')
 | 
					const Path = require('path')
 | 
				
			||||||
const date = require('date-and-time')
 | 
					const date = require('date-and-time')
 | 
				
			||||||
 | 
					const serverVersion = require('../../package.json').version
 | 
				
			||||||
const { PlayMethod } = require('../utils/constants')
 | 
					const { PlayMethod } = require('../utils/constants')
 | 
				
			||||||
const PlaybackSession = require('../objects/PlaybackSession')
 | 
					const PlaybackSession = require('../objects/PlaybackSession')
 | 
				
			||||||
 | 
					const DeviceInfo = require('../objects/DeviceInfo')
 | 
				
			||||||
const Stream = require('../objects/Stream')
 | 
					const Stream = require('../objects/Stream')
 | 
				
			||||||
const Logger = require('../Logger')
 | 
					const Logger = require('../Logger')
 | 
				
			||||||
const fs = require('fs-extra')
 | 
					const fs = require('fs-extra')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uaParserJs = require('../libs/uaParserJs')
 | 
				
			||||||
 | 
					const requestIp = require('../libs/requestIp')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PlaybackSessionManager {
 | 
					class PlaybackSessionManager {
 | 
				
			||||||
  constructor(db, emitter, clientEmitter) {
 | 
					  constructor(db, emitter, clientEmitter) {
 | 
				
			||||||
    this.db = db
 | 
					    this.db = db
 | 
				
			||||||
@ -27,8 +32,21 @@ class PlaybackSessionManager {
 | 
				
			|||||||
    return session ? session.stream : null
 | 
					    return session ? session.stream : null
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async startSessionRequest(user, libraryItem, episodeId, options, res) {
 | 
					  getDeviceInfo(req) {
 | 
				
			||||||
    const session = await this.startSession(user, libraryItem, episodeId, options)
 | 
					    const ua = uaParserJs(req.headers['user-agent'])
 | 
				
			||||||
 | 
					    const ip = requestIp.getClientIp(req)
 | 
				
			||||||
 | 
					    const clientDeviceInfo = req.body ? req.body.deviceInfo || null : null // From mobile client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const deviceInfo = new DeviceInfo()
 | 
				
			||||||
 | 
					    deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion)
 | 
				
			||||||
 | 
					    return deviceInfo
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async startSessionRequest(req, res, episodeId) {
 | 
				
			||||||
 | 
					    const deviceInfo = this.getDeviceInfo(req)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { user, libraryItem, body: options } = req
 | 
				
			||||||
 | 
					    const session = await this.startSession(user, deviceInfo, libraryItem, episodeId, options)
 | 
				
			||||||
    res.json(session.toJSONForClient(libraryItem))
 | 
					    res.json(session.toJSONForClient(libraryItem))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,7 +102,7 @@ class PlaybackSessionManager {
 | 
				
			|||||||
    res.sendStatus(200)
 | 
					    res.sendStatus(200)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async startSession(user, libraryItem, episodeId, options) {
 | 
					  async startSession(user, deviceInfo, libraryItem, episodeId, options) {
 | 
				
			||||||
    // Close any sessions already open for user
 | 
					    // Close any sessions already open for user
 | 
				
			||||||
    var userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
 | 
					    var userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
 | 
				
			||||||
    for (const session of userSessions) {
 | 
					    for (const session of userSessions) {
 | 
				
			||||||
@ -99,7 +117,7 @@ class PlaybackSessionManager {
 | 
				
			|||||||
    var userStartTime = 0
 | 
					    var userStartTime = 0
 | 
				
			||||||
    if (userProgress) userStartTime = Number.parseFloat(userProgress.currentTime) || 0
 | 
					    if (userProgress) userStartTime = Number.parseFloat(userProgress.currentTime) || 0
 | 
				
			||||||
    const newPlaybackSession = new PlaybackSession()
 | 
					    const newPlaybackSession = new PlaybackSession()
 | 
				
			||||||
    newPlaybackSession.setData(libraryItem, user, mediaPlayer, episodeId)
 | 
					    newPlaybackSession.setData(libraryItem, user, mediaPlayer, deviceInfo, userStartTime, episodeId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var audioTracks = []
 | 
					    var audioTracks = []
 | 
				
			||||||
    if (shouldDirectPlay) {
 | 
					    if (shouldDirectPlay) {
 | 
				
			||||||
@ -122,7 +140,6 @@ class PlaybackSessionManager {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    newPlaybackSession.currentTime = userStartTime
 | 
					 | 
				
			||||||
    newPlaybackSession.audioTracks = audioTracks
 | 
					    newPlaybackSession.audioTracks = audioTracks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Will save on the first sync
 | 
					    // Will save on the first sync
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										74
									
								
								server/objects/DeviceInfo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								server/objects/DeviceInfo.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					class DeviceInfo {
 | 
				
			||||||
 | 
					  constructor(deviceInfo = null) {
 | 
				
			||||||
 | 
					    this.ipAddress = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // From User Agent (see: https://www.npmjs.com/package/ua-parser-js)
 | 
				
			||||||
 | 
					    this.browserName = null
 | 
				
			||||||
 | 
					    this.browserVersion = null
 | 
				
			||||||
 | 
					    this.osName = null
 | 
				
			||||||
 | 
					    this.osVersion = null
 | 
				
			||||||
 | 
					    this.deviceType = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // From client
 | 
				
			||||||
 | 
					    this.clientVersion = null
 | 
				
			||||||
 | 
					    this.manufacturer = null
 | 
				
			||||||
 | 
					    this.model = null
 | 
				
			||||||
 | 
					    this.sdkVersion = null // Android Only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.serverVersion = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (deviceInfo) {
 | 
				
			||||||
 | 
					      this.construct(deviceInfo)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  construct(deviceInfo) {
 | 
				
			||||||
 | 
					    for (const key in deviceInfo) {
 | 
				
			||||||
 | 
					      if (deviceInfo[key] !== undefined && this[key] !== undefined) {
 | 
				
			||||||
 | 
					        this[key] = deviceInfo[key]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toJSON() {
 | 
				
			||||||
 | 
					    const obj = {
 | 
				
			||||||
 | 
					      ipAddress: this.ipAddress,
 | 
				
			||||||
 | 
					      browserName: this.browserName,
 | 
				
			||||||
 | 
					      browserVersion: this.browserVersion,
 | 
				
			||||||
 | 
					      osName: this.osName,
 | 
				
			||||||
 | 
					      osVersion: this.osVersion,
 | 
				
			||||||
 | 
					      deviceType: this.deviceType,
 | 
				
			||||||
 | 
					      clientVersion: this.clientVersion,
 | 
				
			||||||
 | 
					      manufacturer: this.manufacturer,
 | 
				
			||||||
 | 
					      model: this.model,
 | 
				
			||||||
 | 
					      sdkVersion: this.sdkVersion,
 | 
				
			||||||
 | 
					      serverVersion: this.serverVersion
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (const key in obj) {
 | 
				
			||||||
 | 
					      if (obj[key] === null || obj[key] === undefined) {
 | 
				
			||||||
 | 
					        delete obj[key]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return obj
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setData(ip, ua, clientDeviceInfo, serverVersion) {
 | 
				
			||||||
 | 
					    this.ipAddress = ip || null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uaObj = ua || {}
 | 
				
			||||||
 | 
					    this.browserName = uaObj.browser.name || null
 | 
				
			||||||
 | 
					    this.browserVersion = uaObj.browser.version || null
 | 
				
			||||||
 | 
					    this.osName = uaObj.os.name || null
 | 
				
			||||||
 | 
					    this.osVersion = uaObj.os.version || null
 | 
				
			||||||
 | 
					    this.deviceType = uaObj.device.type || null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var cdi = clientDeviceInfo || {}
 | 
				
			||||||
 | 
					    this.clientVersion = cdi.clientVersion || null
 | 
				
			||||||
 | 
					    this.manufacturer = cdi.manufacturer || null
 | 
				
			||||||
 | 
					    this.model = cdi.model || null
 | 
				
			||||||
 | 
					    this.sdkVersion = cdi.sdkVersion || null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.serverVersion = serverVersion || null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					module.exports = DeviceInfo
 | 
				
			||||||
@ -3,6 +3,7 @@ const { getId } = require('../utils/index')
 | 
				
			|||||||
const { PlayMethod } = require('../utils/constants')
 | 
					const { PlayMethod } = require('../utils/constants')
 | 
				
			||||||
const BookMetadata = require('./metadata/BookMetadata')
 | 
					const BookMetadata = require('./metadata/BookMetadata')
 | 
				
			||||||
const PodcastMetadata = require('./metadata/PodcastMetadata')
 | 
					const PodcastMetadata = require('./metadata/PodcastMetadata')
 | 
				
			||||||
 | 
					const DeviceInfo = require('./DeviceInfo')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PlaybackSession {
 | 
					class PlaybackSession {
 | 
				
			||||||
  constructor(session) {
 | 
					  constructor(session) {
 | 
				
			||||||
@ -21,18 +22,21 @@ class PlaybackSession {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    this.playMethod = null
 | 
					    this.playMethod = null
 | 
				
			||||||
    this.mediaPlayer = null
 | 
					    this.mediaPlayer = null
 | 
				
			||||||
 | 
					    this.deviceInfo = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.date = null
 | 
					    this.date = null
 | 
				
			||||||
    this.dayOfWeek = null
 | 
					    this.dayOfWeek = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.timeListening = null
 | 
					    this.timeListening = null
 | 
				
			||||||
 | 
					    this.startTime = null // media current time at start of playback
 | 
				
			||||||
 | 
					    this.currentTime = 0 // Last current time set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.startedAt = null
 | 
					    this.startedAt = null
 | 
				
			||||||
    this.updatedAt = null
 | 
					    this.updatedAt = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Not saved in DB
 | 
					    // Not saved in DB
 | 
				
			||||||
    this.lastSave = 0
 | 
					    this.lastSave = 0
 | 
				
			||||||
    this.audioTracks = []
 | 
					    this.audioTracks = []
 | 
				
			||||||
    this.currentTime = 0
 | 
					 | 
				
			||||||
    this.stream = null
 | 
					    this.stream = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (session) {
 | 
					    if (session) {
 | 
				
			||||||
@ -56,10 +60,13 @@ class PlaybackSession {
 | 
				
			|||||||
      duration: this.duration,
 | 
					      duration: this.duration,
 | 
				
			||||||
      playMethod: this.playMethod,
 | 
					      playMethod: this.playMethod,
 | 
				
			||||||
      mediaPlayer: this.mediaPlayer,
 | 
					      mediaPlayer: this.mediaPlayer,
 | 
				
			||||||
 | 
					      deviceInfo: this.deviceInfo ? this.deviceInfo.toJSON() : null,
 | 
				
			||||||
      date: this.date,
 | 
					      date: this.date,
 | 
				
			||||||
      dayOfWeek: this.dayOfWeek,
 | 
					      dayOfWeek: this.dayOfWeek,
 | 
				
			||||||
      timeListening: this.timeListening,
 | 
					      timeListening: this.timeListening,
 | 
				
			||||||
      lastUpdate: this.lastUpdate,
 | 
					      startTime: this.startTime,
 | 
				
			||||||
 | 
					      currentTime: this.currentTime,
 | 
				
			||||||
 | 
					      startedAt: this.startedAt,
 | 
				
			||||||
      updatedAt: this.updatedAt
 | 
					      updatedAt: this.updatedAt
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -80,13 +87,15 @@ class PlaybackSession {
 | 
				
			|||||||
      duration: this.duration,
 | 
					      duration: this.duration,
 | 
				
			||||||
      playMethod: this.playMethod,
 | 
					      playMethod: this.playMethod,
 | 
				
			||||||
      mediaPlayer: this.mediaPlayer,
 | 
					      mediaPlayer: this.mediaPlayer,
 | 
				
			||||||
 | 
					      deviceInfo: this.deviceInfo ? this.deviceInfo.toJSON() : null,
 | 
				
			||||||
      date: this.date,
 | 
					      date: this.date,
 | 
				
			||||||
      dayOfWeek: this.dayOfWeek,
 | 
					      dayOfWeek: this.dayOfWeek,
 | 
				
			||||||
      timeListening: this.timeListening,
 | 
					      timeListening: this.timeListening,
 | 
				
			||||||
      lastUpdate: this.lastUpdate,
 | 
					      startTime: this.startTime,
 | 
				
			||||||
 | 
					      currentTime: this.currentTime,
 | 
				
			||||||
 | 
					      startedAt: this.startedAt,
 | 
				
			||||||
      updatedAt: this.updatedAt,
 | 
					      updatedAt: this.updatedAt,
 | 
				
			||||||
      audioTracks: this.audioTracks.map(at => at.toJSON()),
 | 
					      audioTracks: this.audioTracks.map(at => at.toJSON()),
 | 
				
			||||||
      currentTime: this.currentTime,
 | 
					 | 
				
			||||||
      libraryItem: libraryItem.toJSONExpanded()
 | 
					      libraryItem: libraryItem.toJSONExpanded()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -101,6 +110,7 @@ class PlaybackSession {
 | 
				
			|||||||
    this.duration = session.duration
 | 
					    this.duration = session.duration
 | 
				
			||||||
    this.playMethod = session.playMethod
 | 
					    this.playMethod = session.playMethod
 | 
				
			||||||
    this.mediaPlayer = session.mediaPlayer || null
 | 
					    this.mediaPlayer = session.mediaPlayer || null
 | 
				
			||||||
 | 
					    this.deviceInfo = new DeviceInfo(session.deviceInfo)
 | 
				
			||||||
    this.chapters = session.chapters || []
 | 
					    this.chapters = session.chapters || []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.mediaMetadata = null
 | 
					    this.mediaMetadata = null
 | 
				
			||||||
@ -118,6 +128,9 @@ class PlaybackSession {
 | 
				
			|||||||
    this.dayOfWeek = session.dayOfWeek
 | 
					    this.dayOfWeek = session.dayOfWeek
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.timeListening = session.timeListening || null
 | 
					    this.timeListening = session.timeListening || null
 | 
				
			||||||
 | 
					    this.startTime = session.startTime || 0
 | 
				
			||||||
 | 
					    this.currentTime = session.currentTime || 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.startedAt = session.startedAt
 | 
					    this.startedAt = session.startedAt
 | 
				
			||||||
    this.updatedAt = session.updatedAt || null
 | 
					    this.updatedAt = session.updatedAt || null
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -127,7 +140,7 @@ class PlaybackSession {
 | 
				
			|||||||
    return Math.max(0, Math.min(this.currentTime / this.duration, 1))
 | 
					    return Math.max(0, Math.min(this.currentTime / this.duration, 1))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setData(libraryItem, user, mediaPlayer, episodeId = null) {
 | 
					  setData(libraryItem, user, mediaPlayer, deviceInfo, startTime, episodeId = null) {
 | 
				
			||||||
    this.id = getId('play')
 | 
					    this.id = getId('play')
 | 
				
			||||||
    this.userId = user.id
 | 
					    this.userId = user.id
 | 
				
			||||||
    this.libraryItemId = libraryItem.id
 | 
					    this.libraryItemId = libraryItem.id
 | 
				
			||||||
@ -146,8 +159,13 @@ class PlaybackSession {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.mediaPlayer = mediaPlayer
 | 
					    this.mediaPlayer = mediaPlayer
 | 
				
			||||||
 | 
					    this.deviceInfo = deviceInfo || new DeviceInfo()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.timeListening = 0
 | 
					    this.timeListening = 0
 | 
				
			||||||
 | 
					    this.startTime = startTime
 | 
				
			||||||
 | 
					    this.currentTime = startTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.date = date.format(new Date(), 'YYYY-MM-DD')
 | 
					    this.date = date.format(new Date(), 'YYYY-MM-DD')
 | 
				
			||||||
    this.dayOfWeek = date.format(new Date(), 'dddd')
 | 
					    this.dayOfWeek = date.format(new Date(), 'dddd')
 | 
				
			||||||
    this.startedAt = Date.now()
 | 
					    this.startedAt = Date.now()
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user