@@ -7,17 +7,22 @@ import { buildDrawerPlayer, buildTrackTable, buildAlbumBuyButton } from './build
77import { PlayerCommands , registerPlayerShortcuts } from '../../keyboardShortcuts' ;
88import { drawOverlay , generateAudioFeatures } from '../../audioFeatures' ;
99import { volumeIcon , mutedVolumeIcon } from './icons' ;
10- import { replaceChildren } from '../dom' ;
10+ import { element , replaceChildren } from '../dom' ;
1111import { inDrawer , allInDrawer , setText , setStyle } from './query' ;
1212import { streamUrlOf , findPlayableTrackAfter , findPlayableTrackBefore } from './trackSelection' ;
1313import { applyTrackState , markRowPlayed } from './trackState' ;
14+ import { buildAlbumNav , updateAlbumNav } from './albumNav' ;
1415import {
1516 selectAlbum ,
1617 hasNextAlbum ,
1718 hasPreviousAlbum ,
1819 nextAlbum ,
1920 previousAlbum ,
20- albumArtUrlFor
21+ albumArtUrlFor ,
22+ canLoadMoreAlbums ,
23+ loadMoreAlbums ,
24+ revealAlbum ,
25+ showAlbumControls
2126} from '../../discography' ;
2227
2328const log = new Logger ( ) ;
@@ -31,6 +36,9 @@ let drawerPlayerCreated = false;
3136let previousVolume = 1.0 ;
3237let configPort : chrome . runtime . Port | null = null ;
3338let waveformEnabled = true ;
39+ let requestedAlbumId : string | null = null ;
40+ let fetchCaching = false ;
41+ let albumNavLoading = false ;
3442
3543const ALBUM_LOAD_SETTLE_MS = 300 ;
3644
@@ -220,6 +228,8 @@ export async function loadAlbumIntoDrawer(
220228 port ?: chrome . runtime . Port
221229) : Promise < void > {
222230 log . info ( `Loading album ${ albumId } (${ albumType } ) into drawer` ) ;
231+ requestedAlbumId = albumId ;
232+ fetchCaching = enableFetchCaching ;
223233
224234 try {
225235 const tralbumDetails = await getTralbumDetails (
@@ -229,6 +239,11 @@ export async function loadAlbumIntoDrawer(
229239 createFetchFunction ( enableFetchCaching )
230240 ) ;
231241
242+ if ( requestedAlbumId !== albumId ) {
243+ log . debug ( `Album ${ albumId } arrived after the drawer moved on` ) ;
244+ return ;
245+ }
246+
232247 currentAlbumData = tralbumDetails ;
233248 selectAlbum ( albumId ) ;
234249
@@ -243,14 +258,14 @@ export async function loadAlbumIntoDrawer(
243258 if ( drawerPlayerCreated ) {
244259 replaceChildren (
245260 elements . tracklistContainer ,
246- buildAlbumBuyButton ( tralbumDetails ) ,
261+ albumToolbar ( buildAlbumBuyButton ( tralbumDetails ) ) ,
247262 buildTrackTable ( tralbumDetails )
248263 ) ;
249264 } else {
250265 const parts = buildDrawerPlayer ( tralbumDetails ) ;
251266
252267 mountDrawerPlayer ( elements , parts ) ;
253- replaceChildren ( elements . tracklistContainer , parts . albumBuyButton , parts . tracklistElement ) ;
268+ replaceChildren ( elements . tracklistContainer , albumToolbar ( parts . albumBuyButton ) , parts . tracklistElement ) ;
254269 drawerPlayerCreated = true ;
255270
256271 startPlayerOnce ( ) ;
@@ -268,6 +283,52 @@ export async function loadAlbumIntoDrawer(
268283 }
269284}
270285
286+ export function getLoadedAlbumId ( ) : string | null {
287+ return requestedAlbumId ;
288+ }
289+
290+ function hasLaterAlbum ( ) : boolean {
291+ return hasNextAlbum ( ) || canLoadMoreAlbums ( ) ;
292+ }
293+
294+ function albumToolbar ( buyButton : HTMLElement | null ) : HTMLElement | null {
295+ if ( ! showAlbumControls ( ) ) return buyButton ;
296+
297+ const nav = buildAlbumNav (
298+ ( ) => void skipAlbum ( loadPreviousAlbum ) ,
299+ ( ) => void skipAlbum ( loadNextAlbum )
300+ ) ;
301+ updateAlbumNav ( nav , { hasPrevious : hasPreviousAlbum ( ) , hasNext : hasLaterAlbum ( ) , loading : albumNavLoading } ) ;
302+
303+ return element ( 'div' , { className : 'bes-album-toolbar' , children : [ nav , buyButton ] } ) ;
304+ }
305+
306+ function refreshAlbumNav ( ) : void {
307+ updateAlbumNav ( inDrawer ( '.bes-album-nav' ) , {
308+ hasPrevious : hasPreviousAlbum ( ) ,
309+ hasNext : hasLaterAlbum ( ) ,
310+ loading : albumNavLoading
311+ } ) ;
312+ }
313+
314+ async function skipAlbum ( load : ( ) => Promise < boolean > ) : Promise < void > {
315+ if ( albumNavLoading ) return ;
316+
317+ const keepPlaying = Boolean ( audioElement && ! audioElement . paused ) ;
318+ albumNavLoading = true ;
319+ refreshAlbumNav ( ) ;
320+
321+ try {
322+ if ( ( await load ( ) ) && keepPlaying ) resumePlayback ( ) ;
323+ } catch ( error ) {
324+ log . error ( `Failed to skip album: ${ error } ` ) ;
325+ } finally {
326+ albumNavLoading = false ;
327+ refreshAlbumNav ( ) ;
328+ if ( currentAlbumData ?. tracks ) updatePrevNextButtons ( currentTrackIndex , currentAlbumData . tracks . length ) ;
329+ }
330+ }
331+
271332function currentTrack ( ) : TralbumTrack | undefined {
272333 return currentAlbumData ?. tracks ?. [ currentTrackIndex ] ;
273334}
@@ -334,7 +395,7 @@ function updatePrevNextButtons(index: number, totalTracks: number): void {
334395 const nextButton = inDrawer ( '.bes-transport-next' ) ;
335396
336397 const hasEarlierTrackOrAlbum = index > 0 || hasPreviousAlbum ( ) ;
337- const hasLaterTrackOrAlbum = index < totalTracks - 1 || hasNextAlbum ( ) ;
398+ const hasLaterTrackOrAlbum = index < totalTracks - 1 || hasLaterAlbum ( ) ;
338399
339400 prevButton ?. classList . toggle ( 'bes-hidden' , ! hasEarlierTrackOrAlbum ) ;
340401 nextButton ?. classList . toggle ( 'bes-hidden' , ! hasLaterTrackOrAlbum ) ;
@@ -366,7 +427,7 @@ interface TrackStep {
366427const forward : TrackStep = {
367428 name : 'next' ,
368429 nextWithinAlbum : findPlayableTrackAfter ,
369- hasAdjacentAlbum : hasNextAlbum ,
430+ hasAdjacentAlbum : hasLaterAlbum ,
370431 loadAdjacentAlbum : loadNextAlbum ,
371432 entryTrack : tracks => findPlayableTrackAfter ( tracks , - 1 )
372433} ;
@@ -395,7 +456,7 @@ async function step(direction: TrackStep, keepPlaying: boolean): Promise<void> {
395456 }
396457
397458 log . info ( `No playable track ${ direction . name } : loading ${ direction . name } album in discography` ) ;
398- await direction . loadAdjacentAlbum ( ) ;
459+ if ( ! ( await direction . loadAdjacentAlbum ( ) ) ) return ;
399460
400461 setTimeout ( ( ) => {
401462 const entry = direction . entryTrack ( currentAlbumData ?. tracks ) ;
@@ -600,25 +661,32 @@ function adjustVolumeBy(delta: number): void {
600661 updateMuteButton ( audioElement . volume === 0 ) ;
601662}
602663
603- export async function loadNextAlbum ( enableFetchCaching : boolean = false ) : Promise < boolean > {
664+ export async function loadNextAlbum ( enableFetchCaching : boolean = fetchCaching ) : Promise < boolean > {
665+ if ( ! hasNextAlbum ( ) && canLoadMoreAlbums ( ) ) {
666+ log . info ( 'Reached the end of the loaded albums, loading more' ) ;
667+ await loadMoreAlbums ( ) ;
668+ }
669+
604670 const item = nextAlbum ( ) ;
605671 if ( ! item ) {
606672 log . info ( 'No next album available' ) ;
607673 return false ;
608674 }
609675
610676 await loadAlbumIntoDrawer ( item . id , item . type , enableFetchCaching ) ;
677+ revealAlbum ( item ) ;
611678 return true ;
612679}
613680
614- export async function loadPreviousAlbum ( enableFetchCaching : boolean = false ) : Promise < boolean > {
681+ export async function loadPreviousAlbum ( enableFetchCaching : boolean = fetchCaching ) : Promise < boolean > {
615682 const item = previousAlbum ( ) ;
616683 if ( ! item ) {
617684 log . info ( 'No previous album available' ) ;
618685 return false ;
619686 }
620687
621688 await loadAlbumIntoDrawer ( item . id , item . type , enableFetchCaching ) ;
689+ revealAlbum ( item ) ;
622690 return true ;
623691}
624692
0 commit comments