Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renderer: store: remember random play order #139

Merged
merged 2 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/renderer/components/TrackList/TrackList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -141,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);
}
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/components/TrackList/TrackListHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { mapActions } from 'vuex';
export default {
inheritAttrs: false,
props: {
/** @type {Vue.PropOptions<Models.Track[]>} */
tracks: {
type: Array,
required: true
Expand All @@ -32,6 +33,7 @@ export default {
}
},
computed: {
/** @returns {string} */
btnPlayText() {
return `播放全部 (${this.count || this.tracks.length})`;
}
Expand Down
97 changes: 60 additions & 37 deletions src/renderer/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,50 +424,58 @@ 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');
}

/**
* @param {ActionContext} param0
* @param {number} number
*/
export function playNextTrack({ dispatch, getters }) {
export async function playTrackOffset({ commit, dispatch, state, getters }, payload) {
const { index, list, loopMode } = getters.queue;
let nextIndex;
switch (loopMode) {
case LOOP_MODE.RANDOM:
nextIndex = Math.floor(Math.random() * list.length);
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({ dispatch, getters }) {
const { index, list, loopMode } = getters.queue;
let nextIndex;
switch (loopMode) {
case LOOP_MODE.RANDOM:
nextIndex = Math.floor(Math.random() * list.length);
break;
default:
nextIndex = (index + list.length - 1) % list.length;
break;
}
dispatch('playTrackIndex', nextIndex);
export function playNextTrack({ dispatch }) {
dispatch('playTrackOffset', 1);
}

/**
* @param {ActionContext} param0
*/
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, firstIndex = -1 }) {
export async function playPlaylist({ commit, dispatch, state }, { tracks, source, start = -1 }) {
const list = [];
for (const t of tracks) {
if (source) {
Expand All @@ -479,21 +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);
}
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.CLEAR_PLAY_LIST);
dispatch('updateUiTrack');
}

Expand Down Expand Up @@ -523,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
Expand Down Expand Up @@ -600,12 +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_PLAYLIST, index);
commit(types.SET_LOOP_MODE_RANDOM);
break;
case LOOP_MODE.RANDOM:
Expand All @@ -618,23 +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 });
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);
if (playingId !== getters.playing.id) {
if (loopMode === LOOP_MODE.RANDOM) {
commit(types.REMOVE_TRACK_FROM_RANDOM_PLAYLIST, payload);
}
if (index >= payload.start) {
dispatch('updateUiTrack');
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/renderer/store/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

Expand Down
90 changes: 74 additions & 16 deletions src/renderer/store/modules/playlist.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -11,6 +16,9 @@ const state = {
loopMode: LOOP_MODE.LIST,
/** @type {Models.Track[]} */
list: [],
/** @type {number[]} */
randomList: [],
randomIndex: 0
};

/**
Expand All @@ -22,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) {
Expand All @@ -41,27 +52,74 @@ const mutations = {
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;
}
};

Expand Down
6 changes: 5 additions & 1 deletion src/renderer/store/mutation-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +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 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';
Expand Down