[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: Implement queued scrobbles display

* Show number of queued scrobbles for clients in ui
* Implement live updates to queue count

FoxxMD (Oct 19, 2023, 1:40 PM EDT) fe9f4977 3aab6a20

+32 -9
+4 -1
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 553 553 // processing play may have changed index while we were scrobbling 554 554 const pIndex = this.queuedScrobbles.findIndex(x => x.id === currQueuedPlay.id); 555 555 if (pIndex !== -1) { 556 + this.emitEvent('scrobbleDequeued', {queuedScrobble: currQueuedPlay}) 556 557 this.queuedScrobbles.splice(pIndex, 1); 557 558 } 558 559 } ··· 668 669 queueScrobble = (data: PlayObject | PlayObject[], source: string) => { 669 670 const plays = Array.isArray(data) ? data : [data]; 670 671 for(const p of plays) { 671 - this.queuedScrobbles.push({id: nanoid(), source, play: p}); 672 + const queuedPlay = {id: nanoid(), source, play: p} 673 + this.emitEvent('scrobbleQueued', {queuedPlay: queuedPlay}); 674 + this.queuedScrobbles.push(queuedPlay); 672 675 } 673 676 this.queuedScrobbles.sort((a, b) => sortByOldestPlayDate(a.play, b.play)); 674 677 }
+3 -2
src/backend/server/api.ts
··· 227 227 status: '', 228 228 type, 229 229 display: capitalize(type), 230 - tracksDiscovered: tracksScrobbled, 230 + scrobbled: tracksScrobbled, 231 231 name, 232 232 hasAuth: requiresAuth, 233 233 hasAuthInteraction: requiresAuthInteraction, 234 234 authed, 235 235 initialized, 236 - deadLetterScrobbles: x.deadLetterScrobbles.length 236 + deadLetterScrobbles: x.deadLetterScrobbles.length, 237 + queued: x.queuedScrobbles.length 237 238 }; 238 239 if (!initialized) { 239 240 base.status = 'Not Initialized';
+4 -2
src/client/components/statusCard/ClientStatusCard.tsx
··· 31 31 type, 32 32 display, 33 33 status, 34 - tracksDiscovered = 0, 34 + scrobbled: scrobbledCount = 0, 35 + queued = 0, 35 36 deadLetterScrobbles = 0 36 37 } = {} 37 38 } = props; ··· 52 53 53 54 // TODO links 54 55 body = (<Fragment> 55 - <div>{scrobbled}: {tracksDiscovered}</div> 56 + <div>{scrobbled}: {scrobbledCount}</div> 57 + <div>Queued Scrobbles: {queued}</div> 56 58 <div><Link to={`/dead?type=${type}&name=${name}`}>Failed Scrobbles</Link>: {deadLetterScrobbles}</div> 57 59 {hasAuth ? <a target="_blank" href={`/api/client/auth?name=${name}&type=${type}`}>(Re)authenticate or initialize</a> : null} 58 60 </Fragment>);
+19 -3
src/client/status/ducks.ts
··· 47 47 (action) => sourceUpdate.match(action) && action.payload.event === 'discovered', 48 48 (state, action) => { 49 49 if(state.entities[action.payload.id] !== undefined) { 50 - state.entities[action.payload.id].tracksDiscovered = state.entities[action.payload.id].tracksDiscovered + 1; 50 + state.entities[action.payload.id].tracksDiscovered++; 51 51 } 52 52 } 53 53 ).addMatcher( ··· 94 94 (action) => clientUpdate.match(action) && action.payload.event === 'scrobble', 95 95 (state, action) => { 96 96 if(state.entities[action.payload.id] !== undefined) { 97 - state.entities[action.payload.id].tracksDiscovered = state.entities[action.payload.id].tracksDiscovered + 1; 97 + state.entities[action.payload.id].scrobbled++; 98 98 } 99 99 } 100 100 ) ··· 102 102 (action) => clientUpdate.match(action) && action.payload.event === 'deadLetter', 103 103 (state, action) => { 104 104 if(state.entities[action.payload.id] !== undefined) { 105 - state.entities[action.payload.id].deadLetterScrobbles = state.entities[action.payload.id].deadLetterScrobbles + 1; 105 + state.entities[action.payload.id].deadLetterScrobbles++; 106 106 } 107 107 } 108 108 ) ··· 111 111 (state, action) => { 112 112 if(state.entities[action.payload.id] !== undefined) { 113 113 state.entities[action.payload.id].status = action.payload.data.status; 114 + } 115 + } 116 + ) 117 + .addMatcher( 118 + (action) => clientUpdate.match(action) && action.payload.event === 'scrobbleQueued', 119 + (state, action) => { 120 + if(state.entities[action.payload.id] !== undefined) { 121 + state.entities[action.payload.id].queued++; 122 + } 123 + } 124 + ) 125 + .addMatcher( 126 + (action) => clientUpdate.match(action) && action.payload.event === 'scrobbleDequeued', 127 + (state, action) => { 128 + if(state.entities[action.payload.id] !== undefined) { 129 + state.entities[action.payload.id].queued--; 114 130 } 115 131 } 116 132 )
+2 -1
src/core/Atomic.ts
··· 19 19 status: string; 20 20 type: "maloja" | "lastfm" | "listenbrainz"; 21 21 display: string; 22 - tracksDiscovered: number; 22 + scrobbled: number; 23 23 deadLetterScrobbles: number 24 + queued: number 24 25 name: string; 25 26 hasAuth: boolean; 26 27 hasAuthInteraction: boolean;