javascriptaudio-player

Keeping track of all history of audio player (previous, next buttons and random selection from browser)


I need to keep track of all history of which songs have been played with audio player. The difficult part is when user continuously clicks on next and previous button.

Testing data (songs):

export const tracksData = [
  {
    name: 'song-1',
    path: 'path.mp3',
    id: 'song-1-id',
  }, {
    name: 'song-2',
    path: 'path.mp3',
    id: 'song-2-id',
  }, {
    name: 'song-3',
    path: 'path.mp3',
    id: 'song-3-id',
  }, {
    name: 'song-4',
    path: 'path.mp3',
    id: 'song-4-id',
  }, {
    name: 'song-5',
    path: 'path.mp3',
    id: 'song-5-id',
  }, {
    name: 'song-6',
    path: 'path.mp3',
    id: 'song-6-id',
  }, {
    name: 'song-7',
    path: 'path.mp3',
    id: 'song-7-id',
  }, {
    name: 'song-8',
    path: 'path.mp3',
    id: 'song-8-id',
  }, {
    name: 'song-9',
    path: 'path.mp3',
    id: 'song-9-id',
  }, {
    name: 'song-10',
    path: 'path.mp3',
    id: 'song-10-id',
  }
];

Some variables used and their usage behind them:

// currentPlayingTrackIndex = index of tracks array 
// trackQueIndex = position of playedTrackedIndexes
// trackStartPoint === 0 then at the the start of the que and should take just next indexes

Initialize tracks (working as intended):

const initializeTracks = (tracks) => {
  const { path, name, id } = tracks[0];

  const playingTrack = {
    path,
    name,
    id,
  };

  return {
    playingTrack,
    currentPlayingTrackIndex: 0,
    trackQueIndex: 0,
    trackStartPoint: 0,
    playedTrackIndexes: [0]
  }
};

Get next track (when next button is clicked or track ends):

const getNextTrack = (tracks, tracksQueData) => {
  const { currentPlayingTrackIndex, trackQueIndex, trackStartPoint, playedTrackIndexes } = tracksQueData;
  const isCurrentTrackTheLastOfPlayQue = trackStartPoint === 0;

  const nextTrackIndex = isCurrentTrackTheLastOfPlayQue ? currentPlayingTrackIndex + 1 : playedTrackIndexes[trackQueIndex + 1];

  const { path, name, id } = tracks[nextTrackIndex];

  const nextTrack = {
    path,
    name,
    id,
  };

  const nextTrackQueIndex = trackQueIndex + 1;
  const startPoint = trackStartPoint === 0 ? trackStartPoint : trackStartPoint - 1;

  return {
    playingTrack: nextTrack,
    currentPlayingTrackIndex: nextTrackIndex,
    trackQueIndex: nextTrackQueIndex,
    trackStartPoint: startPoint,
    playedTrackIndexes: [...playedTrackIndexes, nextTrackIndex]
  }  
};

Get previous track (when previous button is clicked on):

const getPreviousTrack = (tracks, tracksQueData) => {
  const { currentPlayingTrackIndex, trackQueIndex, trackStartPoint, playedTrackIndexes } = tracksQueData;
  
  const previousTrackQueIndex = trackQueIndex - 1;
  const previousTrackIndex = playedTrackIndexes[previousTrackQueIndex];
  const { path, name, id } = tracks[previousTrackIndex];

  const previousTrack = {
    path,
    name,
    id,
  };

  const startPoint = trackStartPoint + 1;

  return {
    playingTrack: previousTrack,
    currentPlayingTrackIndex: previousTrackIndex,
    trackQueIndex: previousTrackQueIndex,
    trackStartPoint: startPoint,
    playedTrackIndexes: [...playedTrackIndexes, previousTrackIndex]
  }
};

Failing test case for getPreviousTrack. Obviously it doesn't work with use case like this anymore. I think the whole logic needs to be rewritten to keep track of the history correctly, but I am not sure how exactly. Maybe using uuid for each played track, but how does that help me exactly? I am actually baffled how complex can a simple audio player logic be (if you really want to keep track of the whole play history correctly and not just reset it at some point for a simplified logic).

it('Should return correct track object when getting previous track and the same tracks have been played multiple times, more complex', () => {
  const tracksQueData = {
    currentPlayingTrackIndex: 5,
    trackQueIndex: 5,
    trackStartPoint: 0,
    playedTrackIndexes: [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 5]
  };

  const track = getPreviousTrack(tracksTestData, tracksQueData);

  expect(track).toEqual(
    {
      playingTrack: {
        name: 'song-4',
        path: 'path.mp3',
        id: 'song-4-id',
      },
    currentPlayingTrackIndex: 4,
    trackQueIndex: 4,
    trackStartPoint: 1,
    playedTrackIndexes: [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 5, 4]
  })
});

