POC: Try adding rexux-observable

I don't know how we'll feasibly solve the multide of potential race conditons and async clean up memory leaks without something like rxjs. So, this is a trial to see the following:

1. How much does it increase bundle size?
2. How effective is typechecking?
3. Does it solve some of the difficult cancelation issues we have?
This commit is contained in:
Jordan Eldredge 2019-05-20 17:09:04 -07:00
parent f3358475fb
commit 58fd75c8a1
7 changed files with 100 additions and 36 deletions

View file

@ -7,7 +7,6 @@ import {
promptForFileReferences,
genArrayBufferFromFileReference,
genMediaDuration,
genMediaTags,
} from "../fileUtils";
import skinParser from "../skinParser";
import {
@ -23,8 +22,6 @@ import {
BUFFER_TRACK,
SET_MEDIA_TAGS,
SET_MEDIA_DURATION,
MEDIA_TAG_REQUEST_INITIALIZED,
MEDIA_TAG_REQUEST_FAILED,
SET_SKIN_DATA,
LOADED,
LOADING,
@ -33,7 +30,7 @@ import LoadQueue from "../loadQueue";
import { removeAllTracks } from "./playlist";
import { setPreamp, setEqBand } from "./equalizer";
import { LoadStyle, Thunk, Track, EqfPreset, SkinData } from "../types";
import { LoadStyle, Thunk, Action, Track, EqfPreset, SkinData } from "../types";
// Lower is better
const DURATION_VISIBLE_PRIORITY = 5;
@ -297,36 +294,8 @@ function queueFetchingMediaTags(id: number): Thunk {
};
}
export function fetchMediaTags(file: string | Blob, id: number): Thunk {
return async (dispatch, getState, { requireMusicMetadata }) => {
dispatch({ type: MEDIA_TAG_REQUEST_INITIALIZED, id });
try {
const metadata = await genMediaTags(file, await requireMusicMetadata());
// There's more data here, but we don't have a use for it yet:
const { artist, title, album, picture } = metadata.common;
const { numberOfChannels, bitrate, sampleRate } = metadata.format;
let albumArtUrl = null;
if (picture && picture.length >= 1) {
const byteArray = new Uint8Array(picture[0].data);
const blob = new Blob([byteArray], { type: picture[0].format });
albumArtUrl = URL.createObjectURL(blob);
}
dispatch({
type: SET_MEDIA_TAGS,
artist: artist ? artist : "",
title: title ? title : "",
album,
albumArtUrl,
numberOfChannels,
bitrate,
sampleRate,
id,
});
} catch (e) {
dispatch({ type: MEDIA_TAG_REQUEST_FAILED, id });
}
};
export function fetchMediaTags(file: string | Blob, id: number): Action {
return { type: "FETCH_MEDIA_TAGS", file, id };
}
export function setEqFromFileReference(fileReference: File): Thunk {

View file

@ -87,3 +87,4 @@ export const TOGGLE_RANDOMIZE_PRESETS = "TOGGLE_RANDOMIZE_PRESETS";
export const TOGGLE_PRESET_CYCLING = "TOGGLE_PRESET_CYCLING";
export const SCHEDULE_MILKDROP_MESSAGE = "SCHEDULE_MILKDROP_MESSAGE";
export const SET_MILKDROP_FULLSCREEN = "SET_MILKDROP_FULLSCREEN";
export const FETCH_MEDIA_TAGS = "FETCH_MEDIA_TAGS";

66
js/epics.ts Normal file
View file

@ -0,0 +1,66 @@
import { Observable, of, defer, concat } from "rxjs";
import { filter, mergeMap, catchError } from "rxjs/operators";
import { Action, AppState, Extras } from "./types";
import {
MEDIA_TAG_REQUEST_INITIALIZED,
SET_MEDIA_TAGS,
MEDIA_TAG_REQUEST_FAILED,
FETCH_MEDIA_TAGS,
} from "./actionTypes";
import { genMediaTags } from "./fileUtils";
export const fetchMediaTagsEpic = (
actions: Observable<Action>,
states: Observable<AppState>,
{ requireMusicMetadata }: Extras
): Observable<Action> => {
return actions.pipe(
filter(action => action.type === FETCH_MEDIA_TAGS),
mergeMap(action => {
// TODO: Check out https://github.com/piotrwitek/typesafe-actions
if (action.type !== FETCH_MEDIA_TAGS) {
throw new Error("Invalid");
}
const { file, id } = action;
return concat(
of({
type: MEDIA_TAG_REQUEST_INITIALIZED,
id,
} as const),
defer(async () => {
const musicMetadata = await requireMusicMetadata();
const metadata = await genMediaTags(file, musicMetadata);
// There's more data here, but we don't have a use for it yet:
const { artist, title, album, picture } = metadata.common;
const { numberOfChannels, bitrate, sampleRate } = metadata.format;
let albumArtUrl = null;
if (picture && picture.length >= 1) {
const byteArray = new Uint8Array(picture[0].data);
const blob = new Blob([byteArray], { type: picture[0].format });
albumArtUrl = URL.createObjectURL(blob);
}
return {
type: SET_MEDIA_TAGS,
artist: artist ? artist : "",
title: title ? title : "",
album,
albumArtUrl,
numberOfChannels,
bitrate,
sampleRate,
id,
} as const;
}).pipe(
catchError(e => {
console.error("Failed to fetch media tags", e);
return of({
type: MEDIA_TAG_REQUEST_FAILED,
id,
} as const);
})
)
);
})
);
};

View file

@ -8,6 +8,8 @@ import { UPDATE_TIME_ELAPSED, STEP_MARQUEE } from "./actionTypes";
import Media from "./media";
import Emitter from "./emitter";
import { Extras, Dispatch, Action, AppState, Middleware } from "./types";
import { createEpicMiddleware, combineEpics } from "redux-observable";
import * as Epics from "./epics";
// TODO: Move to demo
const compose = composeWithDevTools({
@ -34,10 +36,15 @@ export default function(
return next(action);
};
const epicMiddleware = createEpicMiddleware({
dependencies: extras,
});
const enhancer = compose(
applyMiddleware(
...[
thunk.withExtraArgument(extras),
epicMiddleware,
mediaMiddleware(media),
emitterMiddleware,
...customMiddlewares,
@ -50,5 +57,7 @@ export default function(
const store = initialState
? createStore(reducer, initialState, enhancer)
: createStore(reducer, enhancer);
epicMiddleware.run(combineEpics(...Object.values(Epics)));
return store;
}

View file

@ -509,7 +509,12 @@ export type Action =
index: number;
transitionType: TransitionType;
}
| { type: "TOGGLE_PRESET_OVERLAY" };
| { type: "TOGGLE_PRESET_OVERLAY" }
| {
type: "FETCH_MEDIA_TAGS";
file: string | Blob;
id: number;
};
export type MediaTagRequestStatus =
| "INITIALIZED"

View file

@ -154,7 +154,9 @@
},
"dependencies": {
"eslint-plugin-react-hooks": "^1.5.1",
"fscreen": "^1.0.2"
"fscreen": "^1.0.2",
"redux-observable": "^1.1.0",
"rxjs": "^6.5.2"
},
"bundlesize": [
{

View file

@ -9079,6 +9079,11 @@ redux-devtools-extension@^2.13.2:
resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1"
integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg==
redux-observable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/redux-observable/-/redux-observable-1.1.0.tgz#323a8fe53e89fdb519be2807b55f08e21c13e6f1"
integrity sha512-G0nxgmTZwTK3Z3KoQIL8VQu9n0YCUwEP3wc3zxKQ8zAZm+iYkoZvBqAnBJfLi4EsD1E64KR4s4jFH/dFXpV9Og==
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
@ -9459,6 +9464,13 @@ rxjs@^6.4.0:
dependencies:
tslib "^1.9.0"
rxjs@^6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"