@@ -1262,10 +1262,13 @@ function addSongToPlayQueue( fileObject, content ) {
12621262 retrieveMetadata ( ) ;
12631263 }
12641264
1265- if ( queueLength ( ) == 1 && ! isPlaying ( ) )
1265+ if ( queueLength ( ) == 1 && ! isPlaying ( ) ) {
1266+ // if the added song is the only one in the queue and no audio is currently playing, load it into the primary media element
12661267 loadSong ( 0 ) . then ( ( ) => resolve ( 1 ) ) ;
1268+ }
12671269 else {
1268- if ( queueIndex > queueLength ( ) - 3 )
1270+ // if the queue pointer was at the last song, load the newly added song into the secondary media element
1271+ if ( queueIndex == queueLength ( ) - 2 )
12691272 loadSong ( NEXT_TRACK ) ;
12701273 resolve ( 1 ) ;
12711274 }
@@ -1349,14 +1352,13 @@ function clearPlayQueue() {
13491352 revokeBlobURL ( elPlayqueue . removeChild ( elPlayqueue . firstChild ) ) ;
13501353
13511354 if ( ! isPlaying ( ) ) {
1352- queueIndex = 0 ;
1355+ setQueueIndex ( 0 ) ;
13531356 clearAudioElement ( currAudio ) ;
13541357 }
13551358 else
1356- queueIndex = - 1 ;
1359+ setQueueIndex ( - 1 ) ;
13571360
13581361 clearAudioElement ( nextAudio ) ;
1359- updatePlaylistUI ( ) ;
13601362}
13611363
13621364/**
@@ -1900,11 +1902,11 @@ function keyboardControls( event ) {
19001902 } ) ;
19011903 const current = getIndex ( elPlayqueue . querySelector ( '.current' ) ) ;
19021904 if ( current !== undefined )
1903- queueIndex = current ; // update queueIndex if current song hasn't been deleted
1905+ setQueueIndex ( current ) ; // keep the pointer at the current track, if it hasn't been deleted
19041906 else if ( queueIndex > queueLength ( ) - 1 )
1905- queueIndex = queueLength ( ) - 1 ;
1907+ setQueueIndex ( queueLength ( ) - 1 ) ;
19061908 else
1907- queueIndex -- ;
1909+ setQueueIndex ( queueIndex - 1 ) ;
19081910 if ( queueLength ( ) )
19091911 loadSong ( NEXT_TRACK ) ;
19101912 else {
@@ -2572,20 +2574,20 @@ async function loadSavedPlaylists( keyName ) {
25722574}
25732575
25742576/**
2575- * Load a song from the queue into the currently active audio element
2577+ * Load a song from the queue into one of the media elements
25762578 *
2577- * @param {number } index to the desired play queue element
2579+ * @param {number } index to the desired queue element to load; -1 to load the track after the current queueIndex
25782580 * @param {boolean } `true` to start playing
2579- * @returns {Promise } resolves to a boolean indicating success or failure (invalid queue index)
2581+ * @returns {Promise } resolves to a boolean indicating success or failure
25802582 */
25812583async function loadSong ( n , playIt ) {
2582- const isCurrent = n !== NEXT_TRACK ,
2583- index = isCurrent ? n : ( ( queueIndex < queueLength ( ) - 1 ) ? queueIndex + 1 : 0 ) ,
2584- mediaEl = isCurrent ? currAudio : nextAudio ,
2584+ const isCurrent = n !== NEXT_TRACK || playIt , // if requested to play, always use the primary media element
2585+ index = n !== NEXT_TRACK ? n : ( ( queueIndex < queueLength ( ) - 1 ) ? queueIndex + 1 : 0 ) ,
2586+ mediaEl = isCurrent ? currAudio : nextAudio , // next track is (usually) loaded in the secondary element
25852587 audioEl = audioElement [ mediaEl ] ,
25862588 song = elPlayqueue . children [ index ] ;
25872589
2588- debugLog ( 'loadSong start' , { mediaEl , n, index } ) ;
2590+ debugLog ( 'loadSong start' , { n, index, mediaEl } ) ;
25892591
25902592 if ( ! isCurrent )
25912593 setSubtitlesDisplay ( ) ; // avoid stuck subtitles on track change
@@ -2594,7 +2596,7 @@ async function loadSong( n, playIt ) {
25942596
25952597 if ( song ) {
25962598 if ( isCurrent )
2597- queueIndex = index ;
2599+ setQueueIndex ( index ) ;
25982600
25992601 addMetadata ( song , audioEl ) ;
26002602 loadSubs ( audioEl , song ) ;
@@ -2617,29 +2619,27 @@ async function loadSong( n, playIt ) {
26172619 loadAudioSource ( audioEl , song . dataset . file ) ;
26182620 try {
26192621 await waitForLoadedData ( audioEl ) ;
2620- if ( isCurrent && playIt )
2622+ if ( playIt )
26212623 audioEl . play ( ) ;
26222624 success = true ;
26232625 }
26242626 catch ( e ) { } // error will be handled (logged) by `audioOnError()`
26252627 }
26262628
2627- if ( success ) {
2628- if ( isCurrent ) {
2629- updatePlaylistUI ( ) ;
2630- loadSong ( NEXT_TRACK ) ;
2631- }
2632- else
2633- audioEl . load ( ) ;
2634- }
2629+ if ( ! success && playIt ) // in case of error and play was requested, try the next track
2630+ loadSong ( NEXT_TRACK , playIt ) ;
2631+ else if ( isCurrent ) // load the next track in the secondary element
2632+ loadSong ( NEXT_TRACK ) ;
2633+ // else
2634+ // audioEl.load(); // intended to improve gapless playback, but its effectiveness was inconclusive (generates additional abort/emptied events)
26352635
26362636 song . classList . toggle ( 'error' , ! success ) ;
26372637 }
26382638
26392639 if ( ! isCurrent )
26402640 skipping = false ; // finished skipping track
26412641
2642- debugLog ( 'loadSong end' , { mediaEl, success } ) ;
2642+ debugLog ( 'loadSong end' , { n , index , mediaEl, success } ) ;
26432643
26442644 return success ;
26452645}
@@ -2747,7 +2747,7 @@ function openGradientEditorNew( makeCopy ) {
27472747}
27482748
27492749/**
2750- * Play next song on queue
2750+ * Plays the track loaded into the secondary media element (and make it the current one)
27512751 */
27522752function playNextSong ( play ) {
27532753 debugLog ( 'playNextSong' , { play, queueIndex, skipping } ) ;
@@ -2758,9 +2758,9 @@ function playNextSong( play ) {
27582758 skipping = true ;
27592759
27602760 if ( queueIndex < queueLength ( ) - 1 )
2761- queueIndex ++ ;
2761+ setQueueIndex ( queueIndex + 1 ) ;
27622762 else if ( isSwitchOn ( elRepeat ) )
2763- queueIndex = 0 ;
2763+ setQueueIndex ( 0 ) ;
27642764 else {
27652765 skipping = false ;
27662766 return false ;
@@ -2772,23 +2772,21 @@ function playNextSong( play ) {
27722772 setOverlay ( ) ;
27732773 setCurrentCover ( ) ;
27742774
2775- if ( play && audioElement [ currAudio ] . src ) { // note: play() on empty element never resolves!
2776- audioElement [ currAudio ] . play ( )
2775+ const audioEl = audioElement [ currAudio ] ;
2776+ if ( play && audioEl . src ) { // note: play() on empty element never resolves!
2777+ audioEl . play ( )
27772778 . then ( ( ) => loadSong ( NEXT_TRACK ) )
27782779 . catch ( err => {
2779- debugLog ( { err } ) ;
27802780 // ignore AbortError when play promise is interrupted by a new load request or call to pause()
27812781 if ( err . code != ERR_ABORT ) {
27822782 consoleLog ( err , true ) ;
2783- loadSong ( NEXT_TRACK ) ;
2784- playNextSong ( true ) ;
2783+ loadSong ( NEXT_TRACK , play ) ;
27852784 }
27862785 } ) ;
27872786 }
27882787 else
27892788 loadSong ( NEXT_TRACK ) ;
27902789
2791- updatePlaylistUI ( ) ;
27922790 return true ;
27932791}
27942792
@@ -4818,9 +4816,10 @@ function toggleMultiChannel() {
48184816}
48194817
48204818/**
4821- * Update the play queue
4819+ * Set the `queueIndex` global and update the play queue display
48224820 */
4823- function updatePlaylistUI ( ) {
4821+ function setQueueIndex ( newValue ) {
4822+ queueIndex = newValue ;
48244823
48254824 const current = elPlayqueue . querySelector ( '.current' ) ,
48264825 newCurr = elPlayqueue . children [ queueIndex ] ;
@@ -5247,7 +5246,7 @@ function updateRangeValue( el ) {
52475246 e . target . classList . remove ( 'selected' , 'sortable-chosen' ) ;
52485247 }
52495248 } ) ;
5250- queueIndex = 0 ;
5249+ setQueueIndex ( 0 ) ;
52515250
52525251 // Add drag-n-drop functionality to the play queue
52535252 Sortable . create ( elPlayqueue , {
@@ -5261,7 +5260,7 @@ function updateRangeValue( el ) {
52615260 multiDragKey : 'ctrl' ,
52625261 selectedClass : 'selected' ,
52635262 onEnd : evt => {
5264- queueIndex = getIndex ( elPlayqueue . querySelector ( '.current' ) ) ;
5263+ setQueueIndex ( getIndex ( elPlayqueue . querySelector ( '.current' ) ) ) ;
52655264 if ( evt . newIndex == 0 && ! isPlaying ( ) )
52665265 loadSong ( 0 ) ;
52675266 else
0 commit comments