From cdbe67b35cf6fbb141523ad4913bb8c28a915468 Mon Sep 17 00:00:00 2001 From: Yuzi0201 <2205816944@qq.com> Date: Sun, 26 Jun 2022 23:41:10 +0800 Subject: [PATCH 1/2] add a queue to manage the random play list --- .../PlayerBar/VirtualCurrentPlaylist.vue | 11 +++- .../components/TrackList/TrackList.vue | 35 ++++++++++++ src/renderer/store/actions.js | 53 ++++++++++++++++--- src/renderer/store/getters.js | 2 +- src/renderer/store/modules/playlist.js | 21 ++++++++ src/renderer/store/mutation-types.js | 3 ++ 6 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue b/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue index c69f845f..ed1400bb 100644 --- a/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue +++ b/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue @@ -84,6 +84,7 @@ import { mapActions } from 'vuex'; import { workerExecute } from '@/worker/message'; import CenteredTip from '@/components/CenteredTip.vue'; +import { LOOP_MODE } from '@/store/modules/playlist'; const SourceName = { list: '歌单', @@ -136,7 +137,9 @@ export default { if (this.ui.radioMode) return '私人 FM'; if (this.showFindInput && this.findInput) return `找到 ${this.filteredList.length} 首`; return `共 ${this.queue.list.length} 首`; - } + }, + /** @returns {import('@/store/modules/playlist').State} */ + playlist() { return this.$store.state.playlist; }, }, methods: { ...mapActions([ @@ -145,7 +148,8 @@ export default { 'clearPlaylist', 'playTrackIndex', 'toggleCollectPopup', - 'removeTrackFromPlaylist' + 'removeTrackFromPlaylist', + 'insertTrackIntoRandomPlaylist', ]), toggleFindInput() { this.showFindInput = !this.showFindInput; @@ -180,6 +184,9 @@ export default { if (this.indexMap.size > 0 && this.indexMap.has(index)) { i = this.indexMap.get(index); } + if (this.playlist.loopMode == LOOP_MODE.RANDOM) { + this.insertTrackIntoRandomPlaylist({ index: i, offset: 0 }); + } this.playTrackIndex(i); }, sourceTipText(track) { diff --git a/src/renderer/components/TrackList/TrackList.vue b/src/renderer/components/TrackList/TrackList.vue index 28cf263d..31f98ef5 100644 --- a/src/renderer/components/TrackList/TrackList.vue +++ b/src/renderer/components/TrackList/TrackList.vue @@ -23,6 +23,7 @@ import { mapActions } from 'vuex'; import TrackItem from './TrackItem.vue'; import CenteredTip from '@/components/CenteredTip.vue'; +import { LOOP_MODE } from '@/store/modules/playlist'; /** @typedef {{ event: string, icon: string, title: string }} TrackListShortcut */ @@ -82,6 +83,8 @@ export default { 'playTrackIndex', 'toggleCollectPopup', 'insertTrackIntoPlaylist', + 'insertTrackIntoRandomPlaylist', + 'generateRandomHeardList', 'playPlaylist' ]), handleCollect(id) { @@ -103,6 +106,11 @@ export default { queueTrack(index) { if (this.findTrackInPlaylist(index) > -1) { // track exists in playlist + if (this.playlist.loopMode == LOOP_MODE.RANDOM) { + this.insertTrackIntoRandomPlaylist({index,offset: 1}); + this.$toast.message('已添加下一首播放 _(:з」∠)_'); + return; + } this.$toast.message('已经在播放列表中了 ( >﹏<。)~'); return; } @@ -111,6 +119,13 @@ export default { source: this.source, index: this.playlist.index + 1 }); + if (this.playlist.loopMode == LOOP_MODE.RANDOM) { + if (this.playlist.randomHeardList.length == 0) {//若列表为空,直接生成 + this.generateRandomHeardList(this.playlist.list.length); + } + else //否则插入 + this.insertTrackIntoRandomPlaylist({ index:this.playlist.index + 1 ,offset:1}); + } this.$toast.message('已添加下一首播放 _(:з」∠)_'); }, handleQueue(index) { @@ -122,6 +137,26 @@ export default { }, playTrack(index) { const i = this.findTrackInPlaylist(index); + if (this.playlist.loopMode == LOOP_MODE.RANDOM) { + if (i > -1) { + // track exists in playlist + this.insertTrackIntoRandomPlaylist({index: i,offset: 0}); + this.playTrackIndex(i); + return; + } + if (this.playlist.randomHeardList.length == 0) { + this.generateRandomHeardList(this.playlist.list.length); + } + this.insertTrackIntoPlaylist({ + tracks: [this.trackDetails[index]], + source: this.source, + index: this.playlist.index, + }); + const newIndex = this.findTrackInPlaylist(index); + this.insertTrackIntoRandomPlaylist({ index:newIndex, offset: 0 }); + this.playTrackIndex(newIndex); + return; + } if (i > -1) { // track exists in playlist this.playTrackIndex(i); diff --git a/src/renderer/store/actions.js b/src/renderer/store/actions.js index c37a6718..e028e702 100644 --- a/src/renderer/store/actions.js +++ b/src/renderer/store/actions.js @@ -433,12 +433,23 @@ export async function playTrackIndex({ state, commit, dispatch }, index) { /** * @param {ActionContext} param0 */ -export function playNextTrack({ dispatch, getters }) { - const { index, list, loopMode } = getters.queue; +export function playNextTrack({ commit, dispatch, getters }) { + let { index, list, loopMode, randomHeardList, randomHeardListPointer } = getters.queue; let nextIndex; switch (loopMode) { case LOOP_MODE.RANDOM: - nextIndex = Math.floor(Math.random() * list.length); + if (randomHeardList.length == 0) { + commit(types.GENERATE_RANDOM_HEARD_LIST, list.length); + randomHeardList = getters.queue.randomHeardList; + } + if (randomHeardListPointer == randomHeardList.length - 1) { + commit(types.SET_RANDOMLIST_POINTER, 0); + nextIndex = randomHeardList[0]; + } + else { + nextIndex = randomHeardList[randomHeardListPointer + 1]; + commit(types.SET_RANDOMLIST_POINTER, randomHeardListPointer + 1); + } break; default: nextIndex = (index + 1) % list.length; @@ -450,12 +461,23 @@ export function playNextTrack({ dispatch, getters }) { /** * @param {ActionContext} param0 */ -export function playPreviousTrack({ dispatch, getters }) { - const { index, list, loopMode } = getters.queue; +export function playPreviousTrack({ commit, dispatch, getters }) { + let { index, list, loopMode, randomHeardList, randomHeardListPointer } = getters.queue; let nextIndex; switch (loopMode) { case LOOP_MODE.RANDOM: - nextIndex = Math.floor(Math.random() * list.length); + if (randomHeardList.length == 0) { + commit(types.GENERATE_RANDOM_HEARD_LIST, list.length); + randomHeardList = getters.queue.randomHeardList; + } + if (randomHeardListPointer == 0) { + commit(types.SET_RANDOMLIST_POINTER, randomHeardList.length - 1); + nextIndex = randomHeardList[randomHeardList.length - 1]; + } + else { + nextIndex = randomHeardList[randomHeardListPointer - 1]; + commit(types.SET_RANDOMLIST_POINTER, randomHeardListPointer - 1); + } break; default: nextIndex = (index + list.length - 1) % list.length; @@ -480,7 +502,9 @@ export async function playPlaylist({ commit, dispatch, state }, { tracks, source commit(types.ACTIVATE_RADIO, false); } if (firstIndex === -1 && state.playlist.loopMode === LOOP_MODE.RANDOM) { - firstIndex = Math.floor(Math.random() * list.length); + //firstIndex = Math.floor(Math.random() * list.length); + commit(types.GENERATE_RANDOM_HEARD_LIST, list.length);//换歌单了,重置随机队列 + firstIndex = state.playlist.randomHeardList[0]; } if (firstIndex === -1) { firstIndex = 0; @@ -494,6 +518,7 @@ export async function playPlaylist({ commit, dispatch, state }, { tracks, source export function clearPlaylist({ commit, dispatch }) { commit(types.SET_PLAY_LIST, []); commit(types.SET_CURRENT_INDEX, 0); + commit(types.GENERATE_RANDOM_HEARD_LIST, 0); dispatch('updateUiTrack'); } @@ -606,6 +631,7 @@ export function nextLoopMode({ commit, state }) { commit(types.SET_LOOP_MODE_SINGLE); break; case LOOP_MODE.SINGLE: + commit(types.GENERATE_RANDOM_HEARD_LIST, state.playlist.list.length);//换播放方式了,重置随机队列 commit(types.SET_LOOP_MODE_RANDOM); break; case LOOP_MODE.RANDOM: @@ -628,12 +654,25 @@ export function insertTrackIntoPlaylist({ commit, state }, payload) { commit(types.INSERT_TRACK_INTO_PLAYLIST, { tracks: payload.tracks, index }); } +/** + * @param {ActionContext} param0 + * @param {number} payload + */ +export function insertTrackIntoRandomPlaylist({ commit, state }, payload) { + commit(types.INSERT_TRACK_INTO_RANDOM_PLAYLIST, payload); +} + +export function generateRandomHeardList({ commit, state }, payload) { + commit(types.GENERATE_RANDOM_HEARD_LIST, payload); +} + /** * @param {ActionContext} param0 */ export function removeTrackFromPlaylist({ getters, commit, dispatch }, payload) { const playingId = getters.playing.id; commit(types.REMOVE_TRACK_FROM_PLAYLIST, payload); + commit(types.GENERATE_RANDOM_HEARD_LIST, getters.queue.list.length);//删歌了,重置随机队列 if (playingId !== getters.playing.id) { dispatch('updateUiTrack'); } diff --git a/src/renderer/store/getters.js b/src/renderer/store/getters.js index e441d489..b10b5fe7 100644 --- a/src/renderer/store/getters.js +++ b/src/renderer/store/getters.js @@ -5,7 +5,7 @@ /** * @param {State} state - * @typedef {{ index: number, loopMode: number, list: Models.Track[] }} QueueGetter + * @typedef {{ index: number, loopMode: number, list: Models.Track[], randomHeardList: number[], randomHeardListPointer: number }} QueueGetter * @returns {QueueGetter} */ export function queue(state) { diff --git a/src/renderer/store/modules/playlist.js b/src/renderer/store/modules/playlist.js index 7a558c00..b62571b5 100644 --- a/src/renderer/store/modules/playlist.js +++ b/src/renderer/store/modules/playlist.js @@ -11,6 +11,9 @@ const state = { loopMode: LOOP_MODE.LIST, /** @type {Models.Track[]} */ list: [], + /** @type {[number]} */ + randomHeardList: [], + randomHeardListPointer: 0, }; /** @@ -36,6 +39,24 @@ const mutations = { [types.SET_LOOP_MODE_RANDOM](state) { state.loopMode = LOOP_MODE.RANDOM; }, + [types.GENERATE_RANDOM_HEARD_LIST](state, payload) { + state.randomHeardList = Array(payload).fill(1).map((v, i) => i); + for (let i = state.randomHeardList.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [state.randomHeardList[i], state.randomHeardList[j]] = [state.randomHeardList[j], state.randomHeardList[i]]; + }//生成一个0-(length-1)的乱序不重复随机数组 + state.randomHeardListPointer = 0;//初始化指针 + }, + [types.SET_RANDOMLIST_POINTER](state, payload) { + state.randomHeardListPointer = payload; + }, + [types.INSERT_TRACK_INTO_RANDOM_PLAYLIST](state, { index, offset }) { + state.randomHeardList.splice(state.randomHeardListPointer + offset, 0, index) + for (let i = 0; i < state.randomHeardList.length; i++) + if (state.randomHeardList[i] >= index) + if (i != state.randomHeardListPointer + offset) + state.randomHeardList[i]++; + }, [types.RESTORE_PLAYLIST](state, { index, loopMode, list }) { state.index = index || 0; state.loopMode = loopMode || LOOP_MODE.LIST; diff --git a/src/renderer/store/mutation-types.js b/src/renderer/store/mutation-types.js index d567c8cb..1ee027ba 100644 --- a/src/renderer/store/mutation-types.js +++ b/src/renderer/store/mutation-types.js @@ -46,6 +46,9 @@ export const SET_CURRENT_INDEX = 'SET_CURRENT_INDEX'; export const SET_LOOP_MODE_LIST = 'SET_LOOP_MODE_LOOP'; export const SET_LOOP_MODE_SINGLE = 'SET_LOOP_MODE_SINGLE'; export const SET_LOOP_MODE_RANDOM = 'SET_LOOP_MODE_RANDOM'; +export const GENERATE_RANDOM_HEARD_LIST = 'GENERATE_RANDOM_HEARD_LIST'; +export const SET_RANDOMLIST_POINTER = 'SET_RANDOMLIST_POINTER' +export const INSERT_TRACK_INTO_RANDOM_PLAYLIST = 'INSERT_TRACK_INTO_RANDOM_PLAYLIST'; export const RESTORE_PLAYLIST = 'RESTORE_PLAYLIST'; export const INSERT_TRACK_INTO_PLAYLIST = 'INSERT_TRACK_INTO_PLAYLIST'; export const REMOVE_TRACK_FROM_PLAYLIST = 'REMOVE_TRACK_FROM_PLAYLIST'; From c01a67cb3b8f790b4ecd1c03151c0ab8d21d634e Mon Sep 17 00:00:00 2001 From: Rocka Date: Mon, 10 Jul 2023 14:38:03 +0800 Subject: [PATCH 2/2] renderer: store: remember random play order --- .../PlayerBar/VirtualCurrentPlaylist.vue | 11 +- .../components/TrackList/TrackList.vue | 37 +---- .../components/TrackList/TrackListHeader.vue | 2 + src/renderer/store/actions.js | 138 ++++++++---------- src/renderer/store/getters.js | 2 +- src/renderer/store/hooks.js | 10 +- src/renderer/store/modules/playlist.js | 111 +++++++++----- src/renderer/store/mutation-types.js | 9 +- 8 files changed, 152 insertions(+), 168 deletions(-) diff --git a/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue b/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue index ed1400bb..c69f845f 100644 --- a/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue +++ b/src/renderer/components/PlayerBar/VirtualCurrentPlaylist.vue @@ -84,7 +84,6 @@ import { mapActions } from 'vuex'; import { workerExecute } from '@/worker/message'; import CenteredTip from '@/components/CenteredTip.vue'; -import { LOOP_MODE } from '@/store/modules/playlist'; const SourceName = { list: '歌单', @@ -137,9 +136,7 @@ export default { if (this.ui.radioMode) return '私人 FM'; if (this.showFindInput && this.findInput) return `找到 ${this.filteredList.length} 首`; return `共 ${this.queue.list.length} 首`; - }, - /** @returns {import('@/store/modules/playlist').State} */ - playlist() { return this.$store.state.playlist; }, + } }, methods: { ...mapActions([ @@ -148,8 +145,7 @@ export default { 'clearPlaylist', 'playTrackIndex', 'toggleCollectPopup', - 'removeTrackFromPlaylist', - 'insertTrackIntoRandomPlaylist', + 'removeTrackFromPlaylist' ]), toggleFindInput() { this.showFindInput = !this.showFindInput; @@ -184,9 +180,6 @@ export default { if (this.indexMap.size > 0 && this.indexMap.has(index)) { i = this.indexMap.get(index); } - if (this.playlist.loopMode == LOOP_MODE.RANDOM) { - this.insertTrackIntoRandomPlaylist({ index: i, offset: 0 }); - } this.playTrackIndex(i); }, sourceTipText(track) { diff --git a/src/renderer/components/TrackList/TrackList.vue b/src/renderer/components/TrackList/TrackList.vue index 31f98ef5..2b166313 100644 --- a/src/renderer/components/TrackList/TrackList.vue +++ b/src/renderer/components/TrackList/TrackList.vue @@ -23,7 +23,6 @@ import { mapActions } from 'vuex'; import TrackItem from './TrackItem.vue'; import CenteredTip from '@/components/CenteredTip.vue'; -import { LOOP_MODE } from '@/store/modules/playlist'; /** @typedef {{ event: string, icon: string, title: string }} TrackListShortcut */ @@ -83,8 +82,6 @@ export default { 'playTrackIndex', 'toggleCollectPopup', 'insertTrackIntoPlaylist', - 'insertTrackIntoRandomPlaylist', - 'generateRandomHeardList', 'playPlaylist' ]), handleCollect(id) { @@ -106,11 +103,6 @@ export default { queueTrack(index) { if (this.findTrackInPlaylist(index) > -1) { // track exists in playlist - if (this.playlist.loopMode == LOOP_MODE.RANDOM) { - this.insertTrackIntoRandomPlaylist({index,offset: 1}); - this.$toast.message('已添加下一首播放 _(:з」∠)_'); - return; - } this.$toast.message('已经在播放列表中了 ( >﹏<。)~'); return; } @@ -119,13 +111,6 @@ export default { source: this.source, index: this.playlist.index + 1 }); - if (this.playlist.loopMode == LOOP_MODE.RANDOM) { - if (this.playlist.randomHeardList.length == 0) {//若列表为空,直接生成 - this.generateRandomHeardList(this.playlist.list.length); - } - else //否则插入 - this.insertTrackIntoRandomPlaylist({ index:this.playlist.index + 1 ,offset:1}); - } this.$toast.message('已添加下一首播放 _(:з」∠)_'); }, handleQueue(index) { @@ -137,26 +122,6 @@ export default { }, playTrack(index) { const i = this.findTrackInPlaylist(index); - if (this.playlist.loopMode == LOOP_MODE.RANDOM) { - if (i > -1) { - // track exists in playlist - this.insertTrackIntoRandomPlaylist({index: i,offset: 0}); - this.playTrackIndex(i); - return; - } - if (this.playlist.randomHeardList.length == 0) { - this.generateRandomHeardList(this.playlist.list.length); - } - this.insertTrackIntoPlaylist({ - tracks: [this.trackDetails[index]], - source: this.source, - index: this.playlist.index, - }); - const newIndex = this.findTrackInPlaylist(index); - this.insertTrackIntoRandomPlaylist({ index:newIndex, offset: 0 }); - this.playTrackIndex(newIndex); - return; - } if (i > -1) { // track exists in playlist this.playTrackIndex(i); @@ -176,7 +141,7 @@ export default { this.activateRadio(false); } if (this.settings.autoReplacePlaylist) { - this.playPlaylist({ tracks: this.trackDetails, source: this.source, firstIndex: index }); + this.playPlaylist({ tracks: this.trackDetails, source: this.source, start: index }); } else { this.playTrack(index); } diff --git a/src/renderer/components/TrackList/TrackListHeader.vue b/src/renderer/components/TrackList/TrackListHeader.vue index 541ff4a8..53829ead 100644 --- a/src/renderer/components/TrackList/TrackListHeader.vue +++ b/src/renderer/components/TrackList/TrackListHeader.vue @@ -20,6 +20,7 @@ import { mapActions } from 'vuex'; export default { inheritAttrs: false, props: { + /** @type {Vue.PropOptions} */ tracks: { type: Array, required: true @@ -32,6 +33,7 @@ export default { } }, computed: { + /** @returns {string} */ btnPlayText() { return `播放全部 (${this.count || this.tracks.length})`; } diff --git a/src/renderer/store/actions.js b/src/renderer/store/actions.js index e028e702..92236c5d 100644 --- a/src/renderer/store/actions.js +++ b/src/renderer/store/actions.js @@ -424,7 +424,10 @@ export async function playTrackIndex({ state, commit, dispatch }, index) { if (state.ui.radioMode === true) { commit(types.SET_RADIO_INDEX, index); } else { - commit(types.SET_CURRENT_INDEX, index); + commit(types.SET_PLAYLIST_INDEX, index); + if (state.playlist.loopMode === LOOP_MODE.RANDOM) { + commit(types.GENERATE_RANDOM_PLAYLIST, index); + } } await dispatch('updateUiTrack'); dispatch('playAudio'); @@ -432,64 +435,47 @@ export async function playTrackIndex({ state, commit, dispatch }, index) { /** * @param {ActionContext} param0 + * @param {number} number */ -export function playNextTrack({ commit, dispatch, getters }) { - let { index, list, loopMode, randomHeardList, randomHeardListPointer } = getters.queue; +export async function playTrackOffset({ commit, dispatch, state, getters }, payload) { + const { index, list, loopMode } = getters.queue; let nextIndex; - switch (loopMode) { - case LOOP_MODE.RANDOM: - if (randomHeardList.length == 0) { - commit(types.GENERATE_RANDOM_HEARD_LIST, list.length); - randomHeardList = getters.queue.randomHeardList; - } - if (randomHeardListPointer == randomHeardList.length - 1) { - commit(types.SET_RANDOMLIST_POINTER, 0); - nextIndex = randomHeardList[0]; - } - else { - nextIndex = randomHeardList[randomHeardListPointer + 1]; - commit(types.SET_RANDOMLIST_POINTER, randomHeardListPointer + 1); - } - break; - default: - nextIndex = (index + 1) % list.length; - break; + if (loopMode === LOOP_MODE.RANDOM) { + const { randomIndex, randomList } = state.playlist; + const nextRandomIndex = (randomIndex + randomList.length + payload) % randomList.length; + commit(types.SET_RANDOM_PLAYLIST_INDEX, nextRandomIndex); + nextIndex = randomList[nextRandomIndex]; + } else { + nextIndex = (index + list.length + payload) % list.length; } - dispatch('playTrackIndex', nextIndex); + if (state.ui.radioMode === true) { + commit(types.SET_RADIO_INDEX, nextIndex); + } else { + commit(types.SET_PLAYLIST_INDEX, nextIndex); + } + await dispatch('updateUiTrack'); + dispatch('playAudio'); } /** * @param {ActionContext} param0 */ -export function playPreviousTrack({ commit, dispatch, getters }) { - let { index, list, loopMode, randomHeardList, randomHeardListPointer } = getters.queue; - let nextIndex; - switch (loopMode) { - case LOOP_MODE.RANDOM: - if (randomHeardList.length == 0) { - commit(types.GENERATE_RANDOM_HEARD_LIST, list.length); - randomHeardList = getters.queue.randomHeardList; - } - if (randomHeardListPointer == 0) { - commit(types.SET_RANDOMLIST_POINTER, randomHeardList.length - 1); - nextIndex = randomHeardList[randomHeardList.length - 1]; - } - else { - nextIndex = randomHeardList[randomHeardListPointer - 1]; - commit(types.SET_RANDOMLIST_POINTER, randomHeardListPointer - 1); - } - break; - default: - nextIndex = (index + list.length - 1) % list.length; - break; - } - dispatch('playTrackIndex', nextIndex); +export function playNextTrack({ dispatch }) { + dispatch('playTrackOffset', 1); } /** * @param {ActionContext} param0 */ -export async function playPlaylist({ commit, dispatch, state }, { tracks, source, firstIndex = -1 }) { +export function playPreviousTrack({ dispatch }) { + dispatch('playTrackOffset', -1); +} + +/** + * @param {ActionContext} param0 + * @param {{ tracks: Models.Track[], source?: any, start?: number }} + */ +export async function playPlaylist({ commit, dispatch, state }, { tracks, source, start = -1 }) { const list = []; for (const t of tracks) { if (source) { @@ -501,24 +487,25 @@ export async function playPlaylist({ commit, dispatch, state }, { tracks, source if (state.ui.radioMode === true) { commit(types.ACTIVATE_RADIO, false); } - if (firstIndex === -1 && state.playlist.loopMode === LOOP_MODE.RANDOM) { - //firstIndex = Math.floor(Math.random() * list.length); - commit(types.GENERATE_RANDOM_HEARD_LIST, list.length);//换歌单了,重置随机队列 - firstIndex = state.playlist.randomHeardList[0]; - } - if (firstIndex === -1) { - firstIndex = 0; + let nextIndex; + if (start === -1) { + if (state.playlist.loopMode === LOOP_MODE.RANDOM) { + nextIndex = Math.floor(Math.random() * list.length); + commit(types.GENERATE_RANDOM_PLAYLIST, nextIndex); + } else { + nextIndex = 0; + } + } else { + nextIndex = start; } - dispatch('playTrackIndex', firstIndex); + dispatch('playTrackIndex', nextIndex); } /** * @param {ActionContext} context */ export function clearPlaylist({ commit, dispatch }) { - commit(types.SET_PLAY_LIST, []); - commit(types.SET_CURRENT_INDEX, 0); - commit(types.GENERATE_RANDOM_HEARD_LIST, 0); + commit(types.CLEAR_PLAY_LIST); dispatch('updateUiTrack'); } @@ -548,6 +535,9 @@ export async function restorePlaylist({ commit }) { } } commit(types.RESTORE_PLAYLIST, { index, list, loopMode }); + if (loopMode === LOOP_MODE.RANDOM) { + commit(types.GENERATE_RANDOM_PLAYLIST, index); + } } } catch (e) { console.error('restorePlaylist failed:', e); // eslint-disable-line no-console @@ -625,13 +615,13 @@ export async function checkDownloaded({ commit }, { metadata }) { * @param {ActionContext} param0 */ export function nextLoopMode({ commit, state }) { - const { loopMode } = state.playlist; + const { index, loopMode } = state.playlist; switch (loopMode) { case LOOP_MODE.LIST: commit(types.SET_LOOP_MODE_SINGLE); break; case LOOP_MODE.SINGLE: - commit(types.GENERATE_RANDOM_HEARD_LIST, state.playlist.list.length);//换播放方式了,重置随机队列 + commit(types.GENERATE_RANDOM_PLAYLIST, index); commit(types.SET_LOOP_MODE_RANDOM); break; case LOOP_MODE.RANDOM: @@ -644,36 +634,30 @@ export function nextLoopMode({ commit, state }) { * @param {ActionContext} param0 * @param {{ tracks: Models.Track[]; source?: any; index?: number }} payload */ -export function insertTrackIntoPlaylist({ commit, state }, payload) { +export function insertTrackIntoPlaylist({ commit, state, getters }, payload) { if (payload.source) { for (const t of payload.tracks) { t.source = payload.source; } } - const index = payload.index || state.playlist.index; - commit(types.INSERT_TRACK_INTO_PLAYLIST, { tracks: payload.tracks, index }); -} - -/** - * @param {ActionContext} param0 - * @param {number} payload - */ -export function insertTrackIntoRandomPlaylist({ commit, state }, payload) { - commit(types.INSERT_TRACK_INTO_RANDOM_PLAYLIST, payload); -} - -export function generateRandomHeardList({ commit, state }, payload) { - commit(types.GENERATE_RANDOM_HEARD_LIST, payload); + const start = payload.index || state.playlist.index; + commit(types.INSERT_TRACK_INTO_PLAYLIST, { tracks: payload.tracks, start }); + if (getters.queue.loopMode === LOOP_MODE.RANDOM) { + commit(types.INSERT_TRACK_INTO_RANDOM_PLAYLIST, { start, count: payload.tracks.length }); + } } /** * @param {ActionContext} param0 + * @param {{ start: number; count: number }} payload */ export function removeTrackFromPlaylist({ getters, commit, dispatch }, payload) { - const playingId = getters.playing.id; + const { index, loopMode } = getters.queue; commit(types.REMOVE_TRACK_FROM_PLAYLIST, payload); - commit(types.GENERATE_RANDOM_HEARD_LIST, getters.queue.list.length);//删歌了,重置随机队列 - if (playingId !== getters.playing.id) { + if (loopMode === LOOP_MODE.RANDOM) { + commit(types.REMOVE_TRACK_FROM_RANDOM_PLAYLIST, payload); + } + if (index >= payload.start) { dispatch('updateUiTrack'); } } diff --git a/src/renderer/store/getters.js b/src/renderer/store/getters.js index b10b5fe7..e441d489 100644 --- a/src/renderer/store/getters.js +++ b/src/renderer/store/getters.js @@ -5,7 +5,7 @@ /** * @param {State} state - * @typedef {{ index: number, loopMode: number, list: Models.Track[], randomHeardList: number[], randomHeardListPointer: number }} QueueGetter + * @typedef {{ index: number, loopMode: number, list: Models.Track[] }} QueueGetter * @returns {QueueGetter} */ export function queue(state) { diff --git a/src/renderer/store/hooks.js b/src/renderer/store/hooks.js index 8807d908..395347d8 100644 --- a/src/renderer/store/hooks.js +++ b/src/renderer/store/hooks.js @@ -77,14 +77,16 @@ function updatePlaylistTable(mutation) { case SET_PLAY_LIST: PlaylistDb.replace(mutation.payload); break; - case INSERT_TRACK_INTO_PLAYLIST: - const { index, tracks } = mutation.payload; - PlaylistDb.insert(index, tracks); + case INSERT_TRACK_INTO_PLAYLIST: { + const { start, tracks } = mutation.payload; + PlaylistDb.insert(start, tracks); break; - case REMOVE_TRACK_FROM_PLAYLIST: + } + case REMOVE_TRACK_FROM_PLAYLIST: { const { start, count } = mutation.payload; PlaylistDb.remove(start, count); break; + } } } diff --git a/src/renderer/store/modules/playlist.js b/src/renderer/store/modules/playlist.js index b62571b5..fc2d085a 100644 --- a/src/renderer/store/modules/playlist.js +++ b/src/renderer/store/modules/playlist.js @@ -1,3 +1,8 @@ +// @ts-check + +import range from 'lodash/range'; +import shuffle from 'lodash/shuffle'; + import * as types from '../mutation-types'; export const LOOP_MODE = { @@ -11,9 +16,9 @@ const state = { loopMode: LOOP_MODE.LIST, /** @type {Models.Track[]} */ list: [], - /** @type {[number]} */ - randomHeardList: [], - randomHeardListPointer: 0, + /** @type {number[]} */ + randomList: [], + randomIndex: 0 }; /** @@ -25,9 +30,12 @@ const mutations = { state.list = payload; }, [types.CLEAR_PLAY_LIST](state) { + state.index = 0; state.list = []; + state.randomIndex = 0; + state.randomList = []; }, - [types.SET_CURRENT_INDEX](state, payload) { + [types.SET_PLAYLIST_INDEX](state, payload) { state.index = payload; }, [types.SET_LOOP_MODE_LIST](state) { @@ -39,50 +47,79 @@ const mutations = { [types.SET_LOOP_MODE_RANDOM](state) { state.loopMode = LOOP_MODE.RANDOM; }, - [types.GENERATE_RANDOM_HEARD_LIST](state, payload) { - state.randomHeardList = Array(payload).fill(1).map((v, i) => i); - for (let i = state.randomHeardList.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [state.randomHeardList[i], state.randomHeardList[j]] = [state.randomHeardList[j], state.randomHeardList[i]]; - }//生成一个0-(length-1)的乱序不重复随机数组 - state.randomHeardListPointer = 0;//初始化指针 - }, - [types.SET_RANDOMLIST_POINTER](state, payload) { - state.randomHeardListPointer = payload; - }, - [types.INSERT_TRACK_INTO_RANDOM_PLAYLIST](state, { index, offset }) { - state.randomHeardList.splice(state.randomHeardListPointer + offset, 0, index) - for (let i = 0; i < state.randomHeardList.length; i++) - if (state.randomHeardList[i] >= index) - if (i != state.randomHeardListPointer + offset) - state.randomHeardList[i]++; - }, [types.RESTORE_PLAYLIST](state, { index, loopMode, list }) { state.index = index || 0; state.loopMode = loopMode || LOOP_MODE.LIST; state.list = list; }, - [types.INSERT_TRACK_INTO_PLAYLIST](state, { tracks, index }) { - state.list.splice(index, 0, ...tracks); - if (index <= state.index) { + [types.INSERT_TRACK_INTO_PLAYLIST](state, /** @type {{ start: number, tracks: Models.Track[] }} */ { start, tracks }) { + state.list.splice(start, 0, ...tracks); + if (start <= state.index) { // keep current track unchanged state.index += tracks.length; } }, - [types.REMOVE_TRACK_FROM_PLAYLIST](state, { start, count }) { - state.list.splice(start, count); - const end = start + count - 1; - if (start > state.index) return; - if (end < state.index) { - state.index -= count; - return; - } - // start <= index && end >= index - if (start < state.list.length) { - state.index = start; + [types.REMOVE_TRACK_FROM_PLAYLIST](state, /** @type {{ start: number, count: number }} */ { start, count }) { + const { index, list } = state; + const removed = list.splice(start, count).length; + if (index < start) { + // nothing + } else if (index >= start + removed) { + state.index -= removed; } else { - state.index = state.list.length - 1; + state.index = (start < list.length) ? start : list.length - 1; + } + }, + [types.GENERATE_RANDOM_PLAYLIST](state, /** @type {number} */ payload = 0) { + const arr = shuffle(range(state.list.length)); + state.randomIndex = arr.findIndex(v => v === payload); + state.randomList = arr; + }, + [types.SET_RANDOM_PLAYLIST_INDEX](state, /** @type {number} */ payload) { + state.randomIndex = payload; + }, + // eslint-disable-next-line no-unused-vars + [types.INSERT_TRACK_INTO_RANDOM_PLAYLIST](state, /** @type {{ start: number, count: number }} */ { start, count }) { + const { index, randomIndex, randomList } = state; + // queued track(s) should be played right after current track. + // so it's index in playlist (`index + 1`) should be instered in `randomList` after `randomIndex` + const insertPosition = randomIndex; + const seq = range(index + 1, index + 1 + count); + randomList.splice(insertPosition + 1, 0, ...seq); + const total = randomList.length; + for (let i = 0; i < total; i++) { + if (randomList[i] >= seq[0]) { + if (i <= insertPosition || insertPosition + count < i) { + randomList[i] += count; + } + } + } + }, + [types.REMOVE_TRACK_FROM_RANDOM_PLAYLIST](state, /** @type {{ start: number, count: number }} */ { start, count }) { + const { randomIndex, randomList } = state; + const kept = []; + const removed2 = []; + let offset = 0; + const total = randomList.length; + for (let i = 0; i < total; i++) { + const r = randomList[i]; + // filter out tracks in "removed" range + if (r < start || start + count <= r) { + kept.push(r); + } else { + // for each track removed before randomIndex, + // it shoud decrease by 1 + if (i < randomIndex) offset++; + removed2.push(r); + } + } + for (let i = 0; i < kept.length; i++) { + const k = kept[i]; + // every kept value shoud decrease by count of removed values that less than it + kept[i] -= removed2.filter(r => r < k).length; } + state.randomList = kept; + state.randomIndex = randomIndex - offset; } }; diff --git a/src/renderer/store/mutation-types.js b/src/renderer/store/mutation-types.js index 1ee027ba..ae305032 100644 --- a/src/renderer/store/mutation-types.js +++ b/src/renderer/store/mutation-types.js @@ -42,16 +42,17 @@ export const SET_COLLECT_TRACKS = 'SET_COLLECT_TRACKS'; // Playlist export const SET_PLAY_LIST = 'SET_PLAY_LIST'; export const CLEAR_PLAY_LIST = 'CLEAR_PLAY_LIST'; -export const SET_CURRENT_INDEX = 'SET_CURRENT_INDEX'; +export const SET_PLAYLIST_INDEX = 'SET_PLAYLIST_INDEX'; export const SET_LOOP_MODE_LIST = 'SET_LOOP_MODE_LOOP'; export const SET_LOOP_MODE_SINGLE = 'SET_LOOP_MODE_SINGLE'; export const SET_LOOP_MODE_RANDOM = 'SET_LOOP_MODE_RANDOM'; -export const GENERATE_RANDOM_HEARD_LIST = 'GENERATE_RANDOM_HEARD_LIST'; -export const SET_RANDOMLIST_POINTER = 'SET_RANDOMLIST_POINTER' -export const INSERT_TRACK_INTO_RANDOM_PLAYLIST = 'INSERT_TRACK_INTO_RANDOM_PLAYLIST'; export const RESTORE_PLAYLIST = 'RESTORE_PLAYLIST'; export const INSERT_TRACK_INTO_PLAYLIST = 'INSERT_TRACK_INTO_PLAYLIST'; export const REMOVE_TRACK_FROM_PLAYLIST = 'REMOVE_TRACK_FROM_PLAYLIST'; +export const GENERATE_RANDOM_PLAYLIST = 'GENERATE_RANDOM_PLAYLIST'; +export const SET_RANDOM_PLAYLIST_INDEX = 'SET_RANDOM_PLAYLIST_INDEX'; +export const INSERT_TRACK_INTO_RANDOM_PLAYLIST = 'INSERT_TRACK_INTO_RANDOM_PLAYLIST'; +export const REMOVE_TRACK_FROM_RANDOM_PLAYLIST = 'REMOVE_TRACK_FROM_RANDOM_PLAYLIST'; // Settings export const UPDATE_SETTINGS = 'UPDATE_SETTINGS';