mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 10:15:31 +00:00
Add onClose method
This commit is contained in:
parent
44d0b9e9cb
commit
da792c16b9
6 changed files with 80 additions and 27 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
22
js/emitter.js
Normal 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 = {};
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
js/store.js
18
js/store.js
|
|
@ -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
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
15
js/winamp.js
15
js/winamp.js
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue