UNPKG

audio-source-composer

Version:

Audio Source Composer

753 lines (629 loc) 25 kB
import Song from "../song/Song"; import ClientStorage from "../common/storage/ClientStorage"; import ASPlayerMenu from "./ASPlayerMenu"; class ASPlayerActions extends ASPlayerMenu { constructor(props={}) { super(props); this.onSongEventCallback = (e) => this.onSongEvent(e); // this.activeSong = null; // this.nextPlaylistSong = null; } // getDefaultProgramURL() { // return findThisScript()[0].basePath + 'programs/audio-source-synthesizer.js'; // } /** Song rendering **/ setCurrentSong(song) { if(this.song) { this.setStatus("Unloading song: " + this.song.data.title); if(this.song.isPlaying) { this.song.stopPlayback(); } this.song.removeEventListener('*', this.onSongEventCallback); // this.song.removeDispatchElement(this); // TODO: unload song? } this.song = song; this.song.addEventListener('*', this.onSongEventCallback); this.state.songLength = song.getSongLengthInSeconds(); // this.song.setVolume(this.state.volume); // this.song.addDispatchElement(this); song.playlistPosition = this.getCurrentEntryPosition(); const currentEntry = this.getCurrentEntry(); currentEntry.name = song.data.title; currentEntry.length = song.getSongLengthInSeconds(); this.forceUpdate(); this.setStatus("Initializing song: " + song.data.title); this.song.connect(this.getAudioContext()); this.setStatus("Loaded song: " + song.data.title); } // handleError(err) { // this.setStatus(`<span style="error">${err}</span>`); // console.error(err); // // if(this.webSocket) // } setStatus(statusText, statusType='log') { console.info.apply(null, arguments); // (newStatus); this.setState({statusText, statusType}); } setError(statusText) { this.setStatus(statusText, 'error'); } // closeAllMenus() { // this.menuFile.closeAllMenus(); // } /** Song loading **/ saveSongToFile() { const songData = this.song.data; // const songHistory = this.song.history; const storage = new ClientStorage(); storage.saveSongToFile(songData); this.setStatus("Saved song to file"); } /** Song commands **/ /** Playback **/ getAudioContext() { if (this.audioContext) return this.audioContext; const audioContext = new (window.AudioContext || window.webkitAudioContext)(); this.audioContext = audioContext; return audioContext; } getVolumeGain() { if (!this.volumeGain) { const context = this.getAudioContext(); let gain = context.createGain(); gain.gain.value = this.state.volume; // Song.DEFAULT_VOLUME; gain.connect(context.destination); this.volumeGain = gain; } return this.volumeGain; } getVolume () { return this.state.volume; // if(this.volumeGain) { // return this.volumeGain.gain.value; // } // return ASPlayer.DEFAULT_VOLUME; } setVolume (volume) { console.info("Setting volume: ", volume); if(this.song) this.song.setVolume(volume); // const gain = this.getVolumeGain(); // if(gain.gain.value !== volume) { // gain.gain.value = volume; // } // this.state.volume = volume; // this.fieldSongVolume.value = volume * 100; } async updateSongPositionMaxLength(maxSongLength) { await this.fieldSongPosition.setState({max: Math.ceil(maxSongLength)}); } updateSongPositionValue(playbackPositionInSeconds) { const roundedSeconds = Math.round(playbackPositionInSeconds); this.fieldSongTiming.value = this.values.formatPlaybackPosition(playbackPositionInSeconds); if (this.fieldSongPosition.value !== roundedSeconds) this.fieldSongPosition.value = roundedSeconds; } // async loadSongFromMemory(songUUID) { // await this.song.loadSongFromMemory(songUUID); // // this.render(); // this.setStatus("Song loaded from memory: " + songUUID, this.song); // // console.info(songData); // } // // async loadSongFromFileInput(file=null) { // if(file === null) // file = this.fieldSongFileLoad.inputElm.files[0]; // if (!file) // throw new Error("Invalid file input"); // await this.song.loadSongFromFileInput(file); // this.addSongFileToPlaylist(file, this.song.data.title, this.song.getSongLengthInSeconds()); // // this.render(); // } // async loadSongFromPlaylistEntry(playlistPosition) { // // this.setStatus("Loading from playlist: " + url); // await this.playlist.loadSongFromURL(playlistPosition); // } /** Song ASPPlaylist **/ // async loadPlaylistFromURL(playlistURL) { // await this.playlist.loadSongFromURL(playlistURL); // this.setStatus("Loaded playlist from url: " + playlistURL); // } /** Entries **/ getCurrentEntryPosition() { return this.state.playlist.position || 0; } getCurrentEntry(throwException=true) { if(this.state.playlist.entries.length === 0) throw new Error("Empty playlist"); return this.getEntry(this.getCurrentEntryPosition(), throwException); } getEntry(position, throwException=true) { let foundEntry=null; this.eachEntry((entry, i) => { // console.log('entry', i, position); if(i === position) foundEntry = entry; if(foundEntry) return false; }); if(!foundEntry && throwException) throw new Error("Invalid playlist position: " + position); // console.log('found', foundEntry.state.id, position); return foundEntry; } eachEntry(callback) { const results = []; let offset=0; each(this.state.playlist.entries, 0); return results; function each(playlist, depth) { for (let i = 0; i < playlist.length; i++) { const entry = playlist[i]; const ret = callback(entry, offset, depth); if (ret === false) return false; if (ret !== null) results.push(ret); offset++; if(entry.playlist && entry.open === true) { const ret = each(entry.playlist.entries, depth+1); if (ret === false) return false; } } return true; } } async toggleEntryAtPosition(entryPosition) { const entryData = this.getEntry(entryPosition); if(this.isPlaylist(entryData.url)) { await this.togglePlaylistEntry(entryData); } else { if(this.state.playlist.position !== entryPosition) { this.state.playlist.position = entryPosition; this.playlistPlay(); } else { console.warn('no action'); } } } async togglePlaylistEntry(entryData) { if(!this.isPlaylist(entryData.url)) throw new Error("Invalid playlist entry"); entryData.open = !entryData.open; if(entryData.open === true) { if(!entryData.playlist) { entryData.loading = true; this.playlist && this.playlist.forceUpdate(); // TODO: optimize entryData.playlist = await this.loadPlaylistDataFromURL(entryData.url); delete entryData.loading; } } this.playlist && this.playlist.forceUpdate(); // TODO: optimize } playEntry(entryData) { } /** Loading **/ // /** @deprecated **/ // async loadSongFromURL(url) { // const song = this.song; // if(this.isPlaylist(url)) { // const playlistEntry = new PlaylistPlaylistEntry({url}); // this.addEntry(playlistEntry); // this.forceUpdate(); // // await this.loadPlaylistFromURL(url); // // const entry = this.getCurrentEntry(); // // if(entry.url && !this.isPlaylist(entry.url)) // // await this.loadSongFromPlaylistEntry(this.position); // } else { // await song.loadSongFromURL(url); // this.setStatus("Loaded from url: " + url); // // this.addSongURLToPlaylist(url, song.data.title, song.getSongLengthInSeconds()); // } // // this.render(); // } /** @deprecated **/ async loadURLAsPlaylist(playlistURL) { this.addEntryToPlaylist(playlistURL); this.toggleEntryAtPosition(0); // this.state.playlist = await this.loadPlaylistDataFromURL(playlistURL); // this.forceUpdate(); } async loadSongFromPlaylistEntry() { const currentEntry = await this.getCurrentEntry(); let song; if(this.isPlaylist(currentEntry.url)) throw new Error("Invalid song (playlist): " + currentEntry.url); if(currentEntry.file) { this.setStatus("Loading playlist song from file: " + currentEntry.file.name); song = await Song.loadSongFromFileInput(currentEntry.file); } else if(currentEntry.url) { this.setStatus("Loading playlist song from url: " + currentEntry.url); song = await Song.loadSongFromURL(currentEntry.url); } else { throw new Error("Invalid song entry: " + JSON.stringify(currentEntry)); } return song; // this.setPositionEntry(currentEntry); // song.playlistPosition = this.position; // this.render(); } async loadPlaylistDataFromURL(playlistURL) { console.log("Loading playlist: ", playlistURL); playlistURL = new URL(playlistURL, document.location); const playlistData = await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', playlistURL.toString(), true); xhr.responseType = 'json'; xhr.onload = () => resolve(xhr.response); xhr.onerror = reject; xhr.send(); }); if(!playlistData.entries) throw new Error("No playlist entries: " + playlistURL); if(!Array.isArray(playlistData.entries)) throw new Error("Invalid playlist entries: " + playlistURL); let urlPrefix = playlistData.urlPrefix; if(!urlPrefix || urlPrefix[0] !== '/') urlPrefix = playlistURL.pathname.split("/").slice(0,-1).join("/") + '/' + (urlPrefix||''); for(let id=0; id<playlistData.entries.length; id++) { let entry = playlistData.entries[id]; if(typeof entry === "object") entry.url = urlPrefix + entry.url; else entry = urlPrefix + entry; entry = this.parseEntryData(entry); playlistData.entries[id] = entry; } console.log("Loaded playlist: ", playlistURL, playlistData); return playlistData; } addEntryToPlaylist(entryData, insertAtPosition=null) { entryData = this.parseEntryData(entryData); const playlist = this.state.playlist; if(!playlist.entries) playlist.entries = []; const entries = playlist.entries; if(insertAtPosition === null) { insertAtPosition = entries.length; entries.push(entryData); } else { entries.splice(insertAtPosition, 0, entryData); } } async openSongFromFileDialog(e, accept=null) { const file = await this.openFileDialog(accept); this.addInputFileToPlaylist(file); } addInputFileToPlaylist(file) { const entryData = { file, name: 'file://' + file.name, url: 'file://' + file.name }; this.addEntryToPlaylist(entryData); this.playlist.forceUpdate(); } // addSongURLToPlaylist(url, name=null, length=null) { // this.addEntryToPlaylist({url, name, length}); // if(this.playlist) // this.playlist.forceUpdate(); // this.setStatus("Added URL to playlist: " + url); // } // addSongFileToPlaylist(file, name=null, length=null) { // const entryData = { // url: 'file://' + file.name, // name: name || file.name.split('/').pop(), // length // }; // this.addEntryToPlaylist(entryData); // this.forceUpdate(); // this.setStatus("Added file to playlist: " + file.name); // } scrollToEntry(entryPosition) { console.log('TODO scrollToEntry', entryPosition) // (currentEntry.scrollIntoViewIfNeeded || currentEntry.scrollIntoView).apply(currentEntry); } /** Playback **/ async playlistPlay() { // this.playlistStop(); let position = this.getCurrentEntryPosition(); if(this.song && this.song.playlistPosition === position) { if(this.song.isPaused) return this.song.resume(); if(this.song.isPlaying) throw new Error("Song is already playing"); await this.setCurrentSong(this.song); this.setStatus("Playing: " + this.song.data.title); return await this.song.play(this.getAudioContext()); } // let entry = await this.playlist.getCurrentEntry(); // if(entry instanceof PlaylistPlaylistEntry) // entry = await this.playlistMoveToNextSongEntry(); this.setState({playing: true}); let currentEntry = await this.getCurrentEntry(); if(this.isPlaylist(currentEntry.url)) await this.playlistMoveToNextSongEntry(); while(this.state.playing) { this.scrollToEntry(this.getCurrentEntryPosition()); const currentSong = await this.loadSongFromPlaylistEntry(); await this.setCurrentSong(currentSong); this.setStatus("Playing: " + currentSong.data.title); await currentSong.play(this.getAudioContext()); if(!this.state.playing) break; // currentEntry = await this.playlistMoveToNextSongEntry(); } this.setState({playing: false}); } /** @deprecated **/ async playlistStop() { this.isPlaylistActive = false; if(this.song && this.song.isPlaying) { this.setStatus("Stopping: " + this.song.data.title); this.song.stopPlayback(); this.song.setPlaybackPositionInTicks(0); this.setStatus("Stopped: " + this.song.data.title); } } /** @deprecated **/ async playlistNext() { this.playlistStop(); await this.playlistMoveToNextSongEntry(); await this.playlistPlay(); } async playlistMovePosition(position) { let nextEntry = await this.getEntry(position,false); if(!nextEntry) position = 0; return await this.setPlaylistPosition(position); } /** @deprecated **/ async playlistMoveToEntry(nextEntry) { const position = await this.getEntryPosition(nextEntry); return await this.playlistMovePosition(position); } async playlistMoveToNextSongEntry() { let position = this.getCurrentEntryPosition(); const currentEntry = await this.getEntry(position); if(this.isPlaylist(currentEntry.url) && currentEntry.open !== true) { await this.togglePlaylistEntry(currentEntry); // this.playlist.forceUpdate(); // await currentEntry.togglePlaylist(true); } let totalCount = await this.getPlaylistCount(); for(let i=0; i<totalCount; i++) { position++; const currentEntry = await this.getEntry(position); if(this.isPlaylist(currentEntry.url)) { await this.togglePlaylistEntry(currentEntry); // this.playlist.forceUpdate(); totalCount = await this.getPlaylistCount(); } else { if(position >= totalCount) position = 0; await this.setPlaylistPosition(position); return; // Done // return currentEntry; } } throw new Error("Song entry not found"); } async getPlaylistCount() { let count=0; await this.eachEntry((entry, i) => count = i+1); return count; } async setPlaylistPosition(position) { if(position !== this.state.playlist.position) { this.state.playlist.position = position; this.playlist.forceUpdate(); // const nextEntry = await this.getEntry(position); // await currentEntry.removePosition(); // await nextEntry.setPosition(); } // this.setState({position}); // return nextEntry; } async setPositionEntry(entry) { const position = await this.findEntryPosition(entry); await this.setPlaylistPosition(position); } /** Utilities **/ isPlaylist(entryUrl) { return entryUrl && (entryUrl.toString().toLowerCase().endsWith('.pl.json')); } parseEntryData(entryData) { if(typeof entryData === "string") { const split = entryData.split(';'); entryData = {url: split[0]}; if(split[1]) entryData.name = split[1]; if(split[2]) entryData.length = split[2]; } if(!entryData.url) throw new Error("Invalid ASPPlaylist Entry URL"); if(!entryData.name) entryData.name = '../' + entryData.url.split('/').pop(); return entryData; } // async updateEntries() { // console.time('updateEntries'); // for(let i=0; i<this.playlist.length; i++) { // const entry = this.playlist[i]; // await entry.updateID(i); // } // console.timeEnd('updateEntries'); // } // toggleSelect(position) { // const selected = this.state.selected; // const i = selected.indexOf(position); // if(i === -1) { // selected.push(position); // selected.sort(); // } else { // selected.splice(i, 1); // } // this.getEntry(position) // .updatePlaylist(this); // } // async eachEntry(callback) { // let offset=0; // return await this.eachEntry(async (entry, i) => { // if(entry instanceof PlaylistPlaylistEntry) // return null; // return await callback(entry, offset++); // }) // } // // async eachPlaylistEntry(callback) { // let offset=0; // return await this.eachEntry(async (entry, i) => { // if(!entry instanceof PlaylistPlaylistEntry) // return null; // return await callback(entry, offset++); // }) // } // getCurrentEntry() { // if(this.state.playlist.length === 0) // throw new Error("Empty playlist"); // return this.getEntry(this.state.playlist.position); // } // async addEntry(entry, insertAtPosition=null, skipDuplicate=true) { // if(!entry instanceof ASPPlaylistEntry) // throw new Error("Invalid ASPPlaylistEntry"); // if(skipDuplicate && this.entries.find(e => e.url === entry.url)) { // return false; // } // if(insertAtPosition === null) { // insertAtPosition = this.entries.length; // this.entries.push(entry); // } else { // this.entries.splice(insertAtPosition, 0, entry); // } // await entry.updateID(insertAtPosition); // // this.forceUpdate(); // return true; // } // async updateNextPosition() { // let position = this.state.playlist.position; // const currentEntry = await this.getEntry(position); // await currentEntry.removePosition(); // position++; // let nextEntry = await this.getEntry(position,false); // if(!nextEntry) position = 0; // return await this.setPlaylistPosition(position); // } // getPlaylistPosition() { return this.state.playlist.position; } // async updatePosition(position) { // if(!this.playlist[position]) // throw new Error("Invalid playlist position: " + position); // this.state.playlist.position = position; // // await this.updateEntries(); // } /** Song Playback **/ // async songPlay() { // if(this.playlistActive) // throw new Error("Playback is already active"); // this.playlistActive = true; // await this.song.play(); // await this.songNext(); // TODO: prevent async playback // } // // async songNext() { // this.playlistActive = true; // while(this.playlistActive) { // const entry = await this.playlistMovePosition(); // (entry.scrollIntoViewIfNeeded || entry.scrollIntoView).apply(entry); // const nextSong = await this.playlist.loadSongFromPlaylistEntry(); // this.setCurrentSong(nextSong); // await this.song.play(); // } // } // // async songPause() { // this.song.stopPlayback(); // } // songStopIfPlaying() { // if(this.playlistActive) // this.songStop(); // } setSongPositionPercentage(playbackPercentage) { const playbackPosition = (playbackPercentage / 100) * this.state.songLength; return this.setSongPosition(playbackPosition); } setSongPosition(playbackPosition) { // const wasPlaying = !!this.song.playback; // if (wasPlaying) // this.song.stopPlayback(); if(!this.song) throw new Error("No song loaded"); const song = this.song; song.setPlaybackPosition(playbackPosition); // if (wasPlaying) // this.song.play(); } // async clearPlaylist() { // this.playlist.clear(); // } /** @deprecated **/ async playlistPlayTest() { let entry = await this.playlist.getCurrentEntry(); while(entry) { (entry.scrollIntoViewIfNeeded || entry.scrollIntoView).apply(entry); await new Promise((resolve, reject) => { setTimeout(resolve, 1000); }); entry = await this.playlistMoveToNextSongEntry(); } } /** Toggle Panels **/ togglePanelPlaylist(e) { this.classList.toggle('hide-panel-playlist'); } togglePanelSong(e) { this.classList.toggle('hide-panel-song'); } toggleFullscreen(e) { const setFullScreen = !this.classList.contains('fullscreen'); // this.containerElm.classList.toggle('fullscreen', setFullScreen); this.classList.toggle('fullscreen', setFullScreen); if (setFullScreen) { } } /** Prompt **/ async openFileDialog(accept=null) { return await new Promise((resolve, reject) => { const input = document.createElement('input'); input.setAttribute('type', 'file'); if(accept) input.setAttribute('accept', accept); input.addEventListener('change', () => { const file = input.files[0]; if(file) resolve(file); else reject(); }); input.click(); }) } // get playlist() { return this.state.playlist; } // createStyleSheetLink(stylePath, scriptElm=null) { // // const AudioSourceLoader = customElements.get('audio-source-loader'); // const linkHRef = new URL(stylePath, (scriptElm || thisModule).src); // const link = document.createElement('link'); // link.setAttribute('rel', 'stylesheet'); // link.href = linkHRef; // return link; // } // restart() { // const RNRestart = require('react-native-restart').default; // RNRestart.Restart(); // } // openMenu(menuKey) { // this.state.menuKey = menuKey; // // if(this.props.onUpdateMenu) // this.props.onUpdateMenu(); // // setTimeout(e => this.toggleMenu(), 10); // } // // toggleMenu(menuKey=null) { // if(this.props.onToggleMenu) // this.props.onToggleMenu(); // } } export default ASPlayerActions;