Skip to content

Commit

Permalink
Major improvements (#210)
Browse files Browse the repository at this point in the history
Major release
  • Loading branch information
ch1ller0 authored Feb 18, 2021
1 parent 8805c92 commit 625acd7
Show file tree
Hide file tree
Showing 50 changed files with 1,174 additions and 701 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ node_modules
.DS_Store
lib
coverage
yarn-error.log
yarn-error.log
.clinic
examples/music/.test
58 changes: 33 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Radio engine for NodeJS
[![build](https://img.shields.io/circleci/build/github/Kefir100/fridgefm-radio-core.svg)](https://circleci.com/gh/Kefir100/fridgefm-radio-core)
[![coverage](https://img.shields.io/codecov/c/gh/Kefir100/fridgefm-radio-core.svg)](https://codecov.io/gh/Kefir100/fridgefm-radio-core)
[![build](https://img.shields.io/circleci/build/github/ch1ller0/fridgefm-radio-core.svg)](https://circleci.com/gh/ch1ller0/fridgefm-radio-core)
[![coverage](https://img.shields.io/codecov/c/gh/ch1ller0/fridgefm-radio-core.svg)](https://codecov.io/gh/ch1ller0/fridgefm-radio-core)
[![npm](https://img.shields.io/npm/dw/@fridgefm/radio-core.svg)](https://www.npmjs.com/package/@fridgefm/radio-core)
![GitHub](https://img.shields.io/github/license/kefir100/fridgefm-radio-core.svg)
![GitHub](https://img.shields.io/github/license/ch1ller0/fridgefm-radio-core.svg)
![node](https://img.shields.io/node/v/@fridgefm/radio-core.svg)

## Usage
Expand Down Expand Up @@ -37,6 +37,17 @@ station.start();
/>
```

## Station constructor
Creating a station is as simple as
```javascript
const myAwesomeStation = new Station({
verbose: false, // if true - enables verbose logging (great for debugging),
responseHeaders: { // in case you want custom response headers for your endpoint
'icy-genre': 'jazz'
}
})
```

## Station methods
### Public methods that should be exposed to users
`connectListener` connects real users to your station
Expand All @@ -61,42 +72,39 @@ station.next();
```javascript
station.getPlaylist();
```
`shufflePlaylist` shuffles playlist once
You may want to pass your own sorting function, defaults to random shuffle
```javascript
station.shufflePlaylist(sortingFunction);
```
`rearrangePlaylist` just returns you the entire playlist
```javascript
// the example moves the first track to the 5th position in playlist
station.rearrangePlaylist(0, 4);
```

## Station events
#### `nextTrack`
Station emits several events - they are available via
```javascript
const { PUBLIC_EVENTS } = require('@fridgefm/radio-core')
```
#### `NEXT_TRACK`
event fires when track changes
useful for getting to know when exactly the track changed and what track that is
```javascript
station.on('nextTrack', (track) => { console.log(track) });
station.on(PUBLIC_EVENTS.NEXT_TRACK, (track) => {
const result = await track.getMetaAsync();
console.log(result)
})
```

#### `start`
event fires on station start
#### `START`
Event fires on station start
```javascript
station.on('start', () => { console.log('Station started') });
station.on(PUBLIC_EVENTS.START, () => { console.log('Station started') });
```

#### `restart`
event fires on station restart (when playlist is drained and new one is created)
#### `RESTART`
Event fires on station restart (when playlist is drained and new one is created)
it might be a nice time to shuffle your playlist for example
```javascript
station.on('restart', () => { station.shufflePlaylist() });
station.on(PUBLIC_EVENTS.RESTART, () => { /* do something*/ });
```

#### `error`
event fires when there is some error
#### `ERROR`
Event fires when there is some error. You `should` add this handler - otherwise any error will be treated as unhandled (causing `process.exit` depending on Node version)
```javascript
station.on('error', (e) => { handleError(e) });
station.on(PUBLIC_EVENTS.ERROR, (e) => { handleError(e) });
```

## or just go to [examples](./examples/server.js)
Expand All @@ -112,4 +120,4 @@ node examples/server.js [path/to/your_mp3tracks]
```

## Demo
Fully working demo is available on http://fridgefm.com
Fully working demo is available on https://fridgefm.com
4 changes: 2 additions & 2 deletions examples/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ <h2>Station controls should be private</h2>
}
const getCoverUrl = (image = {}) => {
const { imageBuffer } = image;
if (!imageBuffer) return;
if (!imageBuffer) return 'https://t3.ftcdn.net/jpg/01/09/40/34/240_F_109403479_3BJH2QY7zrMV5OUGPePPmxPYZf0zY4lR.jpg';
const { imageBuffer: { data: uintarr } = {} } = image;
const blob = new Blob([new Uint8Array(uintarr)]);
const urlCreator = window.URL || window.webkitURL;
Expand Down Expand Up @@ -104,7 +104,7 @@ <h2>Station controls should be private</h2>
request('/controls/shufflePlaylist').then(controlsGetPlaylist)
}
function controlsNext() {
request('/controls/next').then(controlsGetPlaylist)
request('/controls/next').then(controlsGetPlaylist).then(info)
}
function controlsRearrange({ oldIndex, newIndex }) {
request(`/controls/rearrangePlaylist?oldIndex=${oldIndex}&newIndex=${newIndex}`).then(controlsGetPlaylist)
Expand Down
41 changes: 24 additions & 17 deletions examples/server.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
const express = require('express');
const path = require('path');
const { Station } = require('../lib/index');
const { Station, SHUFFLE_METHODS, PUBLIC_EVENTS } = require('../lib/index');
const port = 3001;
const server = express();
const musicPath = path.resolve(__dirname, process.argv[2] || './music');

const station = new Station();
const station = new Station({
verbose: true, // for verbose logging to console
responseHeaders: {
'icy-genre': 'jazz'
},
});
// add folder to station
station.addFolder(musicPath);

// update currently playing track info
let currentTrack;
station.on('nextTrack', async (track, stats) => {
console.log(`nextTrack handler took ${stats.time}ms`)
station.on(PUBLIC_EVENTS.NEXT_TRACK, async (track) => {
const result = await track.getMetaAsync();
currentTrack = result;
});

station.on('start', () => {
station.shufflePlaylist();
station.on(PUBLIC_EVENTS.START, () => {
// double the playlist on start
station.reorderPlaylist(a => a.concat(a))
});

station.on('restart', (_, stats) => {
console.log(`restart handler took ${stats.time}ms`)
station.shufflePlaylist();
station.on(PUBLIC_EVENTS.RESTART, () => {
station.reorderPlaylist(a => a.concat(a))
});

// add this handler - otherwise any error will exit the process as unhandled
station.on(PUBLIC_EVENTS.ERROR, console.error)

// main stream route
server.get('/stream', (req, res) => {
station.connectListener(req, res);
Expand All @@ -44,23 +51,23 @@ server.get('/controls/next', (req, res) => {

// shuffle playlist
server.get('/controls/shufflePlaylist', (req, res) => {
station.shufflePlaylist();
station.reorderPlaylist(SHUFFLE_METHODS.randomShuffle());
res.json('Playlist shuffled');
});

// rearrange tracks in a playlist
server.get('/controls/rearrangePlaylist', (req, res) => {
const { newIndex, oldIndex } = req.query;
station.reorderPlaylist(SHUFFLE_METHODS.rearrange({ from: oldIndex, to: newIndex }))
res.json(`Succesfully moved element from "${oldIndex}" to "${newIndex}"`);
});

// just get the entire playlist
server.get('/controls/getPlaylist', (req, res) => {
const plist = station.getPlaylist();
res.json(plist);
});

// rearrange tracks in a playlist
server.get('/controls/rearrangePlaylist', (req, res) => {
const { newIndex, oldIndex } = req.query;
const { from, to } = station.rearrangePlaylist(oldIndex, newIndex);
res.json(`Succesfully moved element from "${from}" to "${to}"`);
});

// route for serving static
server.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, './example.html'));
Expand Down
5 changes: 3 additions & 2 deletions jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"transform": {
"^.+\\.ts$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
"testRegex": "(test|spec)\\.[jt]sx?$",
"collectCoverageFrom": [
"src/**/*.ts"
],
Expand All @@ -14,5 +14,6 @@
"tsConfig": false,
"diagnostics": false
}
}
},
"verbose": true
}
15 changes: 11 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fridgefm/radio-core",
"author": "Grigory Gorshkov",
"version": "2.3.2",
"version": "3.0.0",
"description": "internet radio engine made on NodeJS platform",
"license": "MIT",
"main": "./lib/index.js",
Expand All @@ -25,10 +25,11 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/Kefir100/fridgefm-radio-core.git"
"url": "git+https://github.com/ch1ller0/fridgefm-radio-core.git"
},
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"lint": "eslint --fix ./src/**/*.*",
"lint:ci": "eslint ./src/**/*.*",
"test": "jest --config jest.config.json",
Expand All @@ -38,15 +39,20 @@
"chalk": "^4.1.0",
"dev-null": "^0.1.1",
"express": "^4.17.1",
"fs-extra": "^9.1.0",
"get-mp3-duration": "^1.0.0",
"highland": "^2.13.5",
"klaw-sync": "^6.0.0",
"lodash": "^4.17.20",
"node-id3": "^0.2.2"
"node-id3": "^0.2.2",
"winston": "^3.3.3"
},
"devDependencies": {
"@types/express": "^4.17.11",
"@types/fs-extra": "^9.0.7",
"@types/highland": "^2.12.11",
"@types/jest": "^26.0.20",
"@types/klaw-sync": "^6.0.0",
"@types/lodash": "^4.14.168",
"@types/node": "^14.14.25",
"@typescript-eslint/eslint-plugin": "^4.14.2",
Expand All @@ -58,10 +64,11 @@
"jest": "^26.6.3",
"lint-staged": ">=10.5.4",
"ts-jest": "^26.5.0",
"typed-emitter": "^1.3.1",
"typescript": "4.1.3"
},
"bugs": {
"url": "https://github.com/Kefir100/fridgefm-radio-core/issues"
"url": "https://github.com/ch1ller0/fridgefm-radio-core/issues"
},
"homepage": "http://fridgefm.com",
"directories": {
Expand Down
14 changes: 14 additions & 0 deletions src/__tests__/fail-path/common.fp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Station } from '../../index';

describe('public/FailPaths/common', () => {
it('non-existing folder - throws error', () => {
const station = new Station();
expect(() => {
station.addFolder('biba'); // non-existing
}).toThrow();
});

it.todo('track was deleted while playback - revalidates folders');
it.todo('track has some problems while reading - skip it');
it.todo('track has some problems on stream - skip it');
});
47 changes: 47 additions & 0 deletions src/__tests__/happy-path/event.hp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Station, PUBLIC_EVENTS } from '../../index';
import { pathToMusic } from '../test-utils.mock';

const createChecker = () => ({
start: jest.fn(),
nextTrack: jest.fn(),
restart: jest.fn(),
error: jest.fn(),
});

describe('public/HappyPath/events', () => {
it('station events - fires', () => {
const checker = createChecker();
const station = new Station();

station.on(PUBLIC_EVENTS.START, (...args) => checker.start(...args));
station.on(PUBLIC_EVENTS.NEXT_TRACK, (...args) => checker.nextTrack(...args));
station.on(PUBLIC_EVENTS.RESTART, (...args) => checker.restart(...args));
station.on(PUBLIC_EVENTS.ERROR, (...args) => checker.error(...args));

station.addFolder(pathToMusic);

// event "start"
station.start();

expect(checker.start).toHaveBeenCalledTimes(1);
// start event fired
expect(checker.start.mock.calls[0][0]).toEqual(station.getPlaylist());
// expect(checker.start).toHaveBeenCalledWith(station.getPlaylist());
// nextTrack event fired
expect(checker.nextTrack.mock.calls[0][0]).toEqual(station.getPlaylist()[0]);

// event "nextTrack"
station.next();
// nextTrack returns a track
expect(checker.nextTrack.mock.calls[1][0]).toEqual(station.getPlaylist()[1]);

// event "restart"
station.next();

// returns playlist
expect(checker.restart.mock.calls[0][0].map((v) => v.fsStats))
.toEqual(station.getPlaylist().map((v) => v.fsStats));

// @TODO find some way to test error event
});
});
Loading

0 comments on commit 625acd7

Please sign in to comment.