Skip to content

Commit

Permalink
Merge pull request #14 from mrf345/testing
Browse files Browse the repository at this point in the history
Refactor structure. Fix es6 import. Bump to 0.0.25
  • Loading branch information
mrf345 committed Jun 11, 2020
2 parents 727e4fc + 447b91e commit da435b1
Show file tree
Hide file tree
Showing 15 changed files with 846 additions and 3,381 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,9 @@ You'll have to run `Player.load().then(function() { Player.play() })` or you can
Player.autoStart = true
Player.load() // will replay automatically as soon as the tracks are loaded.
```

### Development:
- Run tests with: `npm test`
- Auto format with standardJS: `npm run format`
- Publish new release: `npm run push`
- Update live example: `./pages.sh`
2 changes: 2 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module.exports = {
[
'@babel/preset-env',
{
useBuiltIns: 'entry',
corejs: 3,
debug: false,
targets: {
node: 10,
Expand Down
2 changes: 1 addition & 1 deletion bin/AudioSequence.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
verbose: true,
collectCoverage: true,
collectCoverageFrom: ['./src/*']
collectCoverageFrom: ['./src/**/**']
}
3,884 changes: 678 additions & 3,206 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "audio_sequence",
"version": "0.0.19",
"version": "0.0.25",
"description": "simple module to control, monitor and play multiple audio files in sequences at a time.",
"main": "lib/index.js",
"scripts": {
"test": "standard ./src/* && jest ./src",
"test": "standard ./src/**/** && jest",
"build": "./pages.sh",
"format": "standard --fix ./src/*",
"format": "standard --fix ./src/**/**",
"coveralls": "jest --coverage --coverageReporters=text-lcov | coveralls",
"push": "babel --config-file ./babel.config.js src/ --out-dir lib/ && npm version patch && npm login && npm publish --access public"
"push": "npm version patch && babel --config-file ./babel.config.js src/ --out-dir lib/ && npm login && npm publish --access public"
},
"repository": {
"type": "git",
Expand All @@ -27,8 +27,8 @@
},
"homepage": "https://github.com/mrf345/audio_sequance#readme",
"dependencies": {
"jsdom": "^15.2.1",
"@babel/polyfill": "^7.10.1"
"core-js": "^3.6.5",
"jsdom": "^15.2.1"
},
"devDependencies": {
"@babel/cli": "^7.10.1",
Expand Down
153 changes: 3 additions & 150 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import '@babel/polyfill'
import { JSDOM } from 'jsdom'
import { AudioSequence } from './main'

if (JSDOM) {
global.window = new JSDOM().window
global.document = global.window.document

// Workaround JSDOM lacking `HTMLMediaElement` methods
Object.defineProperty(global.window.HTMLMediaElement.prototype, 'play', {
configurable: true,
get () {
Expand All @@ -14,152 +15,4 @@ if (JSDOM) {
})
}

export default class AudioSequence {
/**
* Utility to ease the process of plying audio elements in sequences.
* @param {object} options contains the module options.
*/
constructor (options = {}) {
const AUTOPLAY_MSG = 'AutoPlay permission is lacking. Enable it then reload:'
const b = (value, defValue) => value === undefined ? defValue : !!value

this.files = options.files || [] // files inserted will be stored in
this.repeats = options.repeats || 1 // number of repeats to obey with some adjustments later
this.repeatWhole = b(options.repeat_whole, true) // repeat all files as whole
this.repeatEach = b(options.repeat_each, false) // repeat each file for the number of repeats
this.repeatForever = b(options.repeat_forever, false) // to keep repeating endlessly
this.repeatDelay = options.repeat_delay * 1000 || 0 // to add a time delay between each repeat
this.reverseOrder = b(options.reverse_order, false) // to reverse the order list of audio files
this.shuffleOrder = b(options.shuffle_order, false) // to randomly shuffle the order of the files list
this.volume = options.volume || 0.5 // to set the default volume
this.autoStart = b(options.auto_start, false) // to auto load and start playing as the module loads
this.autoplayWarning = b(options.autoplay_warning, true) // to display warning if AutoPlay's disabled
this.autoplayMessage = options.autoplay_message || AUTOPLAY_MSG // message to show if AutoPlay's disabled

this.playlist = [] // stack of audio elements playing
this.current = 0 // index of the currently playing
this.errors = [] // stack of errors encountered in loading
this.loading = false // indication of all files loaded
this.repeatCounter = 0 // whole repeats index counter
this.prePromises = [] // to resolve prior to ending transition
this.postPromises = [] // to resolve after ending transition
this.logger = undefined // store the logging interval

this.hasFiles = () => !!this.playlist.length
this.hasErrors = () => !!this.errors.length
this.getCurrent = () => ({ index: this.current, item: this.playlist[this.current] })
this.getNext = p => this.keepWithin(p ? this.current - 1 : this.current + 1, this.playlist)
this.isMuted = () => this.hasFiles() && this.getCurrent().item.volume === 0
this.isPaused = () => this.hasFiles() && this.getCurrent().item.paused
this.isLast = () => (this.playlist.length - 1) === this.current
this.notStarted = () => !this.isPaused() && !this.isActive()
this.getPlace = ele => this.files
.map(f => f.replace(window.origin, ''))
.indexOf(ele.src.replace(window.origin, ''))
this.isActive = item => {
if (this.hasFiles()) {
item = item || this.getCurrent().item
return item && !item.ended && item.currentTime > 0
} else return false
}

this.mixIns = ['utils', 'constants', 'fetcher', 'controller', 'repeater', 'logger']
this.mixIns.forEach(mixin => Object.assign(this, require(`./${mixin}`)))

// if auto-play is disabled, will prompt the user with instructions.
this.handleAutoPlayNotAllowed()

// auto start playing audio, or wait for DOM to load.
if (this.autoStart) this.waitForDOM(this.load.bind(this))
}

handleEnding (event) {
return new Promise(resolve => {
const element = this.playlist.find(ele => ele.src === event.target.src)
const eleIndex = this.playlist.indexOf(element)
const { nextIndex, nextItem } = this.shuffleOrder
? this.choice(this.playlist)
: this.reverseOrder
? this.keepWithin(eleIndex - 1, this.playlist)
: this.keepWithin(eleIndex + 1, this.playlist)

const zeroAndPlay = (element) => {
element.volume = this.volume
element.currentTime = 0

const promise = (this.repeatEach || this.repeatWhole) && this.repeatDelay
? new Promise(resolve => setTimeout(() => resolve(element.play()), this.repeatDelay))
: element.play()

return promise && promise.then
? promise.then(() => resolve(element))
: resolve(element)
}

const commonNext = () => {
if (nextItem) {
this.current = nextIndex
nextItem.repeats = 1
return zeroAndPlay(nextItem)
} else return resolve(element)
}

if (this.repeatEach) {
const repeats = element.repeats || 1

if (repeats >= this.repeats && !this.repeatForever) {
element.repeats = 0

return this.isLast() ? resolve(element) : commonNext()
} else {
element.repeats = repeats + 1
return zeroAndPlay(element)
}
} else if (this.repeatWhole) {
if (this.repeatForever) return commonNext()
else {
if (this.repeatCounter >= this.repeats) return resolve(element)
else {
if (this.current === (this.playlist.length - 2)) this.repeatCounter += 1
return commonNext()
}
}
} else {
if (this.repeatForever || (this.playlist.length - 1) >= this.current) return commonNext()
else {
this.current = 0
return resolve(element)
}
}
})
}

tearDown () {
if (this.isActive() && !this.isPaused()) this.getCurrent().item.pause()
this.playlist.forEach(e => document.body.removeChild(e))
this.playlist = []
this.current = 0
this.repeatCounter = 0
this.errors = []
this.prePromises = []
this.postPromises = []
}

load () {
if (this.hasFiles()) this.tearDown()
return new Promise(resolve => {
this.fetchAll()
.then(stack => {
if (stack.length) { // keep original order
this.playlist = Array(stack.length).fill()
stack.forEach(ele => { this.playlist[this.getPlace(ele)] = ele })
this.playlist = this.playlist.filter(ele => !!ele)
}

if (this.hasErrors()) this.errors.forEach(error => console.warn(error))
if (this.autoStart && this.hasFiles()) this.handlePlay(this.getCurrent().item.play())
resolve(stack)
})
})
}
}
export default AudioSequence
149 changes: 149 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
export class AudioSequence {
/**
* Utility to ease the process of plying audio elements in sequences.
* @param {object} options contains the module options.
*/
constructor (options = {}) {
const AUTOPLAY_MSG = 'AutoPlay permission is lacking. Enable it then reload:'
const b = (value, defValue) => value === undefined ? defValue : !!value

this.files = options.files || [] // files inserted will be stored in
this.repeats = options.repeats || 1 // number of repeats to obey with some adjustments later
this.repeatWhole = b(options.repeat_whole, true) // repeat all files as whole
this.repeatEach = b(options.repeat_each, false) // repeat each file for the number of repeats
this.repeatForever = b(options.repeat_forever, false) // to keep repeating endlessly
this.repeatDelay = options.repeat_delay * 1000 || 0 // to add a time delay between each repeat
this.reverseOrder = b(options.reverse_order, false) // to reverse the order list of audio files
this.shuffleOrder = b(options.shuffle_order, false) // to randomly shuffle the order of the files list
this.volume = options.volume || 0.5 // to set the default volume
this.autoStart = b(options.auto_start, false) // to auto load and start playing as the module loads
this.autoplayWarning = b(options.autoplay_warning, true) // to display warning if AutoPlay's disabled
this.autoplayMessage = options.autoplay_message || AUTOPLAY_MSG // message to show if AutoPlay's disabled

this.playlist = [] // stack of audio elements playing
this.current = 0 // index of the currently playing
this.errors = [] // stack of errors encountered in loading
this.loading = false // indication of all files loaded
this.repeatCounter = 0 // whole repeats index counter
this.prePromises = [] // to resolve prior to ending transition
this.postPromises = [] // to resolve after ending transition
this.logger = undefined // store the logging interval

this.hasFiles = () => !!this.playlist.length
this.hasErrors = () => !!this.errors.length
this.getCurrent = () => ({ index: this.current, item: this.playlist[this.current] })
this.getNext = p => this.keepWithin(p ? this.current - 1 : this.current + 1, this.playlist)
this.isMuted = () => this.hasFiles() && this.getCurrent().item.volume === 0
this.isPaused = () => this.hasFiles() && this.getCurrent().item.paused
this.isLast = () => (this.playlist.length - 1) === this.current
this.notStarted = () => !this.isPaused() && !this.isActive()
this.getPlace = ele => this.files
.map(f => f.replace(window.origin, ''))
.indexOf(ele.src.replace(window.origin, ''))
this.isActive = item => {
if (this.hasFiles()) {
item = item || this.getCurrent().item
return item && !item.ended && item.currentTime > 0
} else return false
}

this.mixIns = ['utils', 'constants', 'fetcher', 'controller', 'repeater', 'logger']
this.mixIns.forEach(mixin => Object.assign(this, require(`./mixins/${mixin}`)))

// if auto-play is disabled, will prompt the user with instructions.
this.handleAutoPlayNotAllowed()

// auto start playing audio, or wait for DOM to load.
if (this.autoStart) this.waitForDOM(this.load.bind(this))
}

handleEnding (event) {
return new Promise(resolve => {
const element = this.playlist.find(ele => ele.src === event.target.src)
const eleIndex = this.playlist.indexOf(element)
const { nextIndex, nextItem } = this.shuffleOrder
? this.choice(this.playlist)
: this.reverseOrder
? this.keepWithin(eleIndex - 1, this.playlist)
: this.keepWithin(eleIndex + 1, this.playlist)

const zeroAndPlay = (element) => {
element.volume = this.volume
element.currentTime = 0

const promise = (this.repeatEach || this.repeatWhole) && this.repeatDelay
? new Promise(resolve => setTimeout(() => resolve(element.play()), this.repeatDelay))
: element.play()

return promise && promise.then
? promise.then(() => resolve(element))
: resolve(element)
}

const commonNext = () => {
if (nextItem) {
this.current = nextIndex
nextItem.repeats = 1
return zeroAndPlay(nextItem)
} else return resolve(element)
}

if (this.repeatEach) {
const repeats = element.repeats || 1

if (repeats >= this.repeats && !this.repeatForever) {
element.repeats = 0

return this.isLast() ? resolve(element) : commonNext()
} else {
element.repeats = repeats + 1
return zeroAndPlay(element)
}
} else if (this.repeatWhole) {
if (this.repeatForever) return commonNext()
else {
if (this.repeatCounter >= this.repeats) return resolve(element)
else {
if (this.current === (this.playlist.length - 2)) this.repeatCounter += 1
return commonNext()
}
}
} else {
if (this.repeatForever || (this.playlist.length - 1) >= this.current) return commonNext()
else {
this.current = 0
return resolve(element)
}
}
})
}

tearDown () {
if (this.isActive() && !this.isPaused()) this.getCurrent().item.pause()
this.playlist.forEach(e => document.body.removeChild(e))
this.playlist = []
this.current = 0
this.repeatCounter = 0
this.errors = []
this.prePromises = []
this.postPromises = []
}

load () {
if (this.hasFiles()) this.tearDown()
return new Promise(resolve => {
this.fetchAll()
.then(stack => {
if (stack.length) { // keep original order
this.playlist = Array(stack.length).fill()
stack.forEach(ele => { this.playlist[this.getPlace(ele)] = ele })
this.playlist = this.playlist.filter(ele => !!ele)
}

if (this.hasErrors()) this.errors.forEach(error => console.warn(error))
if (this.autoStart && this.hasFiles()) this.handlePlay(this.getCurrent().item.play())
resolve(stack)
})
})
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
17 changes: 0 additions & 17 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,6 @@ const { resolve } = require('path')
const nodeExternals = require('webpack-node-externals')

module.exports = [
{
entry: './src/index.js',
output: {
path: resolve(__dirname, 'lib'),
filename: 'index.js',
libraryTarget: 'commonjs'
},
plugins: [],
externals: [nodeExternals()],
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: { loader: 'babel-loader' }
}]
}
},
{
entry: './src/index.js',
output: {
Expand Down

0 comments on commit da435b1

Please sign in to comment.