skip to Main Content

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.

2

Answers


  1. 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 in your expectation these variables are wrong.

    So 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 is actually longer.

    To know when to start playing new tracks, you cleverly use the trackStartPoint variable.

    Here is this in action:

    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],
      }
    }
    
    
    
    var test = (tracksData) => {
      const tracksQueData = {
        currentPlayingTrackIndex: 5, // a bit like track id
        trackQueIndex: 7, // the index of the track in history
        trackStartPoint: 0,
        playedTrackIndexes: [0,1,0,1,2,3,4,5],
      };
      
      console.log("Original is:", ...[0,1,0,1,2,3,4,5]);
      console.log("Currently playing:", tracksQueData.playedTrackIndexes[tracksQueData.trackQueIndex])
      
      console.log("go to previous a few times:");
      var track = getPreviousTrack(tracksData, tracksQueData);
      console.log(track.currentPlayingTrackIndex);
      
      var track = getPreviousTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      var track = getPreviousTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      console.log("and go once forward:");
      var track = getNextTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      console.log("and back again:");
      var track = getPreviousTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      console.log("go to next a few times:");
      var track = getNextTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      var track = getNextTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      var track = getNextTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      var track = getNextTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      var track = getNextTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      var track = getNextTrack(tracksData, track);
      console.log(track.currentPlayingTrackIndex);
      
      console.log("resulting history from this manipulation:"); // expected: 0 1 0 1 2 3 4 5 4 3 2 3 2 3 4 5 6 7 8
      console.log(...track.playedTrackIndexes);
    
      return track;
    };
    
    console.log("Track order is as expected:")
    test(tracksData);

    Note – as I stated in the comments, I think it would be a good idea to convert this code into classes as that would make it more readable. This is the perfect use case for OOP: you have a couple entities (the player and tracks) that interract with each other.

    Login or Signup to reply.
  2. As far as I understood, you have your set of data, which contains a playlist. You want to be able to keep a record of the playing history. You also want to be able to click on a song and play it, aswell using the next and previous buttons to navigate through the history if possible, otherwise you want to go through the playlist.
    Use the browser’s console to have a better view of the console logs.

    To make it as simple as possible, I would do the following:

    const playlist = [{
        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',
    }];
    
    const history = [];
    let historyIndex;
    let currentSong = {};
    
    function playSong(song, fromHistory = false) {
        // if a song is not played from the history, it shall be added to the history and historyIndex will be set as the latest song
        if (!fromHistory && song !== currentSong){
            history.push(song);
            historyIndex = history.length - 1;
        }
        currentSong = song;
        console.log("current: ", currentSong.name);
        console.log("playing from history: ", fromHistory);
        console.log("history: ", history);
        console.log("historyIndex: ", historyIndex);
    }
    
    // a general function for the NEXT button
    function next() {
        if (currentSong === {}) return; // do nothing if no song is selected
        
        // checks whether there is a history or not
        if (history.length === 0) {
            nextInPlaylist();
        } else {
            nextInHistory();
        }
    }
    
    // plays next song in the playlist
    function nextInPlaylist() {
        // finding the currentIndex of the selected song
        const currentIndex = playlist.findIndex(s => s.id === currentSong.id);
        
        // checks if it is the last song on the playlist. If yes, it should just start from the beginning again
        if (currentIndex === playlist.length - 1) {
            playSong(playlist.at(0));
        } else {
            playSong(playlist.at(currentIndex + 1));
        }
    }
    
    // plays next song in the history
    function nextInHistory() {
        // checks if it is the last song on the history. If yes, it should just play the next song in the playlist
        if (historyIndex === history.length - 1) {
            nextInPlaylist();
        } else {
            historyIndex++;
            playSong(history.at(historyIndex), true);
        }
    }
    
    function back() {
        if (currentSong === {}) return; // do nothing if no song is selected
        
        // checks whether there is a history or not
        if (history.length === 0) {
            backInPlaylist();
        } else {
            backInHistory();
        }
    }
    
    // plays previous song in the playlist
    function backInPlaylist() {
        const currentIndex = playlist.findIndex(s => s.id === currentSong.id);
        
        // checks if it is the first song on the playlist. If yes, it should just start from the end
        if (currentIndex === 0) {
            playSong(playlist.at(-1));
        } else {
            playSong(playlist.at(currentIndex - 1));
        }
    }
    
    // plays previous song in the history
    function backInHistory() {
        // checks if it is the first song on the history. If yes, it should just play the previous song in the playlist
        if (historyIndex === 0) {
            backInPlaylist();
        } else {
            historyIndex--;
            playSong(history.at(historyIndex), true);
        }
    }
    
    
    function selectRandomSong() {
        const maxSongIndex = playlist.length - 1;
        const randomNum = Math.floor(Math.random() * maxSongIndex);
        playSong(playlist.at(randomNum));
    }
    <button onClick="selectRandomSong()">Select random</button><br>
    <button onClick="next()">Next</button><br>
    <button onClick="back()">Back</button>

    The explanation of the code is also included in comments.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search