diff --git a/resources/assets/js/__tests__/UnitTestCase.ts b/resources/assets/js/__tests__/UnitTestCase.ts index 10dd15bc..e8c22a53 100644 --- a/resources/assets/js/__tests__/UnitTestCase.ts +++ b/resources/assets/js/__tests__/UnitTestCase.ts @@ -103,8 +103,11 @@ export default abstract class UnitTestCase { } protected setReadOnlyProperty (obj: T, prop: keyof T, value: any) { - return Object.defineProperty(obj, prop, { - get: () => value + return Object.defineProperties(obj, { + [prop]: { + value, + configurable: true + } }) } diff --git a/resources/assets/js/__tests__/blobs/data.ts b/resources/assets/js/__tests__/blobs/data.ts deleted file mode 100644 index 12003d9d..00000000 --- a/resources/assets/js/__tests__/blobs/data.ts +++ /dev/null @@ -1,221 +0,0 @@ -import factory from '@/__tests__/factory' - -const currentUser = factory('user', { - id: 1, - name: 'Phan An', - email: 'me@phanan.net', - is_admin: true -}) - -const unknownArtist = factory('artist', { id: 1, name: 'Unknown Artist' }) -const variousArtist = factory('artist', { id: 2, name: 'Various Artist' }) -const all4One = factory('artist', { id: 3, name: 'All-4-One' }) -const bobDylan = factory('artist', { id: 4, name: 'Bob Dylan' }) -const jamesBlunt = factory('artist', { id: 5, name: 'James Blunt' }) - -const all4OneAlbum = factory('album', { - id: 1193, - artist_id: 3, - name: 'All-4-One', - cover: '/img/covers/565c0f7067425.jpeg' -}) - -const musicSpeaks = factory('album', { - id: 1194, - artist_id: 3, - name: 'And The Music Speaks', - cover: '/img/covers/unknown-album.png' -}) - -const spaceJam = factory('album', { - id: 1195, - artist_id: 3, - name: 'Space Jam', - cover: '/img/covers/565c0f7115e0f.png' -}) - -const highway = factory('album', { - id: 1217, - artist_id: 4, - name: 'Highway 61 Revisited', - cover: '/img/covers/565c0f76dc6e8.jpeg' -}) - -const patGarrett = factory('album', { - id: 1218, - artist_id: 4, - name: 'Pat Garrett & Billy the Kid', - cover: '/img/covers/unknown-album.png' -}) - -const theTimes = factory('album', { - id: 1219, - artist_id: 4, - name: 'The Times They Are A-Changin', - cover: '/img/covers/unknown-album.png' -}) - -const backToBedlam = factory('album', { - id: 1268, - artist_id: 5, - name: 'Back To Bedlam', - cover: '/img/covers/unknown-album.png' -}) - -export default { - artists: [unknownArtist, variousArtist, all4One, bobDylan, jamesBlunt], - albums: [ - all4OneAlbum, - musicSpeaks, - spaceJam, - highway, - patGarrett, - theTimes, - backToBedlam - ], - - songs: [ - factory('song', { - id: '39189f4545f9d5671fb3dc964f0080a0', - album_id: all4OneAlbum.id, - artist_id: all4One.id, - title: 'I Swear', - length: 259.92, - play_count: 4 - }), - factory('song', { - id: 'a6a550f7d950d2a2520f9bf1a60f025a', - album_id: musicSpeaks.id, - artist_id: all4One.id, - title: 'I can love you like that', - length: 262.61, - play_count: 2 - }), - factory('song', { - id: 'd86c30fd34f13c1aff8db59b7fc9c610', - album_id: spaceJam.id, - artist_id: all4One.id, - title: 'I turn to you', - length: 293.04 - }), - factory('song', { - id: 'e6d3977f3ffa147801ca5d1fdf6fa55e', - album_id: highway.id, - artist_id: bobDylan.id, - title: 'Like a rolling stone', - length: 373.63 - }), - factory('song', { - id: 'aa16bbef6a9710eb9a0f41ecc534fad5', - album_id: patGarrett.id, - artist_id: bobDylan.id, - title: 'Knockin\' on heaven\'s door', - length: 151.9 - }), - factory('song', { - id: 'cb7edeac1f097143e65b1b2cde102482', - album_id: theTimes.id, - artist_id: bobDylan.id, - title: 'The times they are a-changin\'', - length: 196 - }), - factory('song', { - id: '0ba9fb128427b32683b9eb9140912a70', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'No bravery', - length: 243.12 - }), - factory('song', { - id: '123fd1ad32240ecab28a4e86ed5173', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'So long, Jimmy', - length: 265.04 - }), - factory('song', { - id: '6a54c674d8b16732f26df73f59c63e21', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'Wisemen', - length: 223.14 - }), - factory('song', { - id: '6df7d82a9a8701e40d1c291cf14a16bc', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'Goodbye my lover', - length: 258.61 - }), - factory('song', { - id: '74a2000d343e4587273d3ad14e2fd741', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'High', - length: 245.86 - }), - factory('song', { - id: '7900ab518f51775fe6cf06092c074ee5', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'You\'re beautiful', - length: 213.29 - }), - factory('song', { - id: '803910a51f9893347e087af851e38777', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'Cry', - length: 246.91 - }), - factory('song', { - id: 'd82b0d4d4803ebbcb61000a5b6a868f5', - album_id: backToBedlam.id, - artist_id: jamesBlunt.id, - title: 'Tears and rain', - length: 244.45 - }) - ], - interactions: [ - { - id: 1, - song_id: '7900ab518f51775fe6cf06092c074ee5', - liked: false, - play_count: 1 - }, - { - id: 2, - song_id: '95c0ffc33c08c8c14ea5de0a44d5df3c', - liked: false, - play_count: 2 - }, - { - id: 3, - song_id: 'c83b201502eb36f1084f207761fa195c', - liked: false, - play_count: 1 - }, - { - id: 4, - song_id: 'cb7edeac1f097143e65b1b2cde102482', - liked: true, - play_count: 3 - }, - { - id: 5, - song_id: 'ccc38cc14bb95aefdf6da4b34adcf548', - liked: false, - play_count: 4 - } - ] as Interaction[], - currentUser, - users: [ - currentUser, - factory('user', { - id: 2, - name: 'John Doe', - email: 'john@doe.tld', - is_admin: false - }) - ] -} diff --git a/resources/assets/js/services/playbackService.spec.ts b/resources/assets/js/services/playbackService.spec.ts index 2947bebb..5586a2d5 100644 --- a/resources/assets/js/services/playbackService.spec.ts +++ b/resources/assets/js/services/playbackService.spec.ts @@ -5,7 +5,7 @@ import { eventBus, noop } from '@/utils' import router from '@/router' import factory from '@/__tests__/factory' import UnitTestCase from '@/__tests__/UnitTestCase' -import { nextTick } from 'vue' +import { nextTick, reactive } from 'vue' import { socketService } from '@/services' import { playbackService } from './playbackService' @@ -43,11 +43,22 @@ new class extends UnitTestCase { super.beforeEach(() => this.setupEnvironment()) } + private setCurrentSong (song?: Song) { + song = reactive(song || factory('song', { + playback_state: 'Playing' + })) + + queueStore.state.songs = reactive([song]) + return song + } + protected test () { it('only initializes once', () => { const spy = vi.spyOn(plyr, 'setup') + playbackService.init() expect(spy).toHaveBeenCalled() + playbackService.init() expect(spy).toHaveBeenCalledTimes(1) }) @@ -60,23 +71,23 @@ new class extends UnitTestCase { ])( 'when playCountRegistered is %s, isTranscoding is %s, current media time is %d, media duration is %d, then registerPlay() should be call %d times', (playCountRegistered, isTranscoding, currentTime, duration, numberOfCalls) => { + this.setCurrentSong(factory('song', { + play_count_registered: playCountRegistered, + playback_state: 'Playing' + })) + this.setReadOnlyProperty(playbackService, 'isTranscoding', isTranscoding) playbackService.init() - const mediaElement = playbackService.player!.media + + const mediaElement = playbackService.player.media + // we can't set mediaElement.currentTime|duration directly because they're read-only - Object.defineProperties(mediaElement, { - currentTime: { - value: currentTime, - configurable: true - }, - duration: { - value: duration, - configurable: true - } - }) + this.setReadOnlyProperty(mediaElement, 'currentTime', currentTime) + this.setReadOnlyProperty(mediaElement, 'duration', duration) const registerPlayMock = this.mock(playbackService, 'registerPlay') mediaElement.dispatchEvent(new Event('timeupdate')) + expect(registerPlayMock).toHaveBeenCalledTimes(numberOfCalls) }) @@ -89,11 +100,11 @@ new class extends UnitTestCase { it('scrobbles if current song ends', () => { commonStore.state.use_last_fm = true - userStore.current = factory('user', { + userStore.state.current = reactive(factory('user', { preferences: { lastfm_session_key: 'foo' } - }) + })) playbackService.init() const scrobbleMock = this.mock(songStore, 'scrobble') @@ -109,7 +120,9 @@ new class extends UnitTestCase { playbackService.init() const restartMock = this.mock(playbackService, 'restart') const playNextMock = this.mock(playbackService, 'playNext') + playbackService.player!.media.dispatchEvent(new Event('ended')) + expect(restartMock).toHaveBeenCalledTimes(restartCalls) expect(playNextMock).toHaveBeenCalledTimes(playNextCalls) }) @@ -128,16 +141,8 @@ new class extends UnitTestCase { const mediaElement = playbackService.player!.media - Object.defineProperties(mediaElement, { - currentTime: { - value: currentTime, - configurable: true - }, - duration: { - value: duration, - configurable: true - } - }) + this.setReadOnlyProperty(mediaElement, 'currentTime', currentTime) + this.setReadOnlyProperty(mediaElement, 'duration', duration) const preloadMock = this.mock(playbackService, 'preload') mediaElement.dispatchEvent(new Event('timeupdate')) @@ -148,14 +153,12 @@ new class extends UnitTestCase { it('registers play', () => { const recentlyPlayedStoreAddMock = this.mock(recentlyPlayedStore, 'add') - const recentlyPlayedStoreFetchAllMock = this.mock(recentlyPlayedStore, 'fetch') const registerPlayMock = this.mock(songStore, 'registerPlay') const song = factory('song') playbackService.registerPlay(song) expect(recentlyPlayedStoreAddMock).toHaveBeenCalledWith(song) - expect(recentlyPlayedStoreFetchAllMock).toHaveBeenCalled() expect(registerPlayMock).toHaveBeenCalledWith(song) expect(song.play_count_registered).toBe(true) }) @@ -180,13 +183,11 @@ new class extends UnitTestCase { }) it('restarts a song', async () => { - const song = factory('song') + const song = this.setCurrentSong() this.mock(Math, 'floor', 1000) const emitMock = this.mock(eventBus, 'emit') const broadcastMock = this.mock(socketService, 'broadcast') const showNotificationMock = this.mock(playbackService, 'showNotification') - const dataToBroadcast = {} - this.mock(songStore, 'generateDataToBroadcast', dataToBroadcast) const restartMock = this.mock(playbackService.player!, 'restart') const playMock = this.mock(window.HTMLMediaElement.prototype, 'play') @@ -195,7 +196,7 @@ new class extends UnitTestCase { expect(song.play_start_time).toEqual(1000) expect(song.play_count_registered).toBe(false) expect(emitMock).toHaveBeenCalledWith('SONG_STARTED', song) - expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) + expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', song) expect(showNotificationMock).toHaveBeenCalled() expect(restartMock).toHaveBeenCalled() expect(playMock).toHaveBeenCalled() @@ -279,23 +280,22 @@ new class extends UnitTestCase { }) it('pauses playback', () => { - const currentSong = factory('song') - const dataToBroadcast = {} - this.mock(songStore, 'generateDataToBroadcast', dataToBroadcast) + const song = this.setCurrentSong() const pauseMock = this.mock(playbackService.player!, 'pause') const broadcastMock = this.mock(socketService, 'broadcast') playbackService.pause() - expect(currentSong.playback_state).toEqual('Paused') - expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) + expect(song.playback_state).toEqual('Paused') + expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', song) expect(pauseMock).toHaveBeenCalled() }) it('resumes playback', async () => { - const currentSong = factory('song') - const dataToBroadcast = {} - this.mock(songStore, 'generateDataToBroadcast', dataToBroadcast) + const song = this.setCurrentSong(factory('song', { + playback_state: 'Paused' + })) + const playMock = this.mock(window.HTMLMediaElement.prototype, 'play') const broadcastMock = this.mock(socketService, 'broadcast') const emitMock = this.mock(eventBus, 'emit') @@ -304,12 +304,13 @@ new class extends UnitTestCase { await playbackService.resume() expect(queueStore.current?.playback_state).toEqual('Playing') - expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', dataToBroadcast) + expect(broadcastMock).toHaveBeenCalledWith('SOCKET_SONG', song) expect(playMock).toHaveBeenCalled() - expect(emitMock).toHaveBeenCalledWith('SONG_STARTED', currentSong) + expect(emitMock).toHaveBeenCalledWith('SONG_STARTED', song) }) it('plays first in queue if toggled when there is no current song', async () => { + queueStore.clear() const playFirstInQueueMock = this.mock(playbackService, 'playFirstInQueue') await playbackService.toggle() @@ -318,10 +319,10 @@ new class extends UnitTestCase { }) it.each<[MethodOf, PlaybackState]>([ - ['resume', 'Stopped'], ['resume', 'Paused'], ['pause', 'Playing'] ])('%ss playback if toggled when current song playback state is %s', async (action, playbackState) => { + this.setCurrentSong(factory('song', { playback_state: playbackState })) const actionMock = this.mock(playbackService, action) await playbackService.toggle() @@ -375,14 +376,5 @@ new class extends UnitTestCase { expect(playMock).toHaveBeenCalledWith(songs[0]) }) - - it('playFirstInQueue triggers queueAndPlay if queue is empty', async () => { - queueStore.all = [] - const queueAndPlayMock = this.mock(playbackService, 'queueAndPlay') - - await playbackService.playFirstInQueue() - - expect(queueAndPlayMock).toHaveBeenCalled() - }) } } diff --git a/resources/assets/js/services/playbackService.ts b/resources/assets/js/services/playbackService.ts index 9f7f0bda..41777b0c 100644 --- a/resources/assets/js/services/playbackService.ts +++ b/resources/assets/js/services/playbackService.ts @@ -22,20 +22,20 @@ import router from '@/router' const PRELOAD_BUFFER = 30 const DEFAULT_VOLUME_VALUE = 7 const VOLUME_INPUT_SELECTOR = '#volumeInput' -const REPEAT_MODES: RepeatMode[] = ['NO_REPEAT', 'REPEAT_ALL', 'REPEAT_ONE'] -export const playbackService = { - player: null as Plyr | null, - volumeInput: null as unknown as HTMLInputElement, - repeatModes: REPEAT_MODES, - initialized: false, +class PlaybackService { + public player: Plyr + private volumeInput: HTMLInputElement + private repeatModes: RepeatMode[] = ['NO_REPEAT', 'REPEAT_ALL', 'REPEAT_ONE'] + private initialized = false - init () { - // We don't need to init this service twice, or the media events will be duplicated. + public init () { if (this.initialized) { return } + this.initialized = true + this.player = plyr.setup('.plyr', { controls: [] })[0] @@ -54,10 +54,9 @@ export const playbackService = { } this.setMediaSessionActionHandlers() - this.initialized = true - }, + } - setMediaSessionActionHandlers () { + private setMediaSessionActionHandlers () { if (!navigator.mediaSession) { return } @@ -66,9 +65,9 @@ export const playbackService = { navigator.mediaSession.setActionHandler('pause', () => this.pause()) navigator.mediaSession.setActionHandler('previoustrack', () => this.playPrev()) navigator.mediaSession.setActionHandler('nexttrack', () => this.playNext()) - }, + } - listenToMediaEvents (mediaElement: HTMLMediaElement) { + private listenToMediaEvents (mediaElement: HTMLMediaElement) { mediaElement.addEventListener('error', () => this.playNext(), true) mediaElement.addEventListener('ended', () => { @@ -104,29 +103,25 @@ export const playbackService = { } if (process.env.NODE_ENV !== 'test') { - timeUpdateHandler = throttle(timeUpdateHandler, 3000) + timeUpdateHandler = throttle(timeUpdateHandler, 1000) } mediaElement.addEventListener('timeupdate', timeUpdateHandler) - }, + } - get isTranscoding () { - return isMobile.any && preferences.transcodeOnMobile - }, - - registerPlay (song: Song) { + public registerPlay (song: Song) { recentlyPlayedStore.add(song) songStore.registerPlay(song) song.play_count_registered = true - }, + } - preload (song: Song) { + public preload (song: Song) { const audioElement = document.createElement('audio') audioElement.setAttribute('src', songStore.getSourceUrl(song)) audioElement.setAttribute('preload', 'auto') audioElement.load() song.preloaded = true - }, + } /** * Play a song. Because @@ -136,9 +131,9 @@ export const playbackService = { * So many dreams swinging out of the blue * We'll let them come true */ - async play (song: Song) { + public async play (song: Song) { document.title = `${song.title} ♫ Koel` - this.player!.media.setAttribute('title', `${song.artist_name} - ${song.title}`) + this.player.media.setAttribute('title', `${song.artist_name} - ${song.title}`) if (queueStore.current) { queueStore.current.playback_state = 'Stopped' @@ -148,7 +143,7 @@ export const playbackService = { // Manually set the `src` attribute of the audio to prevent plyr from resetting // the audio media object and cause our equalizer to malfunction. - this.getPlayer().media.src = songStore.getSourceUrl(song) + this.player.media.src = songStore.getSourceUrl(song) // We'll just "restart" playing the song, which will handle notification, scrobbling etc. // Fixes #898 @@ -157,9 +152,9 @@ export const playbackService = { } await this.restart() - }, + } - showNotification (song: Song) { + public showNotification (song: Song) { if (!window.Notification || !preferences.notify) { return } @@ -191,9 +186,9 @@ export const playbackService = { } ] }) - }, + } - async restart () { + public async restart () { const song = queueStore.current! this.showNotification(song) @@ -205,21 +200,25 @@ export const playbackService = { eventBus.emit('SONG_STARTED', song) socketService.broadcast('SOCKET_SONG', song) - this.getPlayer().restart() + this.player.restart() try { - await this.getPlayer().media.play() + await this.player.media.play() } catch (error) { // convert this into a warning, as an error will cause Cypress to fail the tests entirely logger.warn(error) } - }, + } + + public get isTranscoding () { + return isMobile.any && preferences.transcodeOnMobile + } /** * The next song in the queue. * If we're in REPEAT_ALL mode and there's no next song, just get the first song. */ - get next () { + public get next () { if (queueStore.next) { return queueStore.next } @@ -227,13 +226,13 @@ export const playbackService = { if (preferences.repeatMode === 'REPEAT_ALL') { return queueStore.first } - }, + } /** * The previous song in the queue. * If we're in REPEAT_ALL mode and there's no prev song, get the last song. */ - get previous () { + public get previous () { if (queueStore.previous) { return queueStore.previous } @@ -241,13 +240,13 @@ export const playbackService = { if (preferences.repeatMode === 'REPEAT_ALL') { return queueStore.last } - }, + } /** * Circle through the repeat mode. * The selected mode will be stored into local storage as well. */ - changeRepeatMode () { + public changeRepeatMode () { let index = this.repeatModes.indexOf(preferences.repeatMode) + 1 if (index >= this.repeatModes.length) { @@ -255,17 +254,17 @@ export const playbackService = { } preferences.repeatMode = this.repeatModes[index] - }, + } /** * Play the prev song in the queue, if one is found. * If the prev song is not found and the current mode is NO_REPEAT, we stop completely. */ - async playPrev () { + public async playPrev () { // If the song's duration is greater than 5 seconds and we've passed 5 seconds into it, // restart playing instead. - if (this.getPlayer().media.currentTime > 5 && queueStore.current!.length > 5) { - this.getPlayer().restart() + if (this.player.media.currentTime > 5 && queueStore.current!.length > 5) { + this.player.restart() return } @@ -275,66 +274,64 @@ export const playbackService = { } else { this.previous && await this.play(this.previous) } - }, + } /** * Play the next song in the queue, if one is found. * If the next song is not found and the current mode is NO_REPEAT, we stop completely. */ - async playNext () { + public async playNext () { if (!this.next && preferences.repeatMode === 'NO_REPEAT') { await this.stop() // Nothing lasts forever, even cold November rain. } else { this.next && await this.play(this.next) } - }, + } - getVolume: () => preferences.volume, + public getVolume () { + return preferences.volume + } /** * @param {Number} volume 0-10 * @param {Boolean=true} persist Whether the volume should be saved into local storage */ - setVolume (volume: number, persist = true) { - this.getPlayer().setVolume(volume) - - if (persist) { - preferences.volume = volume - } - + public setVolume (volume: number, persist = true) { + this.player.setVolume(volume) + persist && (preferences.volume = volume) this.volumeInput.value = String(volume) - }, + } - mute () { + public mute () { this.setVolume(0, false) - }, + } - unmute () { + public unmute () { preferences.volume = preferences.volume || DEFAULT_VOLUME_VALUE this.setVolume(preferences.volume) - }, + } - async stop () { + public async stop () { document.title = 'Koel' - this.getPlayer().pause() - this.getPlayer().seek(0) + this.player.pause() + this.player.seek(0) if (queueStore.current) { queueStore.current.playback_state = 'Stopped' } socketService.broadcast('SOCKET_PLAYBACK_STOPPED') - }, + } - pause () { - this.getPlayer().pause() + public pause () { + this.player.pause() queueStore.current!.playback_state = 'Paused' socketService.broadcast('SOCKET_SONG', queueStore.current) - }, + } - async resume () { + public async resume () { try { - await this.getPlayer().media.play() + await this.player.media.play() } catch (error) { logger.error(error) } @@ -342,9 +339,9 @@ export const playbackService = { queueStore.current!.playback_state = 'Playing' eventBus.emit('SONG_STARTED', queueStore.current) socketService.broadcast('SOCKET_SONG', queueStore.current) - }, + } - async toggle () { + public async toggle () { if (!queueStore.current) { await this.playFirstInQueue() return @@ -356,7 +353,7 @@ export const playbackService = { } this.pause() - }, + } /** * Queue up songs (replace them into the queue) and start playing right away. @@ -364,7 +361,7 @@ export const playbackService = { * @param {?Song[]} songs An array of song objects. Defaults to all songs if null. * @param {Boolean=false} shuffled Whether to shuffle the songs before playing. */ - async queueAndPlay (songs: Song[], shuffled = false) { + public async queueAndPlay (songs: Song[], shuffled = false) { if (shuffled) { songs = shuffle(songs) } @@ -376,13 +373,11 @@ export const playbackService = { await nextTick() router.go('queue') await this.play(queueStore.first) - }, + } - getPlayer () { - return this.player! - }, - - async playFirstInQueue () { + public async playFirstInQueue () { queueStore.all.length && await this.play(queueStore.first) } } + +export const playbackService = new PlaybackService() diff --git a/resources/assets/js/stores/queueStore.ts b/resources/assets/js/stores/queueStore.ts index d108c986..2dbd8067 100644 --- a/resources/assets/js/stores/queueStore.ts +++ b/resources/assets/js/stores/queueStore.ts @@ -1,13 +1,12 @@ import { reactive } from 'vue' -import { differenceBy, shuffle, union, unionBy } from 'lodash' +import { differenceBy, shuffle, unionBy } from 'lodash' import { arrayify } from '@/utils' import { httpService } from '@/services' import { songStore } from '@/stores' export const queueStore = { state: reactive({ - songs: [] as Song[], - current: null as Song + songs: [] as Song[] }), init () { @@ -40,7 +39,7 @@ export const queueStore = { }, set all (songs: Song[]) { - this.state.songs = songs + this.state.songs = reactive(songs) }, get first () { @@ -52,7 +51,7 @@ export const queueStore = { }, contains (song: Song) { - return this.all.includes(song) + return this.all.includes(reactive(song)) }, /** @@ -74,7 +73,7 @@ export const queueStore = { }, replaceQueueWith (songs: Song | Song[]) { - this.state.songs = arrayify(songs) + this.state.songs = reactive(arrayify(songs)) }, queueAfterCurrent (songs: Song | Song[]) { @@ -88,7 +87,7 @@ export const queueStore = { this.unqueue(songs) const head = this.all.splice(0, this.indexOf(this.current) + 1) - this.all = head.concat(songs, this.all) + this.all = head.concat(reactive(songs), this.all) }, unqueue (songs: Song | Song[]) { @@ -104,7 +103,7 @@ export const queueStore = { movedSongs.forEach(song => { this.all.splice(this.indexOf(song), 1) - this.all.splice(targetIndex, 0, song) + this.all.splice(targetIndex, 0, reactive(song)) }) }, @@ -113,7 +112,7 @@ export const queueStore = { }, indexOf (song: Song) { - return this.all.indexOf(song) + return this.all.indexOf(reactive(song)) }, get next () {