diff --git a/config/webpack.library.js b/config/webpack.library.js
index 5d114ac4..dd7fe8e9 100644
--- a/config/webpack.library.js
+++ b/config/webpack.library.js
@@ -7,7 +7,8 @@ module.exports = {
node: {
// Consider suggesting jsmediatags use: https://github.com/feross/is-buffer
// Cuts 22k
- Buffer: false
+ Buffer: false,
+ fs: "empty"
},
module: {
rules: [
@@ -64,7 +65,9 @@ module.exports = {
],
entry: {
bundle: "./js/webamp.js",
- "bundle.min": "./js/webamp.js"
+ "bundle.min": "./js/webamp.js",
+ "lazy-bundle": "./js/webampLazy.js",
+ "lazy-bundle.min": "./js/webampLazy.js"
},
output: {
path: path.resolve(__dirname, "../built"),
diff --git a/js/actionCreators/files.js b/js/actionCreators/files.js
index e0aaf14e..f5c0fdca 100644
--- a/js/actionCreators/files.js
+++ b/js/actionCreators/files.js
@@ -80,10 +80,23 @@ export function loadFilesFromReferences(
}
export function setSkinFromArrayBuffer(arrayBuffer) {
- return async dispatch => {
+ return async (dispatch, getState, { requireJSZip }) => {
+ if (!requireJSZip) {
+ alert("Webamp has not been configured to support custom skins.");
+ return;
+ }
dispatch({ type: LOADING });
+ let JSZip;
try {
- const skinData = await skinParser(arrayBuffer);
+ JSZip = await requireJSZip();
+ } catch (e) {
+ console.error(e);
+ dispatch({ type: LOADED });
+ alert("Failed to load the skin parser.");
+ return;
+ }
+ try {
+ const skinData = await skinParser(arrayBuffer, JSZip);
dispatch({
type: SET_SKIN_DATA,
skinImages: skinData.images,
@@ -250,10 +263,10 @@ function queueFetchingMediaTags(id) {
}
export function fetchMediaTags(file, id) {
- return async dispatch => {
+ return async (dispatch, getState, { requireJSMediaTags }) => {
dispatch({ type: MEDIA_TAG_REQUEST_INITIALIZED, id });
try {
- const data = await genMediaTags(file);
+ const data = await genMediaTags(file, await requireJSMediaTags());
// There's more data here, but we don't have a use for it yet:
// https://github.com/aadsm/jsmediatags#shortcuts
const { artist, title, picture } = data.tags;
diff --git a/js/fileUtils.js b/js/fileUtils.js
index 84e85482..7fc18c4f 100644
--- a/js/fileUtils.js
+++ b/js/fileUtils.js
@@ -1,6 +1,6 @@
import invariant from "invariant";
-export function genMediaTags(file) {
+export function genMediaTags(file, jsmediatags) {
invariant(
file != null,
"Attempted to get the tags of media file without passing a file"
@@ -9,26 +9,22 @@ export function genMediaTags(file) {
if (typeof file === "string" && !/^[a-z]+:\/\//i.test(file)) {
file = `${location.protocol}//${location.host}${location.pathname}${file}`;
}
- return new Promise((resolve, reject) => {
- require.ensure(
- ["jsmediatags/dist/jsmediatags"],
- require => {
- const jsmediatags = require("jsmediatags/dist/jsmediatags");
- try {
- jsmediatags.read(file, { onSuccess: resolve, onError: reject });
- } catch (e) {
- // Possibly jsmediatags could not find a parser for this file?
- // Nothing to do.
- // Consider removing this after https://github.com/aadsm/jsmediatags/issues/83 is resolved.
- reject(e);
- }
- },
- () => {
- // The dependency failed to load
- },
- "jsmediatags"
- );
- });
+ return new Promise(
+ (resolve, reject) => {
+ try {
+ jsmediatags.read(file, { onSuccess: resolve, onError: reject });
+ } catch (e) {
+ // Possibly jsmediatags could not find a parser for this file?
+ // Nothing to do.
+ // Consider removing this after https://github.com/aadsm/jsmediatags/issues/83 is resolved.
+ reject(e);
+ }
+ },
+ () => {
+ // The dependency failed to load
+ },
+ "jsmediatags"
+ );
}
export function genMediaDuration(url) {
diff --git a/js/index.js b/js/index.js
index 491a4e51..92153d39 100644
--- a/js/index.js
+++ b/js/index.js
@@ -12,7 +12,7 @@ import zaxon from "../skins/ZaxonRemake1-0.wsz";
import green from "../skins/Green-Dimension-V2.wsz";
import MilkdropWindow from "./components/MilkdropWindow";
import screenshotInitialState from "./screenshotInitialState";
-import Webamp from "./webamp";
+import WebampLazy from "./webampLazy";
import {
STEP_MARQUEE,
UPDATE_TIME_ELAPSED,
@@ -34,6 +34,38 @@ import {
disableMarquee
} from "./config";
+const requireJSZip = () => {
+ return new Promise((resolve, reject) => {
+ require.ensure(
+ ["jszip/dist/jszip"],
+ require => {
+ resolve(require("jszip/dist/jszip"));
+ },
+ e => {
+ console.error("Error loading JSZip", e);
+ reject(e);
+ },
+ "jszip"
+ );
+ });
+};
+
+const requireJSMediaTags = () => {
+ return new Promise((resolve, reject) => {
+ require.ensure(
+ ["jsmediatags/dist/jsmediatags"],
+ require => {
+ resolve(require("jsmediatags/dist/jsmediatags"));
+ },
+ e => {
+ console.error("Error loading jsmediatags", e);
+ reject(e);
+ },
+ "jsmediatags"
+ );
+ });
+};
+
const NOISY_ACTION_TYPES = new Set([
STEP_MARQUEE,
UPDATE_TIME_ELAPSED,
@@ -122,7 +154,7 @@ Raven.context(() => {
if (screenshot) {
document.getElementsByClassName("about")[0].style.visibility = "hidden";
}
- if (!Webamp.browserIsSupported()) {
+ if (!WebampLazy.browserIsSupported()) {
document.getElementById("browser-compatibility").style.display = "block";
document.getElementById("app").style.visibility = "hidden";
return;
@@ -165,7 +197,7 @@ Raven.context(() => {
const initialSkin = !skinUrl ? null : { url: skinUrl };
- const webamp = new Webamp({
+ const webamp = new WebampLazy({
initialSkin,
initialTracks: screenshot ? null : initialTracks,
availableSkins: [
@@ -191,6 +223,8 @@ Raven.context(() => {
}
],
enableHotkeys: true,
+ requireJSZip,
+ requireJSMediaTags,
__extraWindows,
__initialWindowLayout,
__initialState: screenshot ? screenshotInitialState : initialState,
diff --git a/js/skinParser.js b/js/skinParser.js
index fb3c5d0f..b6e27a1e 100644
--- a/js/skinParser.js
+++ b/js/skinParser.js
@@ -3,21 +3,6 @@ import regionParser from "./regionParser";
import { LETTERS, DEFAULT_SKIN } from "./constants";
import { parseViscolors, parseIni, getFileExtension } from "./utils";
-const getJSZip = () => {
- return new Promise(resolve => {
- require.ensure(
- ["jszip/dist/jszip"],
- require => {
- resolve(require("jszip/dist/jszip"));
- },
- e => {
- console.error("Error loading JSZip", e);
- },
- "jszip"
- );
- });
-};
-
const shallowMerge = objs =>
objs.reduce((prev, img) => Object.assign(prev, img), {});
@@ -249,8 +234,7 @@ async function genGenTextSprites(zip) {
}
// A promise that, given an array buffer returns a skin style object
-async function skinParser(zipFileBuffer) {
- const JSZip = await getJSZip();
+async function skinParser(zipFileBuffer, JSZip) {
const zip = await JSZip.loadAsync(zipFileBuffer);
const [
colors,
diff --git a/js/store.js b/js/store.js
index f1913bd9..2e51367b 100644
--- a/js/store.js
+++ b/js/store.js
@@ -10,12 +10,13 @@ const compose = composeWithDevTools({
actionsBlacklist: [UPDATE_TIME_ELAPSED, STEP_MARQUEE]
});
-const getStore = (
+export default function(
media,
actionEmitter,
customMiddlewares = [],
- stateOverrides
-) => {
+ stateOverrides,
+ extras
+) {
let initialState;
if (stateOverrides) {
initialState = merge(
@@ -36,7 +37,7 @@ const getStore = (
compose(
applyMiddleware(
...[
- thunk,
+ thunk.withExtraArgument(extras),
mediaMiddleware(media),
emitterMiddleware,
...customMiddlewares
@@ -44,6 +45,4 @@ const getStore = (
)
)
);
-};
-
-export default getStore;
+}
diff --git a/js/webamp.js b/js/webamp.js
index 1fd8bbc2..e065f809 100644
--- a/js/webamp.js
+++ b/js/webamp.js
@@ -1,224 +1,14 @@
-import React from "react";
-import { render } from "react-dom";
-import { Provider } from "react-redux";
-
-import getStore from "./store";
-import App from "./components/App";
-import Hotkeys from "./hotkeys";
-import Media from "./media";
-import { getTrackCount, getTracks } from "./selectors";
-import {
- setSkinFromUrl,
- loadMediaFiles,
- setWindowSize,
- loadFilesFromReferences
-} from "./actionCreators";
-import { LOAD_STYLE } from "./constants";
-import { uniqueId, objectMap, objectForEach } from "./utils";
-
-import {
- SET_AVAILABLE_SKINS,
- NETWORK_CONNECTED,
- NETWORK_DISCONNECTED,
- CLOSE_WINAMP,
- MINIMIZE_WINAMP,
- ADD_GEN_WINDOW,
- UPDATE_WINDOW_POSITIONS,
- LOADED,
- REGISTER_VISUALIZER,
- SET_Z_INDEX,
- SET_MEDIA
-} from "./actionTypes";
-import Emitter from "./emitter";
-
-import "../css/base-skin.min.css";
-
-// Return a promise that resolves when the store matches a predicate.
-const storeHas = (store, predicate) =>
- new Promise(resolve => {
- if (predicate(store.getState())) {
- resolve();
- return;
- }
- const unsubscribe = store.subscribe(() => {
- if (predicate(store.getState())) {
- resolve();
- unsubscribe();
- }
- });
- });
-
-class Winamp {
- static browserIsSupported() {
- const supportsAudioApi = !!(
- window.AudioContext || window.webkitAudioContext
- );
- const supportsCanvas = !!window.document.createElement("canvas").getContext;
- const supportsPromises = typeof Promise !== "undefined";
- return supportsAudioApi && supportsCanvas && supportsPromises;
- }
+import JSZip from "jszip";
+import jsmediatags from "jsmediatags";
+import WebampLazy from "./webampLazy";
+class Winamp extends WebampLazy {
constructor(options) {
- this._actionEmitter = new Emitter();
- this.options = options;
- const {
- initialTracks,
- initialSkin,
- avaliableSkins, // Old misspelled name
- availableSkins,
- enableHotkeys = false,
- zIndex,
- __extraWindows
- } = this.options;
-
- this.media = new Media();
- this.store = getStore(
- this.media,
- this._actionEmitter,
- this.options.__customMiddlewares,
- this.options.__initialState
- );
- this.store.dispatch({
- type: navigator.onLine ? NETWORK_CONNECTED : NETWORK_DISCONNECTED
+ super({
+ ...options,
+ requireJSZip: () => JSZip,
+ requireJSMediaTags: () => jsmediatags
});
-
- if (true) {
- const fileInput = document.createElement("input");
- fileInput.id = "webamp-file-input";
- fileInput.style.display = "none";
- fileInput.type = "file";
- fileInput.value = null;
- fileInput.addEventListener("change", e => {
- this.store.dispatch(loadFilesFromReferences(e.target.files));
- });
- document.body.appendChild(fileInput);
- }
-
- if (zIndex != null) {
- this.store.dispatch({ type: SET_Z_INDEX, zIndex });
- }
-
- this.genWindows = [];
- if (__extraWindows) {
- this.genWindows = __extraWindows.map(genWindow => ({
- id: genWindow.id || `${genWindow.title}-${uniqueId()}`,
- ...genWindow
- }));
-
- __extraWindows.forEach(genWindow => {
- if (genWindow.isVisualizer) {
- this.store.dispatch({ type: REGISTER_VISUALIZER, id: genWindow.id });
- }
- });
- }
-
- this.genWindows.forEach(genWindow => {
- this.store.dispatch({
- type: ADD_GEN_WINDOW,
- windowId: genWindow.id,
- title: genWindow.title,
- open: genWindow.open
- });
- });
-
- window.addEventListener("online", () =>
- this.store.dispatch({ type: NETWORK_CONNECTED })
- );
- window.addEventListener("offline", () =>
- this.store.dispatch({ type: NETWORK_DISCONNECTED })
- );
-
- if (initialSkin) {
- this.store.dispatch(setSkinFromUrl(initialSkin.url));
- } else {
- // We are using the default skin.
- this.store.dispatch({ type: LOADED });
- }
-
- if (initialTracks) {
- this.appendTracks(initialTracks);
- }
-
- if (avaliableSkins != null) {
- console.warn(
- "The misspelled option `avaliableSkins` is deprecated. Please use `availableSkins` instead."
- );
- this.store.dispatch({ type: SET_AVAILABLE_SKINS, skins: avaliableSkins });
- } else if (availableSkins != null) {
- this.store.dispatch({ type: SET_AVAILABLE_SKINS, skins: availableSkins });
- }
-
- const layout = options.__initialWindowLayout;
- if (layout != null) {
- objectForEach(layout, (w, windowId) => {
- if (w.size != null) {
- this.store.dispatch(setWindowSize(windowId, w.size));
- }
- });
- this.store.dispatch({
- type: UPDATE_WINDOW_POSITIONS,
- positions: objectMap(layout, w => w.position)
- });
- }
-
- if (enableHotkeys) {
- new Hotkeys(this.store.dispatch);
- }
- }
-
- // Append this array of tracks to the end of the current playlist.
- appendTracks(tracks) {
- const nextIndex = getTrackCount(this.store.getState());
- this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.BUFFER, nextIndex));
- }
-
- // Replace any existing tracks with this array of tracks, and begin playing.
- setTracksToPlay(tracks) {
- this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.PLAY));
- }
-
- onClose(cb) {
- return this._actionEmitter.on(CLOSE_WINAMP, cb);
- }
-
- onTrackDidChange(cb) {
- return this._actionEmitter.on(SET_MEDIA, action => {
- const tracks = getTracks(this.store.getState());
- const track = tracks[action.id];
- if (track == null) {
- return;
- }
- cb({ url: track.url });
- });
- }
-
- onMinimize(cb) {
- return this._actionEmitter.on(MINIMIZE_WINAMP, cb);
- }
-
- async skinIsLoaded() {
- // Wait for the skin to load.
- return storeHas(this.store, state => !state.display.loading);
- }
-
- async renderWhenReady(node) {
- await this.skinIsLoaded();
- const genWindowComponents = {};
- this.genWindows.forEach(w => {
- genWindowComponents[w.id] = w.Component;
- });
-
- render(
-
-
- ,
- node
- );
}
}
diff --git a/js/webampLazy.js b/js/webampLazy.js
new file mode 100644
index 00000000..753f9acf
--- /dev/null
+++ b/js/webampLazy.js
@@ -0,0 +1,231 @@
+import React from "react";
+import { render } from "react-dom";
+import { Provider } from "react-redux";
+
+import getStore from "./store";
+import App from "./components/App";
+import Hotkeys from "./hotkeys";
+import Media from "./media";
+import { getTrackCount, getTracks } from "./selectors";
+import {
+ setSkinFromUrl,
+ loadMediaFiles,
+ setWindowSize,
+ loadFilesFromReferences
+} from "./actionCreators";
+import { LOAD_STYLE } from "./constants";
+import { uniqueId, objectMap, objectForEach } from "./utils";
+
+import {
+ SET_AVAILABLE_SKINS,
+ NETWORK_CONNECTED,
+ NETWORK_DISCONNECTED,
+ CLOSE_WINAMP,
+ MINIMIZE_WINAMP,
+ ADD_GEN_WINDOW,
+ UPDATE_WINDOW_POSITIONS,
+ LOADED,
+ REGISTER_VISUALIZER,
+ SET_Z_INDEX,
+ SET_MEDIA
+} from "./actionTypes";
+import Emitter from "./emitter";
+
+import "../css/base-skin.min.css";
+
+// Return a promise that resolves when the store matches a predicate.
+const storeHas = (store, predicate) =>
+ new Promise(resolve => {
+ if (predicate(store.getState())) {
+ resolve();
+ return;
+ }
+ const unsubscribe = store.subscribe(() => {
+ if (predicate(store.getState())) {
+ resolve();
+ unsubscribe();
+ }
+ });
+ });
+
+class Winamp {
+ static browserIsSupported() {
+ const supportsAudioApi = !!(
+ window.AudioContext || window.webkitAudioContext
+ );
+ const supportsCanvas = !!window.document.createElement("canvas").getContext;
+ const supportsPromises = typeof Promise !== "undefined";
+ return supportsAudioApi && supportsCanvas && supportsPromises;
+ }
+
+ constructor(options) {
+ this._actionEmitter = new Emitter();
+ this.options = options;
+ const {
+ initialTracks,
+ initialSkin,
+ avaliableSkins, // Old misspelled name
+ availableSkins,
+ enableHotkeys = false,
+ zIndex,
+ requireJSZip,
+ requireJSMediaTags,
+ __extraWindows
+ } = this.options;
+
+ // TODO: Validate required options
+
+ this.media = new Media();
+ this.store = getStore(
+ this.media,
+ this._actionEmitter,
+ this.options.__customMiddlewares,
+ this.options.__initialState,
+ { requireJSZip, requireJSMediaTags }
+ );
+ this.store.dispatch({
+ type: navigator.onLine ? NETWORK_CONNECTED : NETWORK_DISCONNECTED
+ });
+
+ if (true) {
+ const fileInput = document.createElement("input");
+ fileInput.id = "webamp-file-input";
+ fileInput.style.display = "none";
+ fileInput.type = "file";
+ fileInput.value = null;
+ fileInput.addEventListener("change", e => {
+ this.store.dispatch(loadFilesFromReferences(e.target.files));
+ });
+ document.body.appendChild(fileInput);
+ }
+
+ if (zIndex != null) {
+ this.store.dispatch({ type: SET_Z_INDEX, zIndex });
+ }
+
+ this.genWindows = [];
+ if (__extraWindows) {
+ this.genWindows = __extraWindows.map(genWindow => ({
+ id: genWindow.id || `${genWindow.title}-${uniqueId()}`,
+ ...genWindow
+ }));
+
+ __extraWindows.forEach(genWindow => {
+ if (genWindow.isVisualizer) {
+ this.store.dispatch({ type: REGISTER_VISUALIZER, id: genWindow.id });
+ }
+ });
+ }
+
+ this.genWindows.forEach(genWindow => {
+ this.store.dispatch({
+ type: ADD_GEN_WINDOW,
+ windowId: genWindow.id,
+ title: genWindow.title,
+ open: genWindow.open
+ });
+ });
+
+ window.addEventListener("online", () =>
+ this.store.dispatch({ type: NETWORK_CONNECTED })
+ );
+ window.addEventListener("offline", () =>
+ this.store.dispatch({ type: NETWORK_DISCONNECTED })
+ );
+
+ if (initialSkin) {
+ this.store.dispatch(setSkinFromUrl(initialSkin.url));
+ } else {
+ // We are using the default skin.
+ this.store.dispatch({ type: LOADED });
+ }
+
+ if (initialTracks) {
+ this.appendTracks(initialTracks);
+ }
+
+ if (avaliableSkins != null) {
+ console.warn(
+ "The misspelled option `avaliableSkins` is deprecated. Please use `availableSkins` instead."
+ );
+ this.store.dispatch({ type: SET_AVAILABLE_SKINS, skins: avaliableSkins });
+ } else if (availableSkins != null) {
+ this.store.dispatch({ type: SET_AVAILABLE_SKINS, skins: availableSkins });
+ }
+
+ const layout = options.__initialWindowLayout;
+ if (layout != null) {
+ objectForEach(layout, (w, windowId) => {
+ if (w.size != null) {
+ this.store.dispatch(setWindowSize(windowId, w.size));
+ }
+ });
+ this.store.dispatch({
+ type: UPDATE_WINDOW_POSITIONS,
+ positions: objectMap(layout, w => w.position)
+ });
+ }
+
+ if (enableHotkeys) {
+ new Hotkeys(this.store.dispatch);
+ }
+ }
+
+ // Append this array of tracks to the end of the current playlist.
+ appendTracks(tracks) {
+ const nextIndex = getTrackCount(this.store.getState());
+ this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.BUFFER, nextIndex));
+ }
+
+ // Replace any existing tracks with this array of tracks, and begin playing.
+ setTracksToPlay(tracks) {
+ this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.PLAY));
+ }
+
+ onClose(cb) {
+ return this._actionEmitter.on(CLOSE_WINAMP, cb);
+ }
+
+ onTrackDidChange(cb) {
+ return this._actionEmitter.on(SET_MEDIA, action => {
+ const tracks = getTracks(this.store.getState());
+ const track = tracks[action.id];
+ if (track == null) {
+ return;
+ }
+ cb({ url: track.url });
+ });
+ }
+
+ onMinimize(cb) {
+ return this._actionEmitter.on(MINIMIZE_WINAMP, cb);
+ }
+
+ async skinIsLoaded() {
+ // Wait for the skin to load.
+ return storeHas(this.store, state => !state.display.loading);
+ }
+
+ async renderWhenReady(node) {
+ await this.skinIsLoaded();
+ const genWindowComponents = {};
+ this.genWindows.forEach(w => {
+ genWindowComponents[w.id] = w.Component;
+ });
+
+ render(
+
+
+ ,
+ node
+ );
+ }
+}
+
+export default Winamp;
+module.exports = Winamp;
diff --git a/package.json b/package.json
index b6e8e228..2bd98b5c 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"files": [
"built/webamp.bundle.js",
"built/webamp.bundle.min.js",
- "index.d.ts"
+ "built/webamp-lazy.bundle.js",
+ "built/webamp-lazy.bundle.min.js"
],
"scripts": {
"lint": "eslint .",