There is also an option to select tracks from the library as well. This is an separate function, which I have not yet implemented. So lets say user plays indexes [0, 1, 2], then selects tracksData[7] then the playedTrackIndexes would be [0, 1, 2, 7].

Working more simple test case:

it('Should return correct track object when getting previous track and the same tracks have been played multiple times', () => {
  const tracksQueData = {
    currentPlayingTrackIndex: 4,
    trackQueIndex: 4,
    trackStartPoint: 4,
    playedTrackIndexes: [0, 1, 2, 3, 2, 1, 2, 1, 2, 3, 4, 3, 2, 3, 4, 5, 4, 3, 2, 3, 2, 3, 4]
  };

  const track = getPreviousTrack(tracksTestData, tracksQueData);

  expect(track).toEqual(
    {
      playingTrack: {
        name: 'song-4',
        path: 'path.mp3',
        id: 'song-4-id',
      },
    currentPlayingTrackIndex: 3,
    trackQueIndex: 3,
    trackStartPoint: 5,
    playedTrackIndexes: [0, 1, 2, 3, 2, 1, 2, 1, 2, 3, 4, 3, 2, 3, 4, 5, 4, 3, 2, 3, 2, 3, 4, 3],
  })
});

Can anyone please point me in the right direction how to implement it better? Obviously this logic only works if user doesn't click on previous/next buttons continuously. Seriously stuck here.


Solution

  • The (very subtle) issue I see here is confusion of the meanings of currentPlayingTrackIndex and trackQueIndex.

    currentPlayingTrackIndex is a bit like the ID of the playing track - it is the index of the playing track inside the tracksData array.

    Meanwhile trackQueIndex is the index of the playing track within the history array. They won't always be the same as each other, which you've "expected" in your test function.

    So actually, your code and logic is correct, but the usage of these variables in your testing function is wrong.

    In your test function you should define the tracksQueData differently, so that the trackQueIndex points to the element stored inside of currentPlayingTrackIndex within the playedTrackIndexes array, and use trackQueIndex to keep track of where in the history you are.

    I.e., currentPlayingTrackIndex should always be given by tracksQueData.playedTrackIndexes[tracksQueData.trackQueIndex].

    However you have defined both as 5 when the history array is actually longer. In your non-working example, the queue index variable should be 21, not 5.

    To know when to start playing new tracks, you cleverly use the trackStartPoint variable and there is no logic error here.

    Here is proof that this code works if you give it valid test data for it to parse:

    var tracksData = [
      {
        name: 'song-1',
        path: 'path.mp3',
        id: 'song-1-id',
      }, {
        name: 'song-2',
        path: 'path.mp3',
        id: 'song-2-id',
      }, {
        name: 'song-3',
        path: 'path.mp3',
        id: 'song-3-id',
      }, {
        name: 'song-4',
        path: 'path.mp3',
        id: 'song-4-id',
      }, {
        name: 'song-5',
        path: 'path.mp3',
        id: 'song-5-id',
      }, {
        name: 'song-6',
        path: 'path.mp3',
        id: 'song-6-id',
      }, {
        name: 'song-7',
        path: 'path.mp3',
        id: 'song-7-id',
      }, {
        name: 'song-8',
        path: 'path.mp3',
        id: 'song-8-id',
      }, {
        name: 'song-9',
        path: 'path.mp3',
        id: 'song-9-id',
      }, {
        name: 'song-10',
        path: 'path.mp3',
        id: 'song-10-id',
      }
    ];
    
    function initializeTracks (tracks) {
      const { path, name, id } = tracks[0];
    
      const playingTrack = {
        path,
        name,
        id,
      };
    
      return {
        playingTrack,
        currentPlayingTrackIndex: 0,
        trackQueIndex: 0,
        trackStartPoint: 0,
        playedTrackIndexes: [0]
      }
    }
    
    function getNextTrack (tracks, tracksQueData) {
      const { currentPlayingTrackIndex, trackQueIndex, trackStartPoint, playedTrackIndexes } = tracksQueData;
      const isCurrentTrackTheLastOfPlayQue = trackStartPoint === 0;
    
      const nextTrackIndex = isCurrentTrackTheLastOfPlayQue ? currentPlayingTrackIndex + 1 : playedTrackIndexes[trackQueIndex + 1];
    
      const { path, name, id } = tracks[nextTrackIndex];
    
      const nextTrack = {
        path,
        name,
        id,
      };
    
      const nextTrackQueIndex = trackQueIndex + 1;
      const startPoint = trackStartPoint === 0 ? trackStartPoint : trackStartPoint - 1;
    
      return {
        playingTrack: nextTrack,
        currentPlayingTrackIndex: nextTrackIndex,
        trackQueIndex: nextTrackQueIndex,
        trackStartPoint: startPoint,
        playedTrackIndexes: [...playedTrackIndexes, nextTrackIndex]
      }  
    }
    
    
    function getPreviousTrack (tracks, tracksQueData) {
      const { currentPlayingTrackIndex, trackQueIndex, trackStartPoint, playedTrackIndexes } = tracksQueData;
      
      const previousTrackQueIndex = trackQueIndex - 1;
      const previousTrackIndex = playedTrackIndexes[previousTrackQueIndex];
      const { path, name, id } = tracks[previousTrackIndex];
    
      const previousTrack = {
        path,
        name,
        id,
      };
    
      const startPoint = trackStartPoint + 1;
    
      return {
        playingTrack: previousTrack,
        currentPlayingTrackIndex: previousTrackIndex,
        trackQueIndex: previousTrackQueIndex,
        trackStartPoint: startPoint,
        playedTrackIndexes: [...playedTrackIndexes, previousTrackIndex]
      }
    }
    
    
    
    
    
    function test() {
      const tracksQueData = {
        currentPlayingTrackIndex: 5, // like id
        trackQueIndex: 21, // index in history
        trackStartPoint: 0,
        playedTrackIndexes: [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 5]
      };
      
      console.log("Currently playing", tracksQueData.currentPlayingTrackIndex);
      let prev = getPreviousTrack(tracksData, tracksQueData);
      console.log("Prev track:", prev.currentPlayingTrackIndex);
      
      prev = getPreviousTrack(tracksData, prev);
      console.log("Prev again:", prev.currentPlayingTrackIndex);
      // ... so on
      
      console.log("History:", ...prev.playedTrackIndexes);
      
      console.log("Now, play next few tracks");
      let next = getNextTrack(tracksData, prev);
      console.log("Prev again:", next.currentPlayingTrackIndex);
      
      next = getNextTrack(tracksData, next);
      console.log("Prev again:", next.currentPlayingTrackIndex);
      
      next = getNextTrack(tracksData, next);
      console.log("Prev again:", next.currentPlayingTrackIndex);
    }
    
    console.clear();
    test();
    <h3>Please go full screen to be able to see all of the content written here</h3>
    <ul>
      <li> Look at the history array in the `test()` function here.
      <li> To start with, it is playing track 5.
      <li> Call `getPrevTrack` once, and it plays track 4. 
      <li> Call it again and it plays track 3. 
      <li> Now look at the history array (in the console). You will see the tracks 4 and 3 appended to it after the initially playing one, 5.
      <br><br>
      <li> If you now call getNextTrack() it will play track 4 <br>
      <li> Call it again and you get track 5 <br>
      <li> Call it again, and it skips the ones that were <em>just</em> appended to the history array and instead plays the one that comes after 5 in the playlist array - in other words, track 6.
    </ul>

    To make it very clear, the issue is that

    const tracksQueData = {
        currentPlayingTrackIndex: 5,
        trackQueIndex: 5,
        trackStartPoint: 0,
        playedTrackIndexes: [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 5]
    };
    

    is wrong as this data cannot be created if you use the functions getNextTrack and getPreviousTrack.

    Instead, it should be:

    const tracksQueData = {
        currentPlayingTrackIndex: 5, // like id
        trackQueIndex: 21, // index in history
        trackStartPoint: 0,
        playedTrackIndexes: [0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 5]
    };
    

    The error is that, again, as I mentioned before, you are using trackQueIndex incorrectly. In your code example above, you assign 5 to it.

    So it points to:

    [0, 1, 2, 1, 0, 1, ...]
       _____________^
    

    instead of to

    ..., 2, 3, 4, 5]
          ________^
    

    for it to point to the correct track in the history array, it must be set to the value 21. Because when the function getPrevTrack is called, it will decrement and point to 4.

    The reason why you thought your code didn't work is because when it had the wrong value of 5 assigned to it, when it decrements it becomes 4 and points to the track 0 instead of 4, as 0 is at index 4 in the history array.

    I hope that is clear.