diff --git a/gapless5.js b/gapless5.js index ab1e4f0..93505d3 100644 --- a/gapless5.js +++ b/gapless5.js @@ -1331,36 +1331,39 @@ function Gapless5(options = {}, deprecated = {}) { // eslint-disable-line no-unu * @param {number | string} pointOrPath - audio path or playlist index */ this.removeTrack = (pointOrPath) => { - const point = this.playlist.indexFromTrack(pointOrPath); - if (!isValidIndex(point)) { - log.warn(`Cannot remove missing track: ${pointOrPath}`); + const index = typeof pointOrPath === 'number' ? pointOrPath : this.findTrack(pointOrPath); + if (!isValidIndex(index)) { + log.error(`Cannot remove missing track at index: ${index}`); return; } - const deletedPlaying = point === this.playlist.trackNumber; - const { source: curSource } = this.playlist.getSourceIndexed(point); - if (!curSource) { - return; - } - let wasPlaying = false; + const isCurrentTrack = index === this.playlist.trackNumber; + const wasPlayingCurrentTrack = isCurrentTrack && this.isPlaying(); - if (curSource.state === Gapless5State.Loading) { - curSource.unload(); - } else if (curSource.inPlayState(true)) { - wasPlaying = true; - curSource.stop(); + // If it's the current track, pause playback + if (wasPlayingCurrentTrack) { + this.pause(); } - this.playlist.remove(point); - - if (deletedPlaying) { - this.next(); // Don't stop after a delete - if (wasPlaying) { - this.play(); - } + // If removing a track before the current track, decrement trackNumber + if (index < this.playlist.trackNumber) { + this.playlist.trackNumber--; } + // If removing the current track or a track after it, but trackNumber would exceed new length + else if (this.playlist.trackNumber >= this.playlist.numTracks() - 1) { + this.playlist.trackNumber = Math.max(0, this.playlist.numTracks() - 2); + } + // Otherwise keep the same trackNumber + this.playlist.sources[index].unload(); + this.playlist.sources.splice(index, 1); + this.playlist.shuffledIndices = []; this.uiDirty = true; + + // If we were playing and there are tracks remaining, play the next available track + if (wasPlayingCurrentTrack && this.playlist.numTracks() > 0) { + this.play(); + } }; /** diff --git a/gapless5.test.js b/gapless5.test.js index 5ce152b..49e694f 100644 --- a/gapless5.test.js +++ b/gapless5.test.js @@ -152,6 +152,71 @@ describe('Gapless-5 object with tracklist', () => { player.stop(); expect(player.onstop).toHaveBeenCalledWith(TRACKS[0]); }); + + it('regression: maintains correct index when removing tracks that come after the currently playing track', () => { + const testTracks = ['track1.mp3', 'track2.mp3', 'track3.mp3']; + const player = new Gapless5({ + ...INIT_OPTIONS, + tracks: testTracks, + }); + + // Verify initial state + expect(player.getIndex()).toBe(0); + expect(player.getTracks()).toStrictEqual(testTracks); + + // Move to track 1 + player.gotoTrack(1); + expect(player.getIndex()).toBe(1); + + // Remove track at index 2 + player.removeTrack(2); + + // Check index is still 1 and track list is updated + expect(player.getIndex()).toBe(1); + expect(player.getTracks()).toStrictEqual(['track1.mp3', 'track2.mp3']); + }); + + it('regression: maintains correct index when removing the currently playing track', () => { + const testTracks = ['track1.mp3', 'track2.mp3', 'track3.mp3']; + const player = new Gapless5({ + ...INIT_OPTIONS, + tracks: testTracks, + }); + + // Verify initial state + expect(player.getIndex()).toBe(0); + expect(player.getTracks()).toStrictEqual(testTracks); + + // Move to track 1 + player.gotoTrack(1); + expect(player.getIndex()).toBe(1); + player.play(); + + // Remove the currently playing track + player.removeTrack(1); + + // Check index is still 1 and track list is updated, therefore we move to the next available track + expect(player.getIndex()).toBe(1); + expect(player.getTracks()).toStrictEqual(['track1.mp3', 'track3.mp3']); + + player.play(); + + // Remove the currently playing track + player.removeTrack(1); + + // Check index is still 0 and track list is updated, therefore we move to the next available track + expect(player.getIndex()).toBe(0); + expect(player.getTracks()).toStrictEqual(['track1.mp3']); + + player.play(); + + // Remove the only remaining track + player.removeTrack(0); + + // Check index is still 0 and track list is updated + expect(player.getIndex()).toBe(0); + expect(player.getTracks()).toStrictEqual([]); + }); }); describe('Gapless-5 object with load limit', () => { @@ -179,3 +244,5 @@ describe('Gapless-5 object with load limit', () => { expect(loadedTracks.size).toBe(0); }); }); + + diff --git a/package.json b/package.json index 29c0769..f599c5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@regosen/gapless-5", - "version": "1.5.5", + "version": "1.5.6", "description": "A gapless JavaScript audio player for HTML5", "main": "gapless5.js", "style": "gapless5.css", diff --git a/types/gapless5.d.ts b/types/gapless5.d.ts index 104c2a8..de46405 100644 --- a/types/gapless5.d.ts +++ b/types/gapless5.d.ts @@ -331,7 +331,7 @@ declare class Gapless5Source { isPlayActive: (checkStarting: any) => boolean; getPosition: () => number; getLength: () => number; - play: (syncPosition: any) => void; + play: (syncPosition: any, skipCallback: any) => void; setPlaybackRate: (rate: any) => void; tick: (updateLoopState: any) => number; getSeekablePercent: () => number;