import React, {Component} from 'react'; import Select from "react-virtualized-select"; import Sound from 'react-sound'; import AlbumList from './AlbumList.js'; import MediaSession from './MediaSession.js'; import Controls from './Controls.js'; import Playlist from './Playlist.js'; import {getQueryString} from './utils.js'; const fetchOpts = { credentials: 'same-origin' } export default class Player extends Component { selects = [{type: 'artist', title: 'Artists'}, {type: 'year', title: 'Years'}, {type: 'genre', title: 'Genres'}, {type: 'publisher', title: 'Labels'}, {type: 'country', title: 'Countries'}, {type: 'type', title: 'Album types'}, {type: 'status', title: 'Album statuses'}]; constructor(props) { super(props); this.state = { albumList: {key:'', albums: [], error: false, hasMore: false}, filters: {}, filter: '', sound: {url: '', playStatus: Sound.status.STOPPED}, tracks: null, activeTrack: -1, activeAlbum: null, metadata: {artist: '', album: '', title: '', cover: ''} }; this.filterTimeout = null; } genAlbumListKey() { const {filter, filters} = this.state return `${filter}-${this.selects.map(({type}) => filters[type] ? filters[type].item : '').join('-')}` } loadAlbums = (page) => { const {filter, filters} = this.state const limit = 15; const offset = page * limit; const params = {filter, offset, limit:(limit+1)}; let key = (page === 0 ? this.genAlbumListKey() : this.state.albumList.key) for (const {type} of this.selects) { if (filters[type]) { params[type] = filters[type].item; } } return fetch(`/cat/album?${getQueryString(params)}`, fetchOpts) .then(res => (res.ok ? res.json() : Promise.reject({message:res.statusText}))) .then(data => { const hasMore = (data.length > limit) const albums = page === 0 ? data.slice(0, limit) : this.state.albumList.albums.concat(data.slice(0, limit)) const albumList = {key, albums, hasMore} this.setState({albumList}) return albums; }) .catch(error => { console.log(error) const albumList = {key, error} this.setState({albumList}) }) } componentDidMount() { this.loadAlbums(0) } componentDidUpdate(prevProps, prevState) { const {filters} = this.state; if (filters !== prevState.filters) { this.loadAlbums(0); } if (this.state.activeAlbum !== prevState.activeAlbum) { this.fetchTracks(this.state.activeAlbum); } if (this.state.activeTrack !== prevState.activeTrack) { if (this.state.activeTrack !== -1 && Array.isArray(this.state.tracks)) { const track = this.state.tracks[this.state.activeTrack]; if (!track) { console.log('Bad activeTrack', this.state.tracks, this.state.activeTrack); } else { this.setState({ sound: { url: track.url, position: 0, playStatus: Sound.status.PLAYING }, metadata: { title: track.title, artist: track.artist, album: track.album, cover: this.state.activeAlbum.cover } }) } } } } fetchTracks(album) { fetch(`/album/${album.id}/tracks`, fetchOpts) .then(res => (res.ok ? res.json() : Promise.reject({message:res.statusText}))) .then(result => this.setState({tracks: result, activeTrack: 0})) .catch(error => this.setState({tracks: error.message})); this.setState({tracks: "Loading", activeTrack: -1}) } handleFilterChange = (e) => { if (this.filterTimeout) { clearInterval(this.filterTimeout); } const filter = e.target.value; this.filterTimeout = setTimeout(() => { this.loadAlbums(0); }, 500); this.setState({filter}) } handleControlPrev = () => { if (!Array.isArray(this.state.tracks)) { return; } var activeTrack = this.state.activeTrack - 1 if (activeTrack < 0) { activeTrack = -1; } this.setState({activeTrack: activeTrack}) } handleControlPlayPause = () => { const playStatus = (this.state.sound.playStatus === Sound.status.PLAYING ? Sound.status.PAUSED : Sound.status.PLAYING); this.setState({sound: Object.assign({}, this.state.sound, {playStatus: playStatus})}); } handleControlStop = () => { this.setState({ sound: Object.assign({}, this.state.sound, { playStatus: Sound.status.STOPPED, position: 0}), activeTrack: -1}); } handleControlNext = () => { if (!Array.isArray(this.state.tracks)) { return; } var activeTrack = this.state.activeTrack + 1 if (activeTrack >= this.state.tracks.length) { activeTrack = -1; } this.setState({activeTrack: activeTrack}) } handleSoundError = (errorCode, description) => { console.log('sound error', errorCode, description) // try next track this.handleControlNext(); } handleSoundLoading = (sound) => { // TODOconsole.log('sound loading', sound) } handleSoundPlaying = (sound) => { this.setState({sound: Object.assign({}, this.state.sound, { position: sound.position, duration: sound.duration })}) } handleSoundFinished = () => { console.log('sound finished'); this.setState({sound: Object.assign({}, this.state.sound, {playStatus: Sound.status.STOPPED})}); this.handleControlNext(); } handleActivateTrack = (activeTrack) => { this.setState({activeTrack: activeTrack}) } handlePlayAlbum = (album) => { this.setState({activeAlbum: album}) } handleLoadOptions(cat, q, page) { const pageSize = 100 const params = {filter:q} params.offset = (page - 1) * pageSize; params.limit = pageSize; return fetch(`/cat/${cat}?${getQueryString(params)}`, fetchOpts) .then(res => (res.ok ? res.json() : Promise.reject({message:res.statusText}))) .then(options => ({options})) } handleSelectChange(cat, option) { const filters = Object.assign({}, this.state.filters, {[cat]: option}); this.setState({filters, filter: ''}) } _optionRenderer ({ focusedOption, focusOption, key, labelKey, option, selectValue, style, valueArray }) { const className = ['VirtualizedSelectOption'] if (option === focusedOption) { className.push('VirtualizedSelectFocusedOption') } if (option.disabled) { className.push('VirtualizedSelectDisabledOption') } if (valueArray && valueArray.indexOf(option) >= 0) { className.push('VirtualizedSelectSelectedOption') } if (option.className) { className.push(option.className) } const events = option.disabled ? {} : { onClick: () => selectValue(option), onMouseEnter: () => focusOption(option) } return (