2024-06-23 18:01:25 +02:00
const uuidv4 = require ( 'uuid' ) . v4
2022-03-16 01:28:54 +01:00
const Path = require ( 'path' )
2022-05-27 02:09:46 +02:00
const serverVersion = require ( '../../package.json' ) . version
2022-11-24 22:53:58 +01:00
const Logger = require ( '../Logger' )
const SocketAuthority = require ( '../SocketAuthority' )
2023-07-05 01:14:44 +02:00
const Database = require ( '../Database' )
2022-11-24 22:53:58 +01:00
const date = require ( '../libs/dateAndTime' )
const fs = require ( '../libs/fsExtra' )
const uaParserJs = require ( '../libs/uaParser' )
const requestIp = require ( '../libs/requestIp' )
2022-03-20 22:41:06 +01:00
const { PlayMethod } = require ( '../utils/constants' )
2022-11-24 22:53:58 +01:00
2022-03-20 22:41:06 +01:00
const PlaybackSession = require ( '../objects/PlaybackSession' )
2022-05-27 02:09:46 +02:00
const DeviceInfo = require ( '../objects/DeviceInfo' )
2022-03-20 22:41:06 +01:00
const Stream = require ( '../objects/Stream' )
2022-03-16 00:57:15 +01:00
class PlaybackSessionManager {
2023-07-05 01:14:44 +02:00
constructor ( ) {
2022-03-16 01:28:54 +01:00
this . StreamsPath = Path . join ( global . MetadataPath , 'streams' )
2023-07-16 22:05:51 +02:00
this . oldPlaybackSessionMap = { } // TODO: Remove after updated mobile versions
2024-07-04 19:00:54 +02:00
/** @type {PlaybackSession[]} */
2022-03-16 01:28:54 +01:00
this . sessions = [ ]
}
2022-03-18 01:10:47 +01:00
getSession ( sessionId ) {
2024-06-23 18:01:25 +02:00
return this . sessions . find ( ( s ) => s . id === sessionId )
2022-03-18 01:10:47 +01:00
}
getUserSession ( userId ) {
2024-06-23 18:01:25 +02:00
return this . sessions . find ( ( s ) => s . userId === userId )
2022-03-18 01:10:47 +01:00
}
getStream ( sessionId ) {
2022-12-13 00:52:20 +01:00
const session = this . getSession ( sessionId )
2023-04-09 01:01:24 +02:00
return session ? . stream || null
2022-03-16 01:28:54 +01:00
}
2022-03-16 00:57:15 +01:00
2024-06-30 23:36:00 +02:00
/ * *
*
2024-08-11 22:15:34 +02:00
* @ param { import ( '../controllers/SessionController' ) . RequestWithUser } req
2024-06-30 23:36:00 +02:00
* @ param { Object } [ clientDeviceInfo ]
* @ returns { Promise < DeviceInfo > }
* /
async getDeviceInfo ( req , clientDeviceInfo = null ) {
2022-05-27 02:09:46 +02:00
const ua = uaParserJs ( req . headers [ 'user-agent' ] )
const ip = requestIp . getClientIp ( req )
2023-04-09 01:01:24 +02:00
2022-05-27 02:09:46 +02:00
const deviceInfo = new DeviceInfo ( )
2024-08-11 23:07:29 +02:00
deviceInfo . setData ( ip , ua , clientDeviceInfo , serverVersion , req . user ? . id )
2023-07-05 01:14:44 +02:00
if ( clientDeviceInfo ? . deviceId ) {
2024-09-12 23:36:39 +02:00
const existingDevice = await Database . deviceModel . getOldDeviceByDeviceId ( clientDeviceInfo . deviceId )
2023-07-05 01:14:44 +02:00
if ( existingDevice ) {
if ( existingDevice . update ( deviceInfo ) ) {
2024-09-12 23:36:39 +02:00
await Database . deviceModel . updateFromOld ( existingDevice )
2023-07-05 01:14:44 +02:00
}
return existingDevice
}
}
2024-09-12 23:36:39 +02:00
await Database . deviceModel . createFromOld ( deviceInfo )
2023-07-05 01:14:44 +02:00
2022-05-27 02:09:46 +02:00
return deviceInfo
}
2024-06-23 18:01:25 +02:00
/ * *
*
2024-08-11 22:15:34 +02:00
* @ param { import ( '../controllers/SessionController' ) . RequestWithUser } req
2024-06-23 18:01:25 +02:00
* @ param { import ( 'express' ) . Response } res
* @ param { string } [ episodeId ]
* /
2022-05-27 02:09:46 +02:00
async startSessionRequest ( req , res , episodeId ) {
2024-06-30 23:36:00 +02:00
const deviceInfo = await this . getDeviceInfo ( req , req . body ? . deviceInfo )
2023-02-04 20:23:13 +01:00
Logger . debug ( ` [PlaybackSessionManager] startSessionRequest for device ${ deviceInfo . deviceDescription } ` )
2024-08-11 22:15:34 +02:00
const { libraryItem , body : options } = req
2024-08-11 23:07:29 +02:00
const session = await this . startSession ( req . user , deviceInfo , libraryItem , episodeId , options )
2022-03-26 17:59:34 +01:00
res . json ( session . toJSONForClient ( libraryItem ) )
2022-03-18 01:10:47 +01:00
}
2024-08-11 22:15:34 +02:00
/ * *
*
* @ param { import ( '../models/User' ) } user
* @ param { * } session
* @ param { * } payload
* @ param { import ( 'express' ) . Response } res
* /
2022-03-18 01:10:47 +01:00
async syncSessionRequest ( user , session , payload , res ) {
2023-01-08 00:33:05 +01:00
if ( await this . syncSession ( user , session , payload ) ) {
res . sendStatus ( 200 )
} else {
res . sendStatus ( 500 )
2022-03-26 17:59:34 +01:00
}
2022-03-18 01:10:47 +01:00
}
2023-02-05 23:52:17 +01:00
async syncLocalSessionsRequest ( req , res ) {
2024-06-30 23:36:00 +02:00
const deviceInfo = await this . getDeviceInfo ( req , req . body ? . deviceInfo )
2024-08-11 23:07:29 +02:00
const user = req . user
2023-02-05 23:52:17 +01:00
const sessions = req . body . sessions || [ ]
2022-08-24 01:10:06 +02:00
2023-02-05 23:52:17 +01:00
const syncResults = [ ]
for ( const sessionJson of sessions ) {
Logger . info ( ` [PlaybackSessionManager] Syncing local session " ${ sessionJson . displayTitle } " ( ${ sessionJson . id } ) ` )
2023-07-16 22:05:51 +02:00
const result = await this . syncLocalSession ( user , sessionJson , deviceInfo )
2023-02-05 23:52:17 +01:00
syncResults . push ( result )
2022-05-12 00:07:41 +02:00
}
2022-04-10 00:56:51 +02:00
2023-02-05 23:52:17 +01:00
res . json ( {
results : syncResults
2023-01-15 22:00:18 +01:00
} )
2023-02-05 23:52:17 +01:00
}
2023-01-15 22:00:18 +01:00
2024-08-11 22:15:34 +02:00
/ * *
*
* @ param { import ( '../models/User' ) } user
* @ param { * } sessionJson
* @ param { * } deviceInfo
* @ returns
* /
2023-07-16 22:05:51 +02:00
async syncLocalSession ( user , sessionJson , deviceInfo ) {
2024-10-26 00:27:50 +02:00
// TODO: Combine libraryItem query with library query
2023-09-04 23:33:55 +02:00
const libraryItem = await Database . libraryItemModel . getOldById ( sessionJson . libraryItemId )
2024-06-23 18:01:25 +02:00
const episode = sessionJson . episodeId && libraryItem && libraryItem . isPodcast ? libraryItem . media . getEpisode ( sessionJson . episodeId ) : null
2023-02-05 23:52:17 +01:00
if ( ! libraryItem || ( libraryItem . isPodcast && ! episode ) ) {
Logger . error ( ` [PlaybackSessionManager] syncLocalSession: Media item not found for session " ${ sessionJson . displayTitle } " ( ${ sessionJson . id } ) ` )
return {
id : sessionJson . id ,
success : false ,
error : 'Media item not found'
}
}
2022-08-24 01:10:06 +02:00
2024-10-26 00:27:50 +02:00
const library = await Database . libraryModel . findByPk ( libraryItem . libraryId )
if ( ! library ) {
Logger . error ( ` [PlaybackSessionManager] syncLocalSession: Library not found for session " ${ sessionJson . displayTitle } " ( ${ sessionJson . id } ) ` )
return {
id : sessionJson . id ,
success : false ,
error : 'Library not found'
}
}
2023-07-17 20:58:19 +02:00
sessionJson . userId = user . id
sessionJson . serverVersion = serverVersion
2023-07-16 22:05:51 +02:00
// TODO: Temp update local playback session id to uuidv4 & library item/book/episode ids
if ( sessionJson . id ? . startsWith ( 'play_local_' ) ) {
if ( ! this . oldPlaybackSessionMap [ sessionJson . id ] ) {
const newSessionId = uuidv4 ( )
this . oldPlaybackSessionMap [ sessionJson . id ] = newSessionId
sessionJson . id = newSessionId
} else {
sessionJson . id = this . oldPlaybackSessionMap [ sessionJson . id ]
}
}
if ( sessionJson . libraryItemId !== libraryItem . id ) {
Logger . info ( ` [PlaybackSessionManager] Mapped old libraryItemId " ${ sessionJson . libraryItemId } " to ${ libraryItem . id } ` )
sessionJson . libraryItemId = libraryItem . id
sessionJson . bookId = episode ? null : libraryItem . media . id
}
if ( ! sessionJson . bookId && ! episode ) {
sessionJson . bookId = libraryItem . media . id
}
if ( episode && sessionJson . episodeId !== episode . id ) {
Logger . info ( ` [PlaybackSessionManager] Mapped old episodeId " ${ sessionJson . episodeId } " to ${ episode . id } ` )
sessionJson . episodeId = episode . id
}
if ( sessionJson . libraryId !== libraryItem . libraryId ) {
sessionJson . libraryId = libraryItem . libraryId
}
2023-07-05 01:14:44 +02:00
let session = await Database . getPlaybackSession ( sessionJson . id )
2022-04-10 00:56:51 +02:00
if ( ! session ) {
// New session from local
session = new PlaybackSession ( sessionJson )
2023-07-16 22:05:51 +02:00
session . deviceInfo = deviceInfo
2024-09-12 20:05:08 +02:00
session . setDuration ( libraryItem , sessionJson . episodeId )
2023-02-05 23:52:17 +01:00
Logger . debug ( ` [PlaybackSessionManager] Inserting new session for " ${ session . displayTitle } " ( ${ session . id } ) ` )
2023-07-05 01:14:44 +02:00
await Database . createPlaybackSession ( session )
2022-04-10 00:56:51 +02:00
} else {
2022-08-27 02:28:41 +02:00
session . currentTime = sessionJson . currentTime
2022-04-10 00:56:51 +02:00
session . timeListening = sessionJson . timeListening
session . updatedAt = sessionJson . updatedAt
2024-03-30 17:40:35 +01:00
let jsDate = new Date ( sessionJson . updatedAt )
if ( isNaN ( jsDate ) ) {
jsDate = new Date ( )
}
session . date = date . format ( jsDate , 'YYYY-MM-DD' )
session . dayOfWeek = date . format ( jsDate , 'dddd' )
2023-02-05 23:52:17 +01:00
Logger . debug ( ` [PlaybackSessionManager] Updated session for " ${ session . displayTitle } " ( ${ session . id } ) ` )
2023-07-05 01:14:44 +02:00
await Database . updatePlaybackSession ( session )
2022-04-10 00:56:51 +02:00
}
2023-02-05 23:52:17 +01:00
const result = {
id : session . id ,
success : true ,
progressSynced : false
}
2022-04-10 00:56:51 +02:00
2024-08-11 22:15:34 +02:00
const mediaItemId = session . episodeId || libraryItem . media . id
let userProgressForItem = user . getMediaProgress ( mediaItemId )
2023-02-05 23:52:17 +01:00
if ( userProgressForItem ) {
2024-08-11 22:15:34 +02:00
if ( userProgressForItem . updatedAt . valueOf ( ) > session . updatedAt ) {
2023-02-05 23:52:17 +01:00
Logger . debug ( ` [PlaybackSessionManager] Not updating progress for " ${ session . displayTitle } " because it has been updated more recently ` )
} else {
Logger . debug ( ` [PlaybackSessionManager] Updating progress for " ${ session . displayTitle } " with current time ${ session . currentTime } (previously ${ userProgressForItem . currentTime } ) ` )
2024-08-11 22:15:34 +02:00
const updateResponse = await user . createUpdateMediaProgressFromPayload ( {
libraryItemId : libraryItem . id ,
episodeId : session . episodeId ,
2024-10-26 00:27:50 +02:00
... session . mediaProgressObject ,
markAsFinishedPercentComplete : library . librarySettings . markAsFinishedPercentComplete ,
markAsFinishedTimeRemaining : library . librarySettings . markAsFinishedTimeRemaining
2024-08-11 22:15:34 +02:00
} )
result . progressSynced = ! ! updateResponse . mediaProgress
if ( result . progressSynced ) {
userProgressForItem = updateResponse . mediaProgress
}
2023-02-05 23:52:17 +01:00
}
} else {
Logger . debug ( ` [PlaybackSessionManager] Creating new media progress for media item " ${ session . displayTitle } " ` )
2024-08-11 22:15:34 +02:00
const updateResponse = await user . createUpdateMediaProgressFromPayload ( {
libraryItemId : libraryItem . id ,
episodeId : session . episodeId ,
2024-10-26 00:27:50 +02:00
... session . mediaProgressObject ,
markAsFinishedPercentComplete : library . librarySettings . markAsFinishedPercentComplete ,
markAsFinishedTimeRemaining : library . librarySettings . markAsFinishedTimeRemaining
2024-08-11 22:15:34 +02:00
} )
result . progressSynced = ! ! updateResponse . mediaProgress
if ( result . progressSynced ) {
userProgressForItem = updateResponse . mediaProgress
}
2022-04-10 00:56:51 +02:00
}
2023-02-05 23:52:17 +01:00
// Update user and emit socket event
if ( result . progressSynced ) {
2024-08-11 22:15:34 +02:00
SocketAuthority . clientEmitter ( user . id , 'user_item_progress_updated' , {
id : userProgressForItem . id ,
sessionId : session . id ,
deviceDescription : session . deviceDescription ,
data : userProgressForItem . getOldMediaProgress ( )
} )
2022-04-10 00:56:51 +02:00
}
2022-08-24 01:10:06 +02:00
2023-02-05 23:52:17 +01:00
return result
}
2022-08-24 01:10:06 +02:00
2024-08-11 22:15:34 +02:00
/ * *
*
* @ param { import ( '../controllers/SessionController' ) . RequestWithUser } req
* @ param { * } res
* /
2023-07-16 22:05:51 +02:00
async syncLocalSessionRequest ( req , res ) {
2024-06-30 23:36:00 +02:00
const deviceInfo = await this . getDeviceInfo ( req , req . body ? . deviceInfo )
2023-07-16 22:05:51 +02:00
const sessionJson = req . body
2024-08-11 23:07:29 +02:00
const result = await this . syncLocalSession ( req . user , sessionJson , deviceInfo )
2023-02-05 23:52:17 +01:00
if ( result . error ) {
res . status ( 500 ) . send ( result . error )
} else {
res . sendStatus ( 200 )
}
2022-04-10 00:56:51 +02:00
}
2024-08-11 22:15:34 +02:00
/ * *
*
* @ param { import ( '../models/User' ) } user
* @ param { * } session
* @ param { * } syncData
* @ param { import ( 'express' ) . Response } res
* /
2022-03-18 01:10:47 +01:00
async closeSessionRequest ( user , session , syncData , res ) {
await this . closeSession ( user , session , syncData )
res . sendStatus ( 200 )
}
2022-03-17 01:15:25 +01:00
2024-06-23 18:01:25 +02:00
/ * *
*
2024-08-11 22:15:34 +02:00
* @ param { import ( '../models/User' ) } user
2024-06-23 18:01:25 +02:00
* @ param { DeviceInfo } deviceInfo
* @ param { import ( '../objects/LibraryItem' ) } libraryItem
* @ param { string | null } episodeId
* @ param { { forceDirectPlay ? : boolean , forceTranscode ? : boolean , mediaPlayer : string , supportedMimeTypes ? : string [ ] } } options
* @ returns { Promise < PlaybackSession > }
* /
2022-05-27 02:09:46 +02:00
async startSession ( user , deviceInfo , libraryItem , episodeId , options ) {
2023-04-09 01:01:24 +02:00
// Close any sessions already open for user and device
2024-06-23 18:01:25 +02:00
const userSessions = this . sessions . filter ( ( playbackSession ) => playbackSession . userId === user . id && playbackSession . deviceId === deviceInfo . id )
2022-04-23 23:18:34 +02:00
for ( const session of userSessions ) {
2023-02-04 20:23:13 +01:00
Logger . info ( ` [PlaybackSessionManager] startSession: Closing open session " ${ session . displayTitle } " for user " ${ user . username } " (Device: ${ session . deviceDescription } ) ` )
2022-04-23 23:18:34 +02:00
await this . closeSession ( user , session , null )
}
2022-12-13 00:18:56 +01:00
const shouldDirectPlay = options . forceDirectPlay || ( ! options . forceTranscode && libraryItem . media . checkCanDirectPlay ( options , episodeId ) )
const mediaPlayer = options . mediaPlayer || 'unknown'
2022-03-18 01:10:47 +01:00
2024-08-11 22:15:34 +02:00
const mediaItemId = episodeId || libraryItem . media . id
const userProgress = user . getMediaProgress ( mediaItemId )
2022-12-13 00:18:56 +01:00
let userStartTime = 0
if ( userProgress ) {
if ( userProgress . isFinished ) {
Logger . info ( ` [PlaybackSessionManager] Starting session for user " ${ user . username } " and resetting progress for finished item " ${ libraryItem . media . metadata . title } " ` )
// Keep userStartTime as 0 so the client restarts the media
} else {
userStartTime = Number . parseFloat ( userProgress . currentTime ) || 0
}
}
2022-03-16 01:28:54 +01:00
const newPlaybackSession = new PlaybackSession ( )
2024-06-23 18:01:25 +02:00
newPlaybackSession . setData ( libraryItem , user . id , mediaPlayer , deviceInfo , userStartTime , episodeId )
2022-03-18 01:10:47 +01:00
2024-09-04 00:04:58 +02:00
let audioTracks = [ ]
if ( shouldDirectPlay ) {
Logger . debug ( ` [PlaybackSessionManager] " ${ user . username } " starting direct play session for item " ${ libraryItem . id } " with id ${ newPlaybackSession . id } (Device: ${ newPlaybackSession . deviceDescription } ) ` )
audioTracks = libraryItem . getDirectPlayTracklist ( episodeId )
newPlaybackSession . playMethod = PlayMethod . DIRECTPLAY
2022-03-18 01:10:47 +01:00
} else {
2024-09-04 00:04:58 +02:00
Logger . debug ( ` [PlaybackSessionManager] " ${ user . username } " starting stream session for item " ${ libraryItem . id } " (Device: ${ newPlaybackSession . deviceDescription } ) ` )
const stream = new Stream ( newPlaybackSession . id , this . StreamsPath , user , libraryItem , episodeId , userStartTime )
await stream . generatePlaylist ( )
stream . start ( ) // Start transcode
audioTracks = [ stream . getAudioTrack ( ) ]
newPlaybackSession . stream = stream
newPlaybackSession . playMethod = PlayMethod . TRANSCODE
stream . on ( 'closed' , ( ) => {
Logger . debug ( ` [PlaybackSessionManager] Stream closed for session " ${ newPlaybackSession . id } " (Device: ${ newPlaybackSession . deviceDescription } ) ` )
newPlaybackSession . stream = null
} )
2022-03-18 01:10:47 +01:00
}
2024-09-04 00:04:58 +02:00
newPlaybackSession . audioTracks = audioTracks
2022-03-18 01:10:47 +01:00
2022-03-16 01:28:54 +01:00
this . sessions . push ( newPlaybackSession )
2023-08-12 23:11:58 +02:00
SocketAuthority . adminEmitter ( 'user_stream_update' , user . toJSONForPublic ( this . sessions ) )
2022-03-18 01:10:47 +01:00
2022-03-16 01:28:54 +01:00
return newPlaybackSession
2022-03-16 00:57:15 +01:00
}
2022-03-18 01:10:47 +01:00
2024-08-11 22:15:34 +02:00
/ * *
*
* @ param { import ( '../models/User' ) } user
* @ param { * } session
* @ param { * } syncData
* @ returns
* /
2022-03-18 01:10:47 +01:00
async syncSession ( user , session , syncData ) {
2024-10-26 00:27:50 +02:00
// TODO: Combine libraryItem query with library query
2023-08-20 20:34:03 +02:00
const libraryItem = await Database . libraryItemModel . getOldById ( session . libraryItemId )
2022-03-18 21:31:46 +01:00
if ( ! libraryItem ) {
2022-03-26 23:41:26 +01:00
Logger . error ( ` [PlaybackSessionManager] syncSession Library Item not found " ${ session . libraryItemId } " ` )
2022-03-26 17:59:34 +01:00
return null
2022-03-18 21:31:46 +01:00
}
2024-10-26 00:27:50 +02:00
const library = await Database . libraryModel . findByPk ( libraryItem . libraryId )
if ( ! library ) {
Logger . error ( ` [PlaybackSessionManager] syncSession Library not found " ${ libraryItem . libraryId } " ` )
return null
}
2022-03-18 01:10:47 +01:00
session . currentTime = syncData . currentTime
session . addListeningTime ( syncData . timeListened )
2023-02-04 20:23:13 +01:00
Logger . debug ( ` [PlaybackSessionManager] syncSession " ${ session . id } " (Device: ${ session . deviceDescription } ) | Total Time Listened: ${ session . timeListening } ` )
2022-03-18 01:10:47 +01:00
2024-08-11 22:15:34 +02:00
const updateResponse = await user . createUpdateMediaProgressFromPayload ( {
libraryItemId : libraryItem . id ,
episodeId : session . episodeId ,
2024-10-22 00:48:02 +02:00
// duration no longer required (v2.15.1) but used if available
2024-10-29 21:41:31 +01:00
duration : syncData . duration || session . duration || 0 ,
2022-03-18 01:10:47 +01:00
currentTime : syncData . currentTime ,
2024-10-26 00:27:50 +02:00
progress : session . progress ,
markAsFinishedTimeRemaining : library . librarySettings . markAsFinishedTimeRemaining ,
markAsFinishedPercentComplete : library . librarySettings . markAsFinishedPercentComplete
2024-08-11 22:15:34 +02:00
} )
if ( updateResponse . mediaProgress ) {
2022-11-24 22:53:58 +01:00
SocketAuthority . clientEmitter ( user . id , 'user_item_progress_updated' , {
2024-08-11 22:15:34 +02:00
id : updateResponse . mediaProgress . id ,
2023-05-28 00:21:43 +02:00
sessionId : session . id ,
deviceDescription : session . deviceDescription ,
2024-08-11 22:15:34 +02:00
data : updateResponse . mediaProgress . getOldMediaProgress ( )
2022-03-18 01:10:47 +01:00
} )
}
this . saveSession ( session )
2022-03-26 17:59:34 +01:00
return {
libraryItem
}
2022-03-18 01:10:47 +01:00
}
2024-08-11 22:15:34 +02:00
/ * *
*
* @ param { import ( '../models/User' ) } user
* @ param { * } session
* @ param { * } syncData
* @ returns
* /
2022-03-18 01:10:47 +01:00
async closeSession ( user , session , syncData = null ) {
if ( syncData ) {
await this . syncSession ( user , session , syncData )
} else {
await this . saveSession ( session )
}
Logger . debug ( ` [PlaybackSessionManager] closeSession " ${ session . id } " ` )
2023-08-12 23:11:58 +02:00
SocketAuthority . adminEmitter ( 'user_stream_update' , user . toJSONForPublic ( this . sessions ) )
2023-04-09 01:01:24 +02:00
SocketAuthority . clientEmitter ( session . userId , 'user_session_closed' , session . id )
2022-03-18 01:10:47 +01:00
return this . removeSession ( session . id )
}
saveSession ( session ) {
2022-03-18 21:31:46 +01:00
if ( ! session . timeListening ) return // Do not save a session with no listening time
2022-03-18 01:10:47 +01:00
if ( session . lastSave ) {
2023-07-05 01:14:44 +02:00
return Database . updatePlaybackSession ( session )
2022-03-18 01:10:47 +01:00
} else {
session . lastSave = Date . now ( )
2023-07-05 01:14:44 +02:00
return Database . createPlaybackSession ( session )
2022-03-18 01:10:47 +01:00
}
}
2024-07-04 19:00:54 +02:00
/ * *
*
* @ param { string } sessionId
* /
2022-03-18 01:10:47 +01:00
async removeSession ( sessionId ) {
2024-06-23 18:01:25 +02:00
const session = this . sessions . find ( ( s ) => s . id === sessionId )
2022-03-18 01:10:47 +01:00
if ( ! session ) return
if ( session . stream ) {
await session . stream . close ( )
}
2024-06-23 18:01:25 +02:00
this . sessions = this . sessions . filter ( ( s ) => s . id !== sessionId )
2022-03-18 01:10:47 +01:00
Logger . debug ( ` [PlaybackSessionManager] Removed session " ${ sessionId } " ` )
}
2022-04-16 19:37:10 +02:00
2023-10-12 00:05:56 +02:00
/ * *
* Remove all stream folders in ` /metadata/streams `
* /
2022-04-16 19:37:10 +02:00
async removeOrphanStreams ( ) {
await fs . ensureDir ( this . StreamsPath )
try {
2022-12-13 00:52:20 +01:00
const streamsInPath = await fs . readdir ( this . StreamsPath )
2023-10-12 00:05:56 +02:00
for ( const streamId of streamsInPath ) {
2024-06-23 18:01:25 +02:00
if ( /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/ . test ( streamId ) ) {
// Ensure is uuidv4
const session = this . sessions . find ( ( se ) => se . id === streamId )
2022-04-16 19:37:10 +02:00
if ( ! session ) {
2022-12-13 00:52:20 +01:00
const streamPath = Path . join ( this . StreamsPath , streamId )
2022-04-16 19:37:10 +02:00
Logger . debug ( ` [PlaybackSessionManager] Removing orphan stream " ${ streamPath } " ` )
await fs . remove ( streamPath )
}
}
}
} catch ( error ) {
Logger . error ( ` [PlaybackSessionManager] cleanOrphanStreams failed ` , error )
}
}
2024-07-04 19:00:54 +02:00
/ * *
* Close all open sessions that have not been updated in the last 36 hours
* /
async closeStaleOpenSessions ( ) {
const updatedAtTimeCutoff = Date . now ( ) - 1000 * 60 * 60 * 36
const staleSessions = this . sessions . filter ( ( session ) => session . updatedAt < updatedAtTimeCutoff )
for ( const session of staleSessions ) {
const sessionLastUpdate = new Date ( session . updatedAt )
Logger . info ( ` [PlaybackSessionManager] Closing stale session " ${ session . displayTitle } " ( ${ session . id } ) last updated at ${ sessionLastUpdate } ` )
await this . removeSession ( session . id )
}
}
2022-03-16 00:57:15 +01:00
}
2022-08-27 02:28:41 +02:00
module . exports = PlaybackSessionManager