diff --git a/src/playlist/Playlist.cpp b/src/playlist/Playlist.cpp index 3436c73bd..557c82e4f 100644 --- a/src/playlist/Playlist.cpp +++ b/src/playlist/Playlist.cpp @@ -34,6 +34,7 @@ bool Playlist::Empty() const void Playlist::Clear() { + m_presetHistory.clear(); m_items.clear(); } @@ -59,6 +60,7 @@ bool Playlist::AddItem(const std::string& filename, uint32_t index, bool allowDu } } + m_presetHistory.clear(); if (index >= m_items.size()) { m_items.emplace_back(filename); @@ -76,6 +78,7 @@ auto Playlist::AddPath(const std::string& path, uint32_t index, bool recursive, { uint32_t presetsAdded{0}; + m_presetHistory.clear(); if (recursive) { for (const auto& entry : recursive_directory_iterator(path)) @@ -124,6 +127,7 @@ auto Playlist::RemoveItem(uint32_t index) -> bool return false; } + m_presetHistory.clear(); m_items.erase(m_items.cbegin() + index); return true; @@ -145,6 +149,8 @@ auto Playlist::Shuffle() const -> bool void Playlist::Sort(uint32_t startIndex, uint32_t count, Playlist::SortPredicate predicate, Playlist::SortOrder order) { + m_presetHistory.clear(); + std::sort(m_items.begin() + startIndex, m_items.begin() + startIndex + count, [predicate, order](const Item& left, const Item& right) { @@ -187,6 +193,8 @@ auto Playlist::NextPresetIndex() -> size_t throw PlaylistEmptyException(); } + AddCurrentPresetIndexToHistory(); + if (m_shuffle) { std::uniform_int_distribution randomDistribution(0, m_items.size()); @@ -205,6 +213,58 @@ auto Playlist::NextPresetIndex() -> size_t } +auto Playlist::PreviousPresetIndex() -> size_t +{ + if (m_items.empty()) + { + throw PlaylistEmptyException(); + } + + AddCurrentPresetIndexToHistory(); + + if (m_shuffle) + { + std::uniform_int_distribution randomDistribution(0, m_items.size()); + m_currentPosition = randomDistribution(m_randomGenerator); + } + else + { + if (m_currentPosition == 0) + { + m_currentPosition = m_items.size() -1; + } + else + { + m_currentPosition--; + } + } + + return m_currentPosition; +} + +auto Playlist::LastPresetIndex() -> size_t +{ + if (m_items.empty()) + { + throw PlaylistEmptyException(); + } + + if (!m_presetHistory.empty()) + { + m_currentPosition = m_presetHistory.back(); + m_presetHistory.pop_back(); + } + else + { + m_currentPosition = PreviousPresetIndex(); + // Remove added history item again to prevent ping-pong behavior + m_presetHistory.pop_back(); + } + + return m_currentPosition; +} + + auto Playlist::PresetIndex() const -> size_t { if (m_items.empty()) @@ -223,6 +283,13 @@ auto Playlist::SetPresetIndex(size_t presetIndex) -> size_t throw PlaylistEmptyException(); } + AddCurrentPresetIndexToHistory(); + + if (presetIndex == m_currentPosition) + { + return m_currentPosition; + } + m_currentPosition = presetIndex; if (m_currentPosition >= m_items.size()) @@ -234,5 +301,30 @@ auto Playlist::SetPresetIndex(size_t presetIndex) -> size_t } +void Playlist::AddCurrentPresetIndexToHistory() +{ + // No duplicate entries. + if (!m_presetHistory.empty() && m_currentPosition == m_presetHistory.back()) + { + return; + } + + m_presetHistory.push_back(m_currentPosition); + if (m_presetHistory.size() > MaxHistoryItems) + { + m_presetHistory.pop_front(); + } +} + + +void Playlist::RemoveLastHistoryEntry() +{ + if (!m_presetHistory.empty()) + { + m_presetHistory.pop_back(); + } +} + + } // namespace Playlist } // namespace ProjectM diff --git a/src/playlist/Playlist.hpp b/src/playlist/Playlist.hpp index 56ae93b0f..95fd0f329 100644 --- a/src/playlist/Playlist.hpp +++ b/src/playlist/Playlist.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,11 @@ public: */ static constexpr auto InsertAtEnd = std::numeric_limits::max(); + /** + * Maximum number of items in the playback history. + */ + static constexpr size_t MaxHistoryItems = 1000; + /** * Sort predicate. */ @@ -172,6 +178,28 @@ public: */ virtual auto NextPresetIndex() -> size_t; + /** + * @brief Returns the previous preset index in the playlist. + * + * Each call will either decrement the current index, or select a random preset, depending on + * the shuffle setting. + * + * @throws PlaylistEmptyException Thrown if the playlist is currently empty. + * @return The index of the previous playlist item. + */ + virtual auto PreviousPresetIndex() -> size_t; + + /** + * @brief Returns the last preset index that has been played. + * + * Each call will pop the last history item. If the history is empty, it will internally call + * PreviousPresetIndex(), but not add a history item. + * + * @throws PlaylistEmptyException Thrown if the playlist is currently empty. + * @return The index of the last (or previous) playlist item. + */ + virtual auto LastPresetIndex() -> size_t; + /** * @brief Returns the current playlist/preset index without changing the position. * @throws PlaylistEmptyException Thrown if the playlist is currently empty. @@ -191,10 +219,22 @@ public: */ virtual auto SetPresetIndex(size_t presetIndex) -> size_t; + /** + * @brief Removes the newest entry in the playback history. + * Useful if the last playlist item failed to load, so it won't get selected again. + */ + virtual void RemoveLastHistoryEntry(); + private: - std::vector m_items; //!< Items in the current playlist. - bool m_shuffle{false}; //!< True if shuffle mode is enabled, false to play presets in order. - size_t m_currentPosition{0}; //!< Current playlist position. + /** + * @brief Adds a preset to the history and trims the list if it gets too long. + */ + void AddCurrentPresetIndexToHistory(); + + std::vector m_items; //!< Items in the current playlist. + bool m_shuffle{false}; //!< True if shuffle mode is enabled, false to play presets in order. + size_t m_currentPosition{0}; //!< Current playlist position. + std::list m_presetHistory; //!< The playback history. std::default_random_engine m_randomGenerator; }; diff --git a/src/playlist/PlaylistCWrapper.cpp b/src/playlist/PlaylistCWrapper.cpp index b868c8d95..e62755e0f 100644 --- a/src/playlist/PlaylistCWrapper.cpp +++ b/src/playlist/PlaylistCWrapper.cpp @@ -59,6 +59,11 @@ void PlaylistCWrapper::OnPresetSwitchFailed(const char* presetFilename, const ch auto* playlist = reinterpret_cast(userData); + // ToDo: Add different retry behavior for set/next/previous/last calls. + + // Don't go back to a broken preset. + playlist->RemoveLastHistoryEntry(); + // Preset switch may fail due to broken presets, retry a few times before giving up. if (playlist->m_presetSwitchFailedCount < playlist->m_presetSwitchRetryCount) { @@ -419,10 +424,57 @@ auto projectm_playlist_set_position(projectm_playlist_handle instance, uint32_t { auto newIndex = playlist->SetPresetIndex(new_position); playlist->PlayPresetIndex(newIndex, hard_cut, true); - return newIndex; + return playlist->PresetIndex(); } catch (ProjectM::Playlist::PlaylistEmptyException&) { return 0; } } + + +uint32_t projectm_playlist_play_next(projectm_playlist_handle instance, bool hard_cut) +{ + auto* playlist = playlist_handle_to_instance(instance); + try + { + auto newIndex = playlist->NextPresetIndex(); + playlist->PlayPresetIndex(newIndex, hard_cut, true); + return playlist->PresetIndex(); + } + catch (ProjectM::Playlist::PlaylistEmptyException&) + { + return 0; + } +} + + +uint32_t projectm_playlist_play_previous(projectm_playlist_handle instance, bool hard_cut) +{ + auto* playlist = playlist_handle_to_instance(instance); + try + { + auto newIndex = playlist->PreviousPresetIndex(); + playlist->PlayPresetIndex(newIndex, hard_cut, true); + return playlist->PresetIndex(); + } + catch (ProjectM::Playlist::PlaylistEmptyException&) + { + return 0; + } +} + +uint32_t projectm_playlist_play_last(projectm_playlist_handle instance, bool hard_cut) +{ + auto* playlist = playlist_handle_to_instance(instance); + try + { + auto newIndex = playlist->LastPresetIndex(); + playlist->PlayPresetIndex(newIndex, hard_cut, true); + return playlist->PresetIndex(); + } + catch (ProjectM::Playlist::PlaylistEmptyException&) + { + return 0; + } +} \ No newline at end of file diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index e48a0aecb..74d7984d9 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -373,6 +373,52 @@ uint32_t projectm_playlist_get_position(projectm_playlist_handle instance); uint32_t projectm_playlist_set_position(projectm_playlist_handle instance, uint32_t new_position, bool hard_cut); +/** + * @brief Plays the next playlist item and returns the index of the new preset. + * + * If shuffle is on, it will select a random preset, otherwise the next in the playlist. If the + * end of the playlist is reached in continuous mode, it will wrap back to 0. + * + * The old playlist item is added to the history. + * + * @param instance The playlist manager instance. + * @param hard_cut If true, the preset transition is instant. If true, a smooth transition is played. + * @return The new playlist position. If the playlist is empty, 0 will be returned. + */ +uint32_t projectm_playlist_play_next(projectm_playlist_handle instance, bool hard_cut); + +/** + * @brief Plays the previous playlist item and returns the index of the new preset. + * + * If shuffle is on, it will select a random preset, otherwise the next in the playlist. If the + * end of the playlist is reached in continuous mode, it will wrap back to 0. + * + * The old playlist item is added to the history. + * + * @param instance The playlist manager instance. + * @param hard_cut If true, the preset transition is instant. If true, a smooth transition is played. + * @return The new playlist position. If the playlist is empty, 0 will be returned. + */ +uint32_t projectm_playlist_play_previous(projectm_playlist_handle instance, bool hard_cut); + +/** + * @brief Plays the last preset played in the history and returns the index of the preset. + * + * The history keeps track of the last 1000 presets and will go back in the history. The + * playback history will be cleared whenever the playlist items are changed. + * + * If the history is empty, this call behaves identical to projectm_playlist_play_previous(), + * but the item is not added to the history. + * + * Presets which failed to load are not recorded in the history and thus will be skipped when + * calling this method. + * + * @param instance The playlist manager instance. + * @param hard_cut If true, the preset transition is instant. If true, a smooth transition is played. + * @return The new playlist position. If the playlist is empty, 0 will be returned. + */ +uint32_t projectm_playlist_play_last(projectm_playlist_handle instance, bool hard_cut); + #ifdef __cplusplus } // extern "C" diff --git a/tests/playlist/APITest.cpp b/tests/playlist/APITest.cpp index 14b4b9d02..0d6e030d9 100644 --- a/tests/playlist/APITest.cpp +++ b/tests/playlist/APITest.cpp @@ -372,6 +372,9 @@ TEST(projectMPlaylistAPI, SetPosition) .Times(1); EXPECT_CALL(mockPlaylist, PlayPresetIndex(512, false, true)) .Times(1); + EXPECT_CALL(mockPlaylist, PresetIndex()) + .Times(2) + .WillRepeatedly(Return(512)); EXPECT_EQ(projectm_playlist_set_position(reinterpret_cast(&mockPlaylist), 256, true), 512); EXPECT_EQ(projectm_playlist_set_position(reinterpret_cast(&mockPlaylist), 256, false), 512); @@ -389,3 +392,118 @@ TEST(projectMPlaylistAPI, SetPositionException) EXPECT_EQ(projectm_playlist_set_position(reinterpret_cast(&mockPlaylist), 256, true), 0); EXPECT_EQ(projectm_playlist_set_position(reinterpret_cast(&mockPlaylist), 256, false), 0); } + + +TEST(projectMPlaylistAPI, PlayNext) +{ + PlaylistCWrapperMock mockPlaylist; + + EXPECT_CALL(mockPlaylist, NextPresetIndex()) + .Times(2) + .WillRepeatedly(Return(512)); + EXPECT_CALL(mockPlaylist, PlayPresetIndex(512, true, true)) + .Times(1); + EXPECT_CALL(mockPlaylist, PlayPresetIndex(512, false, true)) + .Times(1); + EXPECT_CALL(mockPlaylist, PresetIndex()) + .Times(2) + .WillRepeatedly(Return(512)); + + EXPECT_EQ(projectm_playlist_play_next(reinterpret_cast(&mockPlaylist), true), 512); + EXPECT_EQ(projectm_playlist_play_next(reinterpret_cast(&mockPlaylist), false), 512); +} + + +TEST(projectMPlaylistAPI, PlayNextException) +{ + PlaylistCWrapperMock mockPlaylist; + + EXPECT_CALL(mockPlaylist, NextPresetIndex()) + .Times(2) + .WillRepeatedly(Throw(ProjectM::Playlist::PlaylistEmptyException())); + + EXPECT_EQ(projectm_playlist_play_next(reinterpret_cast(&mockPlaylist), true), 0); + EXPECT_EQ(projectm_playlist_play_next(reinterpret_cast(&mockPlaylist), false), 0); +} + + +TEST(projectMPlaylistAPI, PlayPrevious) +{ + PlaylistCWrapperMock mockPlaylist; + + EXPECT_CALL(mockPlaylist, PreviousPresetIndex()) + .Times(2) + .WillRepeatedly(Return(512)); + EXPECT_CALL(mockPlaylist, PlayPresetIndex(512, true, true)) + .Times(1); + EXPECT_CALL(mockPlaylist, PlayPresetIndex(512, false, true)) + .Times(1); + EXPECT_CALL(mockPlaylist, PresetIndex()) + .Times(2) + .WillRepeatedly(Return(512)); + + EXPECT_EQ(projectm_playlist_play_previous(reinterpret_cast(&mockPlaylist), true), 512); + EXPECT_EQ(projectm_playlist_play_previous(reinterpret_cast(&mockPlaylist), false), 512); +} + + +TEST(projectMPlaylistAPI, PlayPreviousException) +{ + PlaylistCWrapperMock mockPlaylist; + + EXPECT_CALL(mockPlaylist, PreviousPresetIndex()) + .Times(2) + .WillRepeatedly(Throw(ProjectM::Playlist::PlaylistEmptyException())); + + EXPECT_EQ(projectm_playlist_play_previous(reinterpret_cast(&mockPlaylist), true), 0); + EXPECT_EQ(projectm_playlist_play_previous(reinterpret_cast(&mockPlaylist), false), 0); +} + + +TEST(projectMPlaylistAPI, PlayLast) +{ + PlaylistCWrapperMock mockPlaylist; + + EXPECT_CALL(mockPlaylist, PreviousPresetIndex()) + .Times(2) + .WillRepeatedly(Return(512)); + EXPECT_CALL(mockPlaylist, PlayPresetIndex(512, true, true)) + .Times(1); + EXPECT_CALL(mockPlaylist, PlayPresetIndex(512, false, true)) + .Times(1); + EXPECT_CALL(mockPlaylist, PresetIndex()) + .Times(2) + .WillRepeatedly(Return(512)); + + EXPECT_EQ(projectm_playlist_play_previous(reinterpret_cast(&mockPlaylist), true), 512); + EXPECT_EQ(projectm_playlist_play_previous(reinterpret_cast(&mockPlaylist), false), 512); +} + + +TEST(projectMPlaylistAPI, PlayLastException) +{ + PlaylistCWrapperMock mockPlaylist; + + EXPECT_CALL(mockPlaylist, LastPresetIndex()) + .Times(2) + .WillRepeatedly(Throw(ProjectM::Playlist::PlaylistEmptyException())); + + EXPECT_EQ(projectm_playlist_play_last(reinterpret_cast(&mockPlaylist), true), 0); + EXPECT_EQ(projectm_playlist_play_last(reinterpret_cast(&mockPlaylist), false), 0); +} + + +TEST(projectMPlaylistAPI, SetPresetSwitchFailedCallback) +{ + PlaylistCWrapperMock mockPlaylist; + + projectm_playlist_preset_switch_failed_event dummyCallback = [](const char* preset_filename, + const char* message, + void* user_data) {}; + void* dummyData{reinterpret_cast(348564)}; + + EXPECT_CALL(mockPlaylist, SetPresetSwitchFailedCallback(dummyCallback, dummyData)) + .Times(1); + + projectm_playlist_set_preset_switch_failed_event_callback(reinterpret_cast(&mockPlaylist), dummyCallback, dummyData); +} \ No newline at end of file diff --git a/tests/playlist/PlaylistCWrapperMock.h b/tests/playlist/PlaylistCWrapperMock.h index 1c9083166..49aa90bb0 100644 --- a/tests/playlist/PlaylistCWrapperMock.h +++ b/tests/playlist/PlaylistCWrapperMock.h @@ -7,7 +7,8 @@ class PlaylistCWrapperMock : public PlaylistCWrapper { public: - PlaylistCWrapperMock() : PlaylistCWrapper(nullptr) {}; + PlaylistCWrapperMock() + : PlaylistCWrapper(nullptr){}; // PlaylistCWrapper members MOCK_METHOD(void, Connect, (projectm_handle)); @@ -17,14 +18,19 @@ public: MOCK_METHOD(bool, Empty, (), (const)); MOCK_METHOD(void, Clear, ()); MOCK_METHOD(const std::vector&, Items, (), (const)); - MOCK_METHOD(bool, AddItem, (const std::string&, uint32_t, bool)); - MOCK_METHOD(uint32_t, AddPath, (const std::string&, uint32_t, bool, bool)); + MOCK_METHOD(bool, AddItem, (const std::string&, uint32_t, bool) ); + MOCK_METHOD(uint32_t, AddPath, (const std::string&, uint32_t, bool, bool) ); MOCK_METHOD(bool, RemoveItem, (uint32_t)); - MOCK_METHOD(void, SetShuffle, (bool)); + MOCK_METHOD(void, SetShuffle, (bool) ); MOCK_METHOD(void, Sort, (uint32_t, uint32_t, SortPredicate, SortOrder)); MOCK_METHOD(uint32_t, RetryCount, ()); MOCK_METHOD(void, SetRetryCount, (uint32_t)); + MOCK_METHOD(size_t, NextPresetIndex, (), ()); + MOCK_METHOD(size_t, PreviousPresetIndex, (), ()); + MOCK_METHOD(size_t, LastPresetIndex, (), ()); MOCK_METHOD(size_t, PresetIndex, (), (const)); MOCK_METHOD(size_t, SetPresetIndex, (size_t)); - MOCK_METHOD(void, PlayPresetIndex, (size_t, bool, bool)); + MOCK_METHOD(void, PlayPresetIndex, (size_t, bool, bool) ); + MOCK_METHOD(void, RemoveLastHistoryEntry, ()); + MOCK_METHOD(void, SetPresetSwitchFailedCallback, (projectm_playlist_preset_switch_failed_event, void*)); }; diff --git a/tests/playlist/PlaylistTest.cpp b/tests/playlist/PlaylistTest.cpp index f6b7bd110..8d20a29f0 100644 --- a/tests/playlist/PlaylistTest.cpp +++ b/tests/playlist/PlaylistTest.cpp @@ -459,6 +459,101 @@ TEST(projectMPlaylistPlaylist, NextPresetIndexSequential) } +TEST(projectMPlaylistPlaylist, PreviousPresetIndexEmptyPlaylist) +{ + Playlist playlist; + + EXPECT_THROW(playlist.PreviousPresetIndex(), ProjectM::Playlist::PlaylistEmptyException); +} + + +TEST(projectMPlaylistPlaylist, PreviousPresetIndexShuffle) +{ + Playlist playlist; + + playlist.SetShuffle(true); + + EXPECT_TRUE(playlist.AddItem("/some/PresetZ.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/some/PresetA.milk", Playlist::InsertAtEnd, false)); + + // Shuffle 100 times, this will have an (almost) 100% chance that both presets were played. + std::set playlistIndices; + for (int i = 0; i < 100; i++) + { + EXPECT_NO_THROW(playlistIndices.insert(playlist.PreviousPresetIndex())); + } + + EXPECT_TRUE(playlistIndices.find(0) != playlistIndices.end()); + EXPECT_TRUE(playlistIndices.find(1) != playlistIndices.end()); +} + + +TEST(projectMPlaylistPlaylist, PreviousPresetIndexSequential) +{ + Playlist playlist; + + playlist.SetShuffle(false); + + EXPECT_TRUE(playlist.AddItem("/some/PresetZ.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/some/PresetA.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/some/other/PresetC.milk", Playlist::InsertAtEnd, false)); + + EXPECT_EQ(playlist.PreviousPresetIndex(), 2); + EXPECT_EQ(playlist.PreviousPresetIndex(), 1); + EXPECT_EQ(playlist.PreviousPresetIndex(), 0); + EXPECT_EQ(playlist.PreviousPresetIndex(), 2); +} + + +TEST(projectMPlaylistPlaylist, LastPresetIndex) +{ + Playlist playlist; + + playlist.SetShuffle(false); + + EXPECT_TRUE(playlist.AddItem("/some/PresetZ.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/some/PresetA.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/some/other/PresetC.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/the/last/Preset.milk", Playlist::InsertAtEnd, false)); + + EXPECT_EQ(playlist.SetPresetIndex(1), 1); + EXPECT_EQ(playlist.SetPresetIndex(2), 2); + EXPECT_EQ(playlist.SetPresetIndex(1), 1); + EXPECT_EQ(playlist.SetPresetIndex(1), 1); + EXPECT_EQ(playlist.SetPresetIndex(0), 0); + + EXPECT_EQ(playlist.PresetIndex(), 0); + + // Index 1 should only be added once here, even if played twice. + EXPECT_EQ(playlist.LastPresetIndex(), 1); + EXPECT_EQ(playlist.PresetIndex(), 1); + EXPECT_EQ(playlist.LastPresetIndex(), 2); + EXPECT_EQ(playlist.PresetIndex(), 2); + EXPECT_EQ(playlist.LastPresetIndex(), 1); + EXPECT_EQ(playlist.PresetIndex(), 1); + + // Starting index 0 is always be in the history. + EXPECT_EQ(playlist.LastPresetIndex(), 0); + EXPECT_EQ(playlist.PresetIndex(), 0); + + // History empty, wrap back to last item. + EXPECT_EQ(playlist.LastPresetIndex(), 3); + EXPECT_EQ(playlist.PresetIndex(), 3); + + // History should stell be empty, go back one item. + EXPECT_EQ(playlist.LastPresetIndex(), 2); + EXPECT_EQ(playlist.PresetIndex(), 2); +} + + +TEST(projectMPlaylistPlaylist, LastPresetIndexEmptyPlaylist) +{ + Playlist playlist; + + EXPECT_THROW(playlist.LastPresetIndex(), ProjectM::Playlist::PlaylistEmptyException); +} + + TEST(projectMPlaylistPlaylist, SetPresetIndex) { Playlist playlist; @@ -489,7 +584,7 @@ TEST(projectMPlaylistPlaylist, SetPresetIndexException) { Playlist playlist; - EXPECT_THROW(playlist.SetPresetIndex(0), ProjectM::Playlist::PlaylistEmptyException);; + EXPECT_THROW(playlist.SetPresetIndex(0), ProjectM::Playlist::PlaylistEmptyException); } @@ -511,5 +606,24 @@ TEST(projectMPlaylistPlaylist, PresetIndexException) { Playlist playlist; - EXPECT_THROW(playlist.PresetIndex(), ProjectM::Playlist::PlaylistEmptyException);; + EXPECT_THROW(playlist.PresetIndex(), ProjectM::Playlist::PlaylistEmptyException); +} + + +TEST(projectMPlaylistPlaylist, RemoveLastHistoryEntry) +{ + Playlist playlist; + + EXPECT_TRUE(playlist.AddItem("/some/PresetZ.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/some/PresetA.milk", Playlist::InsertAtEnd, false)); + EXPECT_TRUE(playlist.AddItem("/some/other/PresetC.milk", Playlist::InsertAtEnd, false)); + + EXPECT_EQ(playlist.SetPresetIndex(1), 1); + EXPECT_EQ(playlist.SetPresetIndex(2), 2); + + // History: 0,1 + playlist.RemoveLastHistoryEntry(); + // History: 0 + + EXPECT_EQ(playlist.LastPresetIndex(), 0); }