Add onClose method

This commit is contained in:
Jordan Eldredge 2018-04-03 06:50:06 -07:00
parent 44d0b9e9cb
commit da792c16b9
6 changed files with 80 additions and 27 deletions

View file

@ -1,6 +1,7 @@
## (next version)
* Deprecated: The misspelled `Winamp` construction option `avaliableSkins` has been deprecated in favor of `availableSkins`. `avaliableSkins` will continue to work, but will log a deprecation warning. [#533](https://github.com/captbaritone/winamp2-js/pull/533) by [@remigallego](https://github.com/remigallego)
* Added: `winamp.onClose()`.
## 0.0.6

View file

@ -193,6 +193,19 @@ winamp.renderWhenReady(container).then(() => {
});
```
### `onClose(callback)`
A callback which will be called when Winamp2-js is closed. Returns an "unsubscribe" function.
```JavaScript
const unsubscribe = winamp.onClose(() => {
console.log("Winamp closed");
});
// If at some point in the future you want to stop listening to these events:
unsubscribe();
```
## Notes
* Internet Explorer is not supported.
@ -200,3 +213,7 @@ winamp.renderWhenReady(container).then(() => {
* Winamp2-js' HTML contains somewhat generic IDs and class names. If you have CSS on your page that is not namespaced, it may accidently be applied to Winamp2-js. If this happens please reach out. I may be able to resolve it.
* Skin and audio URLs are subject to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). Please ensure they are either served from the same domain, or that the other domain is served with the correct headers.
* Please reach out to me. I'd love to help you set it up, and understand how it's being used. I plan to expand this API as I learn how people want to use it.
```
```

22
js/emitter.js Normal file
View file

@ -0,0 +1,22 @@
export default class Emitter {
on(event, callback) {
const eventListeners = this._listeners[event] || [];
eventListeners.push(callback);
this._listeners[event] = eventListeners;
const unsubscribe = () => {
this._listeners[event] = eventListeners.filter(cb => cb !== callback);
};
return unsubscribe;
}
trigger(event) {
const callbacks = this._listeners[event];
if (callbacks) {
callbacks.forEach(cb => cb());
}
}
constructor() {
this._listeners = {};
}
}

View file

@ -1,3 +1,4 @@
import Emitter from "../emitter";
const STATUS = {
PLAYING: "PLAYING",
STOPPED: "STOPPED",
@ -5,25 +6,12 @@ const STATUS = {
};
export default class ElementSource {
on(event, callback) {
const eventListeners = this._listeners[event] || [];
eventListeners.push(callback);
this._listeners[event] = eventListeners;
const unsubscribe = () => {
this._listeners[event] = eventListeners.filter(cb => cb !== callback);
};
return unsubscribe;
}
trigger(event) {
const callbacks = this._listeners[event];
if (callbacks) {
callbacks.forEach(cb => cb());
}
on(eventType, cb) {
return this._emitter.on(eventType, cb);
}
constructor(context, destination) {
this._listeners = {};
this._emitter = new Emitter();
this._context = context;
this._destination = destination;
this._audio = document.createElement("audio");
@ -36,18 +24,18 @@ export default class ElementSource {
});
this._audio.addEventListener("durationchange", () => {
this.trigger("loaded");
this._emitter.trigger("loaded");
this._setStalled(false);
});
this._audio.addEventListener("ended", () => {
this.trigger("ended");
this._emitter.trigger("ended");
this._setStatus(STATUS.STOPPED);
});
// TODO: Throttle to 50 (if needed)
this._audio.addEventListener("timeupdate", () => {
this.trigger("positionChange");
this._emitter.trigger("positionChange");
});
this._audio.addEventListener("error", e => {
@ -76,7 +64,7 @@ export default class ElementSource {
// Rather than just geting stuck in this error state, we can just pretend this is
// the end of the track.
this.trigger("ended");
this._emitter.trigger("ended");
this._setStatus(STATUS.STOPPED);
});
@ -86,7 +74,7 @@ export default class ElementSource {
_setStalled(stalled) {
this._stalled = stalled;
this.trigger("stallChanged");
this._emitter.trigger("stallChanged");
}
disconnect() {
@ -128,7 +116,7 @@ export default class ElementSource {
time = Math.min(time, this.getDuration());
time = Math.max(time, 0);
this._audio.currentTime = time;
this.trigger("positionChange");
this._emitter.trigger("positionChange");
}
getStalled() {
@ -160,6 +148,6 @@ export default class ElementSource {
_setStatus(status) {
this._status = status;
this.trigger("statusChange");
this._emitter.trigger("statusChange");
}
}

View file

@ -11,7 +11,7 @@ const compose = composeWithDevTools({
actionsBlacklist: [UPDATE_TIME_ELAPSED, STEP_MARQUEE]
});
const getStore = (media, stateOverrides) => {
const getStore = (media, actionEmitter, stateOverrides) => {
let initialState;
if (stateOverrides) {
initialState = merge(
@ -19,10 +19,24 @@ const getStore = (media, stateOverrides) => {
stateOverrides
);
}
// eslint-disable-next-line no-unused-vars
const emitterMiddleware = store => next => action => {
actionEmitter.trigger(action.type);
return next(action);
};
return createStore(
reducer,
initialState,
compose(applyMiddleware(thunk, mediaMiddleware(media), analyticsMiddleware))
compose(
applyMiddleware(
thunk,
mediaMiddleware(media),
emitterMiddleware,
analyticsMiddleware
)
)
);
};

View file

@ -13,8 +13,10 @@ import { LOAD_STYLE } from "./constants";
import {
SET_AVAILABLE_SKINS,
NETWORK_CONNECTED,
NETWORK_DISCONNECTED
NETWORK_DISCONNECTED,
CLOSE_WINAMP
} from "./actionTypes";
import Emitter from "./emitter";
// Return a promise that resolves when the store matches a predicate.
const storeHas = (store, predicate) =>
@ -42,6 +44,7 @@ class Winamp {
}
constructor(options) {
this._actionEmitter = new Emitter();
this.options = options;
const {
initialTracks,
@ -51,7 +54,11 @@ class Winamp {
} = this.options;
this.media = new Media();
this.store = getStore(this.media, this.options.__initialState);
this.store = getStore(
this.media,
this._actionEmitter,
this.options.__initialState
);
this.store.dispatch({
type: navigator.onLine ? NETWORK_CONNECTED : NETWORK_DISCONNECTED
});
@ -94,6 +101,10 @@ class Winamp {
this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.PLAY));
}
onClose(cb) {
return this._actionEmitter.on(CLOSE_WINAMP, cb);
}
async renderWhenReady(node) {
// Wait for the skin to load.
await storeHas(this.store, state => !state.display.loading);