mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-28 12:36:35 +00:00
Simplify how files are loaded. Move more stuff out of Winamp.
This commit is contained in:
parent
f8fbe0d304
commit
cde7f3f5b0
11 changed files with 119 additions and 94 deletions
|
|
@ -5,14 +5,17 @@ import {BANDS} from './constants';
|
|||
import {clamp} from './utils';
|
||||
import {
|
||||
CLOSE_WINAMP,
|
||||
LOAD_AUDIO_FILE,
|
||||
LOAD_AUDIO_URL,
|
||||
OPEN_FILE_DIALOG,
|
||||
SET_BALANCE,
|
||||
SET_BAND_VALUE,
|
||||
SET_SKIN_DATA,
|
||||
SET_VOLUME,
|
||||
START_LOADING,
|
||||
STOP,
|
||||
TOGGLE_REPEAT,
|
||||
TOGGLE_SHUFFLE,
|
||||
STOP
|
||||
TOGGLE_SHUFFLE
|
||||
} from './actionTypes';
|
||||
|
||||
export function play() {
|
||||
|
|
@ -75,6 +78,25 @@ export function toggleShuffle() {
|
|||
return {type: TOGGLE_SHUFFLE};
|
||||
}
|
||||
|
||||
const SKIN_FILENAME_MATCHER = new RegExp('(wsz|zip)$', 'i');
|
||||
export function loadFileFromReference(fileReference) {
|
||||
return (dispatch) => {
|
||||
const file = new MyFile();
|
||||
file.setFileReference(fileReference);
|
||||
if (SKIN_FILENAME_MATCHER.test(fileReference.name)) {
|
||||
dispatch(setSkinFromFile(file));
|
||||
} else {
|
||||
dispatch({type: LOAD_AUDIO_FILE, file});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function loadMediaFromUrl(url, name) {
|
||||
return (dispatch) => {
|
||||
dispatch({type: LOAD_AUDIO_URL, url, name});
|
||||
};
|
||||
}
|
||||
|
||||
export function setSkinFromFile(skinFile) {
|
||||
return (dispatch) => {
|
||||
dispatch({type: START_LOADING});
|
||||
|
|
@ -98,8 +120,10 @@ export function setSkinFromFilename(filename) {
|
|||
return setSkinFromUrl(url);
|
||||
}
|
||||
|
||||
export function openFileDialog(winamp) {
|
||||
winamp.openFileDialog();
|
||||
export function openFileDialog(fileInput) {
|
||||
fileInput.click();
|
||||
// No reducers currently respond to this.
|
||||
return {type: OPEN_FILE_DIALOG};
|
||||
}
|
||||
|
||||
export function setEqBand(band, value) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
export const LOAD_AUDIO_URL = 'LOAD_AUDIO_URL';
|
||||
export const LOAD_AUDIO_FILE = 'LOAD_AUDIO_FILE';
|
||||
export const CLOSE_CONTEXT_MENU = 'CLOSE_CONTEXT_MENU';
|
||||
export const CLOSE_WINAMP = 'CLOSE_WINAMP';
|
||||
export const IS_PLAYING = 'IS_PLAYING';
|
||||
export const IS_STOPPED = 'IS_STOPPED';
|
||||
export const OPEN_FILE_DIALOG = 'OPEN_FILE_DIALOG';
|
||||
export const PAUSE = 'PAUSE';
|
||||
export const PLAY = 'PLAY';
|
||||
export const SEEK_TO_PERCENT_COMPLETE = 'SEEK_TO_PERCENT_COMPLETE';
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||
dispatch({type: TOGGLE_CONTEXT_MENU});
|
||||
e.stopPropagation();
|
||||
},
|
||||
openFileDialog: () => openFileDialog(ownProps.winamp),
|
||||
openFileDialog: () => dispatch(openFileDialog(ownProps.fileInput)),
|
||||
setSkin: (filename) => dispatch(setSkinFromFilename(filename))
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,23 +3,15 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {openFileDialog} from '../actionCreators';
|
||||
|
||||
const Eject = (props) => (
|
||||
<div
|
||||
id='eject'
|
||||
onClick={props.openFileDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
class Eject extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
openFileDialog(this.props.winamp);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
id='eject'
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
openFileDialog: () => dispatch(openFileDialog(ownProps.fileInput))
|
||||
});
|
||||
|
||||
module.exports = connect()(Eject);
|
||||
module.exports = connect(() => ({}), mapDispatchToProps)(Eject);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import {
|
|||
SET_FOCUSED_WINDOW
|
||||
} from '../actionTypes';
|
||||
|
||||
import {loadFileFromReference} from '../actionCreators';
|
||||
|
||||
import '../../css/main-window.css';
|
||||
|
||||
export class MainWindow extends React.Component {
|
||||
|
|
@ -51,7 +53,7 @@ export class MainWindow extends React.Component {
|
|||
this.supress(e);
|
||||
const {files} = e.dataTransfer;
|
||||
// TODO: Move this to an actionCreator
|
||||
this.props.winamp.loadFromFileReference(files[0]);
|
||||
this.props.dispatch(loadFileFromReference(files[0]));
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -85,7 +87,7 @@ export class MainWindow extends React.Component {
|
|||
>
|
||||
<div id='loading'>Loading...</div>
|
||||
<div id='title-bar' className='selected title-bard draggable'>
|
||||
<ContextMenu mediaPlayer={this.props.mediaPlayer} winamp={this.props.winamp} />
|
||||
<ContextMenu fileInput={this.props.fileInput} />
|
||||
<ShadeTime />
|
||||
<div id='minimize' />
|
||||
<Shade />
|
||||
|
|
@ -112,7 +114,7 @@ export class MainWindow extends React.Component {
|
|||
</div>
|
||||
<Position />
|
||||
<ActionButtons />
|
||||
<Eject winamp={this.props.winamp} />
|
||||
<Eject fileInput={this.props.fileInput} />
|
||||
<div className='shuffle-repeat'>
|
||||
<Shuffle />
|
||||
<Repeat />
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ module.exports = function(winamp, {dispatch}) {
|
|||
dispatch(pause());
|
||||
break;
|
||||
case 76: // L
|
||||
openFileDialog(winamp);
|
||||
dispatch(openFileDialog(winamp.fileInput));
|
||||
break;
|
||||
case 82: // R
|
||||
dispatch(toggleRepeat());
|
||||
|
|
@ -77,7 +77,7 @@ module.exports = function(winamp, {dispatch}) {
|
|||
winamp.previous();
|
||||
break;
|
||||
case 96: // numpad 0
|
||||
openFileDialog(winamp);
|
||||
dispatch(openFileDialog(winamp.fileInput));
|
||||
break;
|
||||
case 97: // numpad 1
|
||||
winamp.previous(10);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ if (new Browser(window).isCompatible) {
|
|||
the <head>, but browsers don't really care... */}
|
||||
</Skin>
|
||||
<WindowManager>
|
||||
<MainWindow winamp={winamp} mediaPlayer={winamp.media} />
|
||||
<MainWindow fileInput={winamp.fileInput} mediaPlayer={winamp.media} />
|
||||
{ playlist ? <PlaylistWindow /> : '' }
|
||||
{ equalizer ? <EqualizerWindow mediaPlayer={winamp.media} /> : '' }
|
||||
</WindowManager>
|
||||
|
|
|
|||
32
js/media.js
32
js/media.js
|
|
@ -1,5 +1,6 @@
|
|||
/* Emulate the native <audio> element with Web Audio API */
|
||||
import {BANDS} from './constants';
|
||||
import MyFile from './myFile';
|
||||
|
||||
module.exports = {
|
||||
_context: new (window.AudioContext || window.webkitAudioContext)(),
|
||||
|
|
@ -11,7 +12,8 @@ module.exports = {
|
|||
playing: function(){},
|
||||
timeupdate: function(){},
|
||||
visualizerupdate: function(){},
|
||||
ended: function(){}
|
||||
ended: function(){},
|
||||
fileLoaded: function(){}
|
||||
},
|
||||
_startTime: 0,
|
||||
_position: 0,
|
||||
|
|
@ -19,8 +21,11 @@ module.exports = {
|
|||
_playing: false,
|
||||
_loop: false,
|
||||
autoPlay: false,
|
||||
name: null,
|
||||
|
||||
init: function(fileInput) {
|
||||
this.fileInput = fileInput;
|
||||
|
||||
init: function() {
|
||||
// The _source node has to be recreated each time it's stopped or
|
||||
// paused, so we don't create it here.
|
||||
|
||||
|
|
@ -255,11 +260,34 @@ module.exports = {
|
|||
|
||||
seekToTime: function(time) {
|
||||
// Make sure we are within range
|
||||
// TODO: Use clamp
|
||||
time = Math.min(time, this.duration());
|
||||
time = Math.max(time, 0);
|
||||
this.play(time);
|
||||
},
|
||||
|
||||
loadFromFileReference: function(fileReference) {
|
||||
const file = new MyFile();
|
||||
file.setFileReference(fileReference);
|
||||
this.autoPlay = true;
|
||||
this.name = file.name;
|
||||
file.processBuffer(this._loadBuffer.bind(this));
|
||||
},
|
||||
|
||||
// Used only for the initial load, since it must have a CORS header
|
||||
loadFromUrl: function(url, fileName) {
|
||||
const file = new MyFile();
|
||||
this.name = fileName;
|
||||
file.setUrl(url, fileName);
|
||||
file.processBuffer(this._loadBuffer.bind(this));
|
||||
},
|
||||
|
||||
/* Listeners */
|
||||
_loadBuffer: function(buffer) {
|
||||
// Note, this will not happen right away
|
||||
this.loadBuffer(buffer, this._callbacks.fileLoaded);
|
||||
},
|
||||
|
||||
// There is probably a more reasonable way to do this, rather than having
|
||||
// it always running.
|
||||
_draw: function() {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
import {
|
||||
IS_PLAYING,
|
||||
IS_STOPPED,
|
||||
LOAD_AUDIO_URL,
|
||||
PAUSE,
|
||||
PLAY,
|
||||
SEEK_TO_PERCENT_COMPLETE,
|
||||
SET_BALANCE,
|
||||
SET_EQ_BAND,
|
||||
SET_MEDIA,
|
||||
SET_VOLUME,
|
||||
START_WORKING,
|
||||
STOP,
|
||||
STOP_WORKING,
|
||||
TOGGLE_REPEAT,
|
||||
TOGGLE_SHUFFLE,
|
||||
UPDATE_TIME_ELAPSED,
|
||||
SET_EQ_BAND
|
||||
UPDATE_TIME_ELAPSED
|
||||
} from './actionTypes';
|
||||
|
||||
export default (media) => (
|
||||
|
|
@ -37,6 +39,17 @@ export default (media) => (
|
|||
store.dispatch({type: STOP_WORKING});
|
||||
});
|
||||
|
||||
media.addEventListener('fileLoaded', () => {
|
||||
store.dispatch({
|
||||
type: SET_MEDIA,
|
||||
kbps: '128',
|
||||
khz: Math.round(media.sampleRate() / 1000).toString(),
|
||||
channels: media.channels(),
|
||||
name: media.name,
|
||||
length: media.duration()
|
||||
});
|
||||
});
|
||||
|
||||
return (next) => (action) => {
|
||||
switch (action.type) {
|
||||
case PLAY:
|
||||
|
|
@ -66,6 +79,9 @@ export default (media) => (
|
|||
case SET_EQ_BAND:
|
||||
media.setEqBand(action.band);
|
||||
break;
|
||||
case LOAD_AUDIO_URL:
|
||||
media.loadFromUrl(action.url, action.name);
|
||||
break;
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,13 +2,20 @@
|
|||
// `File` is already a builtin, so we use `MyFile`
|
||||
module.exports = function() {
|
||||
this.reader = new FileReader();
|
||||
this.name = null;
|
||||
this.url = null;
|
||||
this.fileReference = null;
|
||||
this.setUrl = function(url){
|
||||
this.setUrl = function(url, name){
|
||||
this.url = url;
|
||||
if (!name) {
|
||||
this.name = url.split('/').pop();
|
||||
} else {
|
||||
this.name = name;
|
||||
}
|
||||
};
|
||||
this.setFileReference = function(fileReference){
|
||||
this.fileReference = fileReference;
|
||||
this.name = fileReference.name;
|
||||
};
|
||||
this.processBuffer = function(bufferHandler) {
|
||||
if (this.url) {
|
||||
|
|
|
|||
73
js/winamp.js
73
js/winamp.js
|
|
@ -1,34 +1,32 @@
|
|||
// UI and App logic
|
||||
import Media from './media';
|
||||
import MyFile from './myFile';
|
||||
import {
|
||||
setSkinFromUrl,
|
||||
setSkinFromFile,
|
||||
setVolume,
|
||||
setPreamp,
|
||||
setBalance
|
||||
setBalance,
|
||||
loadMediaFromUrl,
|
||||
loadFileFromReference
|
||||
} from './actionCreators';
|
||||
|
||||
import {SET_MEDIA} from './actionTypes';
|
||||
|
||||
import '../css/winamp.css';
|
||||
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.style.display = 'none';
|
||||
|
||||
module.exports = {
|
||||
media: Media.init(),
|
||||
media: Media.init(fileInput),
|
||||
fileInput: fileInput,
|
||||
init: function(options) {
|
||||
this.fileInput = document.createElement('input');
|
||||
this.fileInput.type = 'file';
|
||||
this.fileInput.style.display = 'none';
|
||||
this.fileInput.addEventListener('change', (e) => {
|
||||
this.dispatch(loadFileFromReference(e.target.files[0]));
|
||||
});
|
||||
|
||||
this.dispatch(setVolume(options.volume));
|
||||
this.dispatch(setBalance(options.balance));
|
||||
this.dispatch(setPreamp(this.media, 50));
|
||||
this.loadFromUrl(options.mediaFile.url, options.mediaFile.name);
|
||||
this.dispatch(loadMediaFromUrl(options.mediaFile.url, options.mediaFile.name));
|
||||
this.dispatch(setSkinFromUrl(options.skinUrl));
|
||||
|
||||
this.fileInput.onchange = (e) => {
|
||||
this.loadFromFileReference(e.target.files[0]);
|
||||
};
|
||||
return this;
|
||||
},
|
||||
|
||||
|
|
@ -39,50 +37,5 @@ module.exports = {
|
|||
|
||||
seekForwardBy: function(seconds) {
|
||||
this.media.seekToTime(this.media.timeElapsed() + seconds);
|
||||
},
|
||||
|
||||
openFileDialog: function() {
|
||||
this.fileInput.click();
|
||||
},
|
||||
|
||||
loadFromFileReference: function(fileReference) {
|
||||
const file = new MyFile();
|
||||
file.setFileReference(fileReference);
|
||||
if (new RegExp('(wsz|zip)$', 'i').test(fileReference.name)) {
|
||||
this.dispatch(setSkinFromFile(file));
|
||||
} else {
|
||||
this.media.autoPlay = true;
|
||||
this.fileName = fileReference.name;
|
||||
file.processBuffer(this._loadBuffer.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
// Used only for the initial load, since it must have a CORS header
|
||||
loadFromUrl: function(url, fileName) {
|
||||
if (!fileName) {
|
||||
this.fileName = url.split('/').pop();
|
||||
} else {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
const file = new MyFile();
|
||||
file.setUrl(url);
|
||||
file.processBuffer(this._loadBuffer.bind(this));
|
||||
},
|
||||
|
||||
/* Listeners */
|
||||
_loadBuffer: function(buffer) {
|
||||
function setMetaData() {
|
||||
this.dispatch({
|
||||
type: SET_MEDIA,
|
||||
kbps: '128',
|
||||
khz: Math.round(this.media.sampleRate() / 1000).toString(),
|
||||
channels: this.media.channels(),
|
||||
name: this.fileName,
|
||||
length: this.media.duration()
|
||||
});
|
||||
}
|
||||
|
||||
// Note, this will not happen right away
|
||||
this.media.loadBuffer(buffer, setMetaData.bind(this));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue