Demos for music player and music playlist functional.

This commit is contained in:
Harrison Deng 2022-04-18 11:46:23 -05:00
parent cc6b70404b
commit 9453ce5f0b
20 changed files with 1091 additions and 271 deletions

View File

@ -5,8 +5,9 @@
"ckeditor", "ckeditor",
"easings", "easings",
"linebreak", "linebreak",
"songplayer" "musicplayer"
], ],
"javascript.preferences.importModuleSpecifierEnding": "js", "javascript.preferences.importModuleSpecifierEnding": "js",
"html.format.wrapLineLength": 0 "html.format.wrapLineLength": 0,
"liveServer.settings.port": 5501
} }

227
package-lock.json generated
View File

@ -25,6 +25,7 @@
"mocha-junit-reporter": "^2.0.2", "mocha-junit-reporter": "^2.0.2",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"sync-directory": "^4.0.12",
"webpack": "^5.72.0", "webpack": "^5.72.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
@ -2880,6 +2881,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
"dev": true
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -3507,6 +3514,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"dev": true
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -4049,6 +4062,20 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true "dev": true
}, },
"node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -4497,6 +4524,19 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/is-absolute": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
"integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
"dev": true,
"dependencies": {
"is-relative": "^1.0.0",
"is-windows": "^1.0.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-bigint": { "node_modules/is-bigint": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@ -4734,6 +4774,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-relative": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
"integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
"dev": true,
"dependencies": {
"is-unc-path": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-shared-array-buffer": { "node_modules/is-shared-array-buffer": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
@ -4794,6 +4846,18 @@
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true "dev": true
}, },
"node_modules/is-unc-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
"integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
"dev": true,
"dependencies": {
"unc-path-regex": "^0.1.2"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-unicode-supported": { "node_modules/is-unicode-supported": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@ -4818,6 +4882,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-yarn-global": { "node_modules/is-yarn-global": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
@ -6116,6 +6189,23 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/readdir-enhanced": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/readdir-enhanced/-/readdir-enhanced-1.5.2.tgz",
"integrity": "sha1-YUYwSGkKxqRVt1ti+nioj43IXlM=",
"dev": true,
"dependencies": {
"call-me-maybe": "^1.0.1",
"es6-promise": "^4.1.0",
"glob-to-regexp": "^0.3.0"
}
},
"node_modules/readdir-enhanced/node_modules/glob-to-regexp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
"integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
"dev": true
},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -6597,6 +6687,31 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/sync-directory": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/sync-directory/-/sync-directory-4.0.12.tgz",
"integrity": "sha512-ZpBeMXL60flA1jbuhNJoCE6EjzInjTuoh6f7cArruYwQ9V8DEZNzN9FI3Q9CkWz4VzF4IvcKG38xLO1rZaysow==",
"dev": true,
"dependencies": {
"chokidar": "^3.3.1",
"commander": "^6.2.0",
"fs-extra": "^7.0.1",
"is-absolute": "^1.0.0",
"readdir-enhanced": "^1.5.2"
},
"bin": {
"syncdir": "cmd.js"
}
},
"node_modules/sync-directory/node_modules/commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true,
"engines": {
"node": ">= 6"
}
},
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@ -6781,6 +6896,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/undefsafe": { "node_modules/undefsafe": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@ -9403,6 +9527,12 @@
"get-intrinsic": "^1.0.2" "get-intrinsic": "^1.0.2"
} }
}, },
"call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
"dev": true
},
"callsites": { "callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -9870,6 +10000,12 @@
"is-symbol": "^1.0.2" "is-symbol": "^1.0.2"
} }
}, },
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"dev": true
},
"escalade": { "escalade": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -10291,6 +10427,17 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true "dev": true
}, },
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -10609,6 +10756,16 @@
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"dev": true "dev": true
}, },
"is-absolute": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
"integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
"dev": true,
"requires": {
"is-relative": "^1.0.0",
"is-windows": "^1.0.1"
}
},
"is-bigint": { "is-bigint": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@ -10765,6 +10922,15 @@
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
} }
}, },
"is-relative": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
"integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
"dev": true,
"requires": {
"is-unc-path": "^1.0.0"
}
},
"is-shared-array-buffer": { "is-shared-array-buffer": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
@ -10804,6 +10970,15 @@
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true "dev": true
}, },
"is-unc-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
"integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
"dev": true,
"requires": {
"unc-path-regex": "^0.1.2"
}
},
"is-unicode-supported": { "is-unicode-supported": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@ -10819,6 +10994,12 @@
"call-bind": "^1.0.2" "call-bind": "^1.0.2"
} }
}, },
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
"dev": true
},
"is-yarn-global": { "is-yarn-global": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
@ -11795,6 +11976,25 @@
} }
} }
}, },
"readdir-enhanced": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/readdir-enhanced/-/readdir-enhanced-1.5.2.tgz",
"integrity": "sha1-YUYwSGkKxqRVt1ti+nioj43IXlM=",
"dev": true,
"requires": {
"call-me-maybe": "^1.0.1",
"es6-promise": "^4.1.0",
"glob-to-regexp": "^0.3.0"
},
"dependencies": {
"glob-to-regexp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
"integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
"dev": true
}
}
},
"readdirp": { "readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -12142,6 +12342,27 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true "dev": true
}, },
"sync-directory": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/sync-directory/-/sync-directory-4.0.12.tgz",
"integrity": "sha512-ZpBeMXL60flA1jbuhNJoCE6EjzInjTuoh6f7cArruYwQ9V8DEZNzN9FI3Q9CkWz4VzF4IvcKG38xLO1rZaysow==",
"dev": true,
"requires": {
"chokidar": "^3.3.1",
"commander": "^6.2.0",
"fs-extra": "^7.0.1",
"is-absolute": "^1.0.0",
"readdir-enhanced": "^1.5.2"
},
"dependencies": {
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true
}
}
},
"tapable": { "tapable": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@ -12271,6 +12492,12 @@
"which-boxed-primitive": "^1.0.2" "which-boxed-primitive": "^1.0.2"
} }
}, },
"unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
"dev": true
},
"undefsafe": { "undefsafe": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",

View File

@ -10,12 +10,12 @@
"build:dev": "webpack --config webpack.dev.cjs", "build:dev": "webpack --config webpack.dev.cjs",
"test:junit": "mocha tests/** --reporter mocha-junit-reporter --reporter-options mochaFile=./junit/test_results.xml", "test:junit": "mocha tests/** --reporter mocha-junit-reporter --reporter-options mochaFile=./junit/test_results.xml",
"test:console": "mocha tests/**", "test:console": "mocha tests/**",
"watch:docs": "nodemon --watch dist/ --watch tutorials/ --watch src/ --exec 'npm run docs' -e js,mjs,json,htm,html,xml,markdown,md,xhtml", "watch:docs": "nodemon --watch dist/ --watch tutorials/ --watch src/ --exec 'npm run docs' -e js,mjs,json,htm,html,xml,markdown,md,xhtml --ignore ./tutorials/assets/js/audioshowkit.js",
"watch:lib": "webpack --watch --config webpack.dev.cjs", "watch:lib": "webpack --watch --config webpack.dev.cjs",
"build": "npm run build:dev", "build": "npm run build:dev",
"test": "npm run test:console", "test": "npm run test:console",
"watch": "npm run watch:lib", "watch": "npm run watch:lib",
"docs": "jsdoc -c jsdoc.config.json" "docs": "jsdoc -c jsdoc.config.json && cp ./dist/audioshowkit.js ./tutorials/assets/js/audioshowkit.js && syncdir ./tutorials/assets/ ./docs/assets/"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
@ -36,6 +36,7 @@
"mocha-junit-reporter": "^2.0.2", "mocha-junit-reporter": "^2.0.2",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"sync-directory": "^4.0.12",
"webpack": "^5.72.0", "webpack": "^5.72.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"

View File

@ -1,6 +1,4 @@
// TODO: add for width import VisualizerUpdateManager from "../visualization/VisualizerUpdateManager.js";
// TODO: add for height
// TODO: add for background
/** /**
* *
@ -13,6 +11,8 @@
* @param {number} value The new numerical value to update to. * @param {number} value The new numerical value to update to.
*/ */
// FIXME: Warnings about interpolator not being defined are broken. Docs generate fine.
/** /**
* Maps the average of a range of bins to numerical value defined by a getter and a setter. * Maps the average of a range of bins to numerical value defined by a getter and a setter.
* *
@ -42,7 +42,7 @@ function mapRangedAvgNumerical({ minVal, maxVal, lowerBin = undefined, upperBin,
} }
average /= normalBins.length; average /= normalBins.length;
const range = maxVal - minVal; const range = maxVal - minVal;
let interpolated = interpolator(getter(), average, timeDelta); // TODO: May be optimized by allowing for transferring of previous style state. let interpolated = interpolator(getter(), average, timeDelta);
if (reversed) interpolated = 1 - interpolated; if (reversed) interpolated = 1 - interpolated;
setter(minVal + range * interpolated); setter(minVal + range * interpolated);
} }

View File

@ -8,10 +8,10 @@ import Visualizer from "../visualization/Visualizer.js";
*/ */
/** /**
* A song with metadata that can be used as part of a {@link SongPlaylist}. * A music with metadata that can be used as part of a {@link MusicPlaylist}.
* *
*/ */
export default class PlayListSong { export default class Music {
#displayName; #displayName;
#author; #author;
#url; #url;
@ -20,11 +20,11 @@ export default class PlayListSong {
#ready = false; #ready = false;
/** /**
* Constructs a song for a {@link SongPlaylist}. * Constructs a music for a {@link MusicPlaylist}.
* *
* @param {string} url the url to fetch the song from. * @param {string} url the url to fetch the music from.
* @param {string} name the name of the song. * @param {string} name the name of the music.
* @param {string} author the author of the song. * @param {string} author the author of the music.
*/ */
constructor(url, name, author) { constructor(url, name, author) {
this.#displayName = name; this.#displayName = name;
@ -35,14 +35,12 @@ export default class PlayListSong {
/** /**
* *
* @param {AudioEventCallback} [onReady] called when the song is ready, including right away if the song is already ready. * @param {AudioEventCallback} [onReady] called when the music is ready, including right away if the music is already ready.
* @returns {HTMLAudioElement} The audio element that represents this song. * @returns {HTMLAudioElement} The audio element that represents this music.
*/ */
fetchAudio(onReady) { fetchAudio(onReady) {
console.log("Fetching audio...");
if (this.#audio) { if (this.#audio) {
if (this.#ready) { if (this.#ready) {
console.log("Already ready.");
if (onReady) onReady(this.#audio); if (onReady) onReady(this.#audio);
} else if (onReady) { } else if (onReady) {
this.#audio.addEventListener("canplaythrough", () => { this.#audio.addEventListener("canplaythrough", () => {
@ -52,35 +50,31 @@ export default class PlayListSong {
return this.#audio; return this.#audio;
} }
this.#audio = new Audio(this.#url); this.#audio = new Audio(this.#url);
console.log("Fetching from url: " + this.#url);
this.#audio.addEventListener("canplaythrough", () => { this.#audio.addEventListener("canplaythrough", () => {
this.#ready = true; this.#ready = true;
console.log("attempting to invoke onReady.");
console.log(onReady);
if (onReady && this.isAudioInstantiated()) { if (onReady && this.isAudioInstantiated()) {
onReady(this.#audio); onReady(this.#audio);
console.log("onReady invoked.");
} }
}, { once: true }); }, { once: true });
return this.#audio; return this.#audio;
} }
/** /**
* @returns {string} The name of the song to be displayed. * @returns {string} The name of the music to be displayed.
*/ */
get displayName() { get displayName() {
return this.#displayName; return this.#displayName;
} }
/** /**
* @returns {string} The author of the song. * @returns {string} The author of the music.
*/ */
get author() { get author() {
return this.#author; return this.#author;
} }
/** /**
* @returns {string} The url at which the file for this song can be found. * @returns {string} The url at which the file for this music can be found.
*/ */
get url() { get url() {
return this.#url; return this.#url;
@ -96,7 +90,7 @@ export default class PlayListSong {
/** /**
* Begins audio playback as soon as possible. * Begins audio playback as soon as possible.
* *
* This function uses the {@link PlaylistSong#getAudio} function and specifically, the {@link AudioEventCallback}. * This function uses the {@link PlaylistMusic#getAudio} function and specifically, the {@link AudioEventCallback}.
*/ */
play() { play() {
this.fetchAudio((audio) => { this.fetchAudio((audio) => {
@ -131,7 +125,7 @@ export default class PlayListSong {
} }
/** /**
* *
* @returns {number} The number of seconds into the song. * @returns {number} The number of seconds into the music.
*/ */
get currentTime() { get currentTime() {
return this.fetchAudio().currentTime; return this.fetchAudio().currentTime;
@ -139,7 +133,7 @@ export default class PlayListSong {
/** /**
* *
* The time position in the song to jump to in seconds. * The time position in the music to jump to in seconds.
*/ */
set currentTime(currentTime) { set currentTime(currentTime) {
this.fetchAudio().currentTime = currentTime; this.fetchAudio().currentTime = currentTime;
@ -147,7 +141,7 @@ export default class PlayListSong {
/** /**
* *
* @returns {number} The duration of the song. * @returns {number} The duration of the music.
*/ */
get duration() { get duration() {
return this.fetchAudio().duration; return this.fetchAudio().duration;
@ -157,7 +151,7 @@ export default class PlayListSong {
* Unloads the audio data. * Unloads the audio data.
* Makes sure the audio is paused. * Makes sure the audio is paused.
* *
* Also calls {@link PlaylistSong#unloadVisualizer}. * Also calls {@link PlaylistMusic#unloadVisualizer}.
*/ */
unloadAudio() { unloadAudio() {
if (!this.isAudioInstantiated()) return; if (!this.isAudioInstantiated()) return;

View File

@ -1,6 +1,5 @@
import "../styles/songPlayer.css"; import "../styles/musicPlayer.css";
import PlayListSong from "./PlaylistSong.js"; import MusicPlaylist from "./MusicPlaylist.js";
import SongPlaylist from "./SongPlaylist.js";
/**@module */ /**@module */
@ -11,22 +10,20 @@ import SongPlaylist from "./SongPlaylist.js";
*/ */
/** /**
* A player that keeps track of global properties for playback as well as traversing a playlist. Additionally keeps track of song audio data and attempts to reduce memory usage when possible. * A player that keeps track of global properties for playback as well as traversing a playlist. Additionally keeps track of music audio data and attempts to reduce memory usage when possible.
*/ */
export default class SongPlayer { export default class MusicPlayer {
#playlist; #playlist;
#current = 0;
#volume = 1; #volume = 1;
#autoplay = false; #autoplay = false;
#wasPlaying = false; #wasPlaying = false;
#playlistChangeListeners = []; #playlistChangeListeners = [];
#currentSongChangeListeners = [];
#volumeChangeListeners = []; #volumeChangeListeners = [];
#allAudioEventListeners = {}; #allAudioEventListeners = {};
/** /**
* *
* @param {SongPlaylist} playlist the playlist of songs that this player is in charge of. * @param {MusicPlaylist} playlist the playlist of musics that this player is in charge of.
*/ */
constructor(playlist) { constructor(playlist) {
this.playlist = playlist; this.playlist = playlist;
@ -36,15 +33,15 @@ export default class SongPlayer {
} }
/** /**
* The new playlist of songs that this player is in charge of. * The new playlist of musics that this player is in charge of.
*/ */
set playlist(playlist) { set playlist(playlist) {
this.#playlist?.unloadAllAudio(); if (this.playlist) this.unloadAllAudio();
if (this.playlist) this.playlist.removePositionChangeListener(this.#onMusicChange);
const old = this.#playlist; const old = this.#playlist;
this.#playlist = playlist; this.#playlist = playlist;
if (!this.changeCurrentSongIndex(0)) { this.#playlist.addPositionChangeListener(this.#onMusicChange);
throw new Error("The provided playlist has no songs."); this.#playlist.currentPosition = 0; // Updates the listener.
}
this.#playlistChangeListeners.forEach(playlistChangeListener => { this.#playlistChangeListeners.forEach(playlistChangeListener => {
playlistChangeListener(old, this.getPlaylist()); playlistChangeListener(old, this.getPlaylist());
}); });
@ -52,9 +49,9 @@ export default class SongPlayer {
} }
/** /**
* The current playlist of songs that this player is in charge of. * The current playlist of musics that this player is in charge of.
* *
* @returns {SongPlaylist} The song playlist this player is currently using. * @returns {MusicPlaylist} The music playlist this player is currently using.
*/ */
get playlist() { get playlist() {
return this.#playlist; return this.#playlist;
@ -62,65 +59,51 @@ export default class SongPlayer {
/** /**
* *
* @returns {boolean} true if and only if successful in going to the next song. * @returns {boolean} true if and only if successful in going to the next music.
*/ */
next() { next() {
return this.changeCurrentSongIndex(this.currentSongIndex + 1); return this.playlist.currentPosition += 1;
} }
/** /**
* attempts to go to the previous song. * attempts to go to the previous music.
* *
* @returns {boolean} true if and only if successful in going to the previous song. * @returns {boolean} true if and only if successful in going to the previous music.
*/ */
previous() { previous() {
return this.changeCurrentSongIndex(this.currentSongIndex - 1); return this.playlist.currentPosition -= 1;
} }
/** /**
* * @param {number} old the index of the previous music.
* @param {number} index the index of the song to jump to. * @param {number} current the index of the current music.
* @returns {boolean} true if and only if successful jumping to the given index. * @returns {boolean} true if and only if successful jumping to the given index.
*/ */
changeCurrentSongIndex(index) { #onMusicChange = (old, current) => { // Anonymous to avoid overriding "this".
console.log("Changing current song to " + index); const oldMusic = this.playlist.musicAtIndex(old);
if (index >= this.#playlist.total()) return false; const currentMusic = this.playlist.musicAtIndex(current);
if (index < 0) return false;
Object.keys(this.#allAudioEventListeners).forEach(key => { Object.keys(this.#allAudioEventListeners).forEach(key => {
const listeners = this.#allAudioEventListeners[key]; const listeners = this.#allAudioEventListeners[key];
listeners.forEach(listener => { listeners.forEach(listener => {
this.currentSong.fetchAudio().removeEventListener(key, listener); oldMusic.fetchAudio().removeEventListener(key, listener);
}); });
}); });
this.currentSong.unloadAudio(); oldMusic.unloadAudio();
const old = this.currentSong;
this.#current = index;
this.#currentSongChangeListeners.forEach(currentChangeListener => {
currentChangeListener(old, this.currentSong);
});
Object.keys(this.#allAudioEventListeners).forEach(key => { Object.keys(this.#allAudioEventListeners).forEach(key => {
const listeners = this.#allAudioEventListeners[key]; const listeners = this.#allAudioEventListeners[key];
listeners.forEach(listener => { listeners.forEach(listener => {
this.currentSong.fetchAudio().addEventListener(key, listener); currentMusic.fetchAudio().addEventListener(key, listener);
}); });
}); });
if (this.#wasPlaying || this.#autoplay) { if (this.#wasPlaying || this.#autoplay) {
this.playCurrent(); this.playCurrent();
} }
return true; return true;
} };
/**
*
* @returns {number} The current song's index in the playlist.
*/
get currentSongIndex() {
return this.#current;
}
playCurrent() { playCurrent() {
console.log("playing " + this.#current); this.playlist.currentMusic.fetchAudio((audio) => {
this.currentSong.fetchAudio((audio) => {
audio.volume = this.volume; audio.volume = this.volume;
audio.play(); audio.play();
}); });
@ -128,15 +111,14 @@ export default class SongPlayer {
} }
/** /**
* Pauses the current playing song (if there is one playing). * Pauses the current playing music (if there is one playing).
*/ */
pauseCurrent() { pauseCurrent() {
console.log("Pausing."); this.playlist.currentMusic.fetchAudio().pause();
this.currentSong.fetchAudio().pause();
} }
/** /**
* Toggles whether or not the current song is playing. * Toggles whether or not the current music is playing.
*/ */
togglePlay() { togglePlay() {
if (this.playing) { if (this.playing) {
@ -153,7 +135,7 @@ export default class SongPlayer {
if (volume > 1) volume = 1; if (volume > 1) volume = 1;
if (volume < 0) volume = 0; if (volume < 0) volume = 0;
this.#volume = volume; this.#volume = volume;
this.currentSong.fetchAudio().volume = this.#volume; this.playlist.currentMusic.fetchAudio().volume = this.#volume;
} }
/** /**
@ -171,8 +153,8 @@ export default class SongPlayer {
* @returns {boolean} true if and only if the position to seek to is within the duration of the track. This also means that if the track has not finished loading the duration data, than this will always return false. * @returns {boolean} true if and only if the position to seek to is within the duration of the track. This also means that if the track has not finished loading the duration data, than this will always return false.
*/ */
seek(position) { seek(position) {
if (position > this.currentSong.fetchAudio().duration || position < 0) return false; if (position > this.playlist.playlist.currentMusic.fetchAudio().duration || position < 0) return false;
this.currentSong.fetchAudio(audio => { this.playlist.playlist.currentMusic.fetchAudio(audio => {
audio.currentTime = position; audio.currentTime = position;
}); });
return true; return true;
@ -183,15 +165,15 @@ export default class SongPlayer {
* @returns {number} How many seconds into the audio track has been played in seconds. * @returns {number} How many seconds into the audio track has been played in seconds.
*/ */
get currentPosition() { get currentPosition() {
return this.currentSong.fetchAudio().currentTime; return this.playlist.playlist.currentMusic.fetchAudio().currentTime;
} }
/** /**
* *
* @returns {number} The total length of the song, or NaN if this information has not loaded yet. * @returns {number} The total length of the music, or NaN if this information has not loaded yet.
*/ */
get currentDuration() { get currentDuration() {
return this.currentSong.fetchAudio().duration; return this.playlist.playlist.currentMusic.fetchAudio().duration;
} }
/** /**
@ -208,7 +190,6 @@ export default class SongPlayer {
if (this.playing) playButton.classList.add("pause"); if (this.playing) playButton.classList.add("pause");
playButton.addEventListener("click", () => { playButton.addEventListener("click", () => {
console.log("Generated play button has been pressed.");
this.togglePlay(); this.togglePlay();
}); });
@ -241,37 +222,6 @@ export default class SongPlayer {
return previousButton; return previousButton;
} }
/**
* @returns {PlayListSong} The current playlist being used.
*/
get currentSong() {
return this.#playlist.songAtIndex(this.#current);
}
/**
*
* @param {changeListener} listener the listener to receive the updates.
* @returns {boolean} true if and only if successfully added the listener.
*/
addCurrentSongChangeListener(listener) {
if (this.#currentSongChangeListeners.includes(listener)) return false;
this.#currentSongChangeListeners.push(listener);
return true;
}
/**
*
* @param {changeListener} listener the song change listener to remove.
* @returns {boolean} true if and only if the song change listener given was successfully removed.
*/
removeCurrentSongChangeListener(listener) {
const removeIndex = this.#currentSongChangeListeners.indexOf(listener);
if (removeIndex < 0) return false;
this.#currentSongChangeListeners.splice(removeIndex, 1);
return true;
}
/** /**
* *
* @param {changeListener} listener the playlist change listener to add to the callback list. * @param {changeListener} listener the playlist change listener to add to the callback list.
@ -336,7 +286,7 @@ export default class SongPlayer {
} }
if (typeListeners.includes(eventListener)) return false; if (typeListeners.includes(eventListener)) return false;
typeListeners.push(eventListener); typeListeners.push(eventListener);
this.currentSong.fetchAudio().addEventListener(type, eventListener); this.playlist.currentMusic.fetchAudio().addEventListener(type, eventListener);
return true; return true;
} }
@ -352,14 +302,23 @@ export default class SongPlayer {
const removeIndex = typeListeners.indexOf(eventListener); const removeIndex = typeListeners.indexOf(eventListener);
if (removeIndex < 0) return false; if (removeIndex < 0) return false;
typeListeners.splice(removeIndex, 1); typeListeners.splice(removeIndex, 1);
this.currentSong.fetchAudio().removeEventListener(type, eventListener); this.playlist.playlist.currentMusic.fetchAudio().removeEventListener(type, eventListener);
return true; return true;
} }
/** /**
* @returns {boolean} If the player is playing any songs right now. * @returns {boolean} If the player is playing any musics right now.
*/ */
get playing() { get playing() {
return this.currentSong.isAudioInstantiated() && !this.currentSong.fetchAudio().paused; return this.playlist.currentMusic.isAudioInstantiated() && !this.playlist.currentMusic.fetchAudio().paused;
}
/**
* Unloads the audio data of all musics in this playlist.
*/
unloadAllAudio() {
this.playlist.forEach(music => {
music.unloadAudio();
});
} }
} }

250
src/player/MusicPlaylist.js Normal file
View File

@ -0,0 +1,250 @@
import Music from "./Music.js";
/**@module */
/**
* @callback positionChangeListener
* @param {number} old the previous position.
* @param {number} current the the current (new) position.
*/
/**
* @callback lengthChangeListener
* @param {number} changed The index of the music that has changed.
* @param {boolean} added True if and only if the change was caused by adding music.
* @param {Music} music The music that was added or removed.
*/
/**
* A playlist that holds a multitude of musics in the form of {@link Music}.
*/
export default class MusicPlaylist {
#list = [];
#name;
#current = 0;
#positionChangeListeners = [];
#lengthChangeListeners = [];
/**
* Instantiates a playlist for musics.
*
* @param {string} name The name of the playlist.
*/
constructor(name) {
this.#name = name;
}
/**
* @returns {string} The name of the playlist.
*/
get name() {
return this.#name;
}
/**
*
* @param {number} index the index of the music to retrieve.
* @returns {Music} the music at the given index.
*/
musicAtIndex(index) {
if (index >= this.total) {
return null;
}
return this.#list[index];
}
/**
* Automatically creates and adds a {@link Music} to this playlist.
*
* @param {string} url where the audio data can be found.
* @param {string} name the name of the music.
* @param {string} author the author(s) of the music.
*/
add(url, name, author) {
const music = new Music(url, name, author, this);
this.#list.push(music);
this.#onAdd(this.total - 1, music);
}
/**
* Adds an existing {@link Music} object to this playlist. Alternatively, use {@link MusicPlaylist#add} to automatically instantiate a new one.
*
* @param {Music} music The existing music to add.
*/
addExisting(music) {
this.#list.push(music);
this.#onAdd(this.total - 1, music);
}
/**
* Calls all playlist length change listeners.
*
* @param {number} index The index at which the new music was added.
* @param {Music} music The music that was added.
*/
#onAdd(index, music) {
this.#lengthChangeListeners.forEach(listener => {
listener(index, true, music);
});
}
/**
* Removes a {@link Music} from this playlist.
*
* @param {number} index the index of the music to be removed.
* @returns {Music} the music that was removed, or null if the index of was invalid.
*/
remove(index) {
if (index >= this.#list.length) {
return null;
}
let removed = this.#list.splice(index, 1)[0];
this.#lengthChangeListeners.forEach(listener => {
listener(index, false, removed);
});
if (removed.length > 0) {
return removed[0];
}
}
/**
* Attempts to find a {@link Music} given a name. Returns the first one found.
*
* @param {string} name the name of the music to be found.
* @returns {number} the index of the music found, or -1 if it was not found.
*/
findMusicIndex(name) {
return this.#list.findIndex((item) => item.getDisplayName() == name);
}
/**
* The number of {@link Music} in this playlist.
*
* @returns {number} total number of musics in this playlist.
*/
get total() {
return this.#list.length;
}
/**
* @returns {number} The current position in the playlist.
*/
get currentPosition() {
return this.#current;
}
/**
* The current position in the playlist.
*/
set currentPosition(position) {
if (typeof position !== "number") throw new Error("Given position is invalid.");
if (position >= this.#list.length || position < 0) return;
const old = this.#current;
this.#current = position;
this.#positionChangeListeners.forEach(positionChangeListener => {
positionChangeListener(old, position);
});
}
/**
* @returns {Music} The current music in the playlist being used.
*/
get currentMusic() {
return this.musicAtIndex(this.currentPosition);
}
/**
*
* @param {positionChangeListener} listener the listener to receive the updates.
* @returns {boolean} true if and only if successfully added the listener.
*/
addPositionChangeListener(listener) {
if (this.#positionChangeListeners.includes(listener)) return false;
this.#positionChangeListeners.push(listener);
return true;
}
/**
*
* @param {positionChangeListener} listener the music change listener to remove.
* @returns {boolean} true if and only if the music change listener given was successfully removed.
*/
removePositionChangeListener(listener) {
const removeIndex = this.#positionChangeListeners.indexOf(listener);
if (removeIndex < 0) return false;
this.#positionChangeListeners.splice(removeIndex, 1);
return true;
}
/**
* Adds a listener for whether music was added or removed to the playlist.
*
* @param {lengthChangeListener} listener The listener to be added.
* @returns {boolean} True if and only if the listener was successfully added.
*/
addLengthChangeListener(listener) {
if (this.#lengthChangeListeners.includes(listener)) return false;
this.#lengthChangeListeners.push(listener);
return true;
}
/**
* Removes a listener for whether music is added or removed to the playlist.
*
* @param {lengthChangeListener} listener The listener to be removed.
* @returns {boolean} True if and only if the provided listener was successfully removed.
*/
removeLengthChangeListener(listener) {
const removeIndex = this.#lengthChangeListeners.indexOf(listener);
if (removeIndex < 0) return false;
this.#lengthChangeListeners.splice(removeIndex, 1);
return true;
}
generatePlaylistElement() {
const element = document.createElement("table");
element.createTHead().textContent = "Playlist";
const headers = element.insertRow();
const musicHeader = document.createElement("th");
musicHeader.textContent = "Music";
headers.appendChild(musicHeader);
const authorHeader = document.createElement("th");
authorHeader.textContent = "Author";
headers.appendChild(authorHeader);
const insertMusic = (music, musicPos = undefined) => {
if (musicPos) musicPos++;
const row = element.insertRow(musicPos);
row.insertCell().textContent = music.displayName;
row.insertCell().textContent = music.author;
};
for (const music of this) {
insertMusic(music);
}
this.addLengthChangeListener((changed, added, music) => {
if (added) {
insertMusic(music, changed);
} else {
element.deleteRow(changed);
}
});
element.rows[this.currentPosition + 1].classList.add("active");
this.addPositionChangeListener((old, current) => {
element.rows[old + 1].classList.remove("active");
element.rows[current + 1].classList.add("active");
});
return element;
}
[Symbol.iterator]() {
let index = -1;
const data = this.#list;
return {
next: () => ({ value: data[++index], done: index >= data.length }) // Brackets so not read as a body of anonymous function.
};
}
}

View File

@ -1,93 +0,0 @@
import PlaylistSong from "./PlaylistSong.js";
/**@module */
/**
* A playlist that holds a multitude of songs in the form of {@link PlaylistSong}.
*/
export default class SongPlaylist {
#list = [];
#name;
/**
* Instantiates a playlist for songs.
*
* @param {string} name The name of the playlist.
*/
constructor(name) {
this.#name = name;
}
/**
* @returns {string} The name of the playlist.
*/
get name() {
return this.#name;
}
/**
*
* @param {number} index the index of the song to retrieve.
* @returns {PlaylistSong} the song at the given index.
*/
songAtIndex(index) {
if (index >= this.#list.length) {
return null;
}
return this.#list[index];
}
/**
* Automatically creates and adds a {@see PlaylistSong} to this playlist.
*
* @param {string} url where the audio data can be found.
* @param {string} name the name of the song.
* @param {string} author the author(s) of the song.
*/
add(url, name, author) {
this.#list.push(new PlaylistSong(url, name, author, this));
}
/**
* Removes a {@see playlistSong} from this playlist.
*
* @param {number} index the index of the song to be removed.
* @returns {PlaylistSong} the song that was removed, or null if the index of was invalid.
*/
remove(index) {
if (index >= this.#list.length) {
return null;
}
let removed = this.#list.splice(index, 1)[0];
if (removed.length > 0) {
return removed[0];
}
}
/**
* Attempts to find a {@link PlaylistSong} given a name.
*
* @param {string} name the name of the song to be found.
* @returns {number} the index of the song found, or -1 if it was not found.
*/
findSongIndex(name) {
return this.#list.findIndex((item) => item.getDisplayName() == name);
}
/**
*
* @returns {number} total number of songs in this playlist.
*/
total() {
return this.#list.length;
}
/**
* Unloads the audio data of all songs in this playlist.
*/
unloadAllAudio() {
this.#list.forEach(playlistSong => {
playlistSong.unloadAudio();
});
}
}

View File

@ -1,30 +1,30 @@
import Visualizer from "../visualization/Visualizer.js"; import Visualizer from "../visualization/Visualizer.js";
import VisualizerUpdateManager from "../visualization/VisualizerUpdateManager.js"; import VisualizerUpdateManager from "../visualization/VisualizerUpdateManager.js";
import SongPlayer from "./SongPlayer.js"; import MusicPlayer from "./MusicPlayer.js";
import SongPlaylist from "./SongPlaylist.js"; import MusicPlaylist from "./MusicPlaylist.js";
/**@module */ /**@module */
/** /**
* A song player that provides easier access to the current songs visualizer data. * A music player that provides easier access to the current musics visualizer data.
* Additionally, automatically re-binds all the visualizer update listeners for song changes. * Additionally, automatically re-binds all the visualizer update listeners for music changes.
* *
* @augments SongPlayer * @augments MusicPlayer
*/ */
export default class VisualizedSongPlayer extends SongPlayer { export default class VisualizedMusicPlayer extends MusicPlayer {
#fftSize = 1024; #fftSize = 1024;
#visualizerUpdateManager; #visualizerUpdateManager;
/** /**
* Instantiates a song player with visualization features. * Instantiates a music player with visualization features.
* *
* @param {SongPlaylist} playlist the playlist this player manages. * @param {MusicPlaylist} playlist the playlist this player manages.
* @param {number} [fftSize=1024] the size of the fft window for analysis. * @param {number} [fftSize=1024] the size of the fft window for analysis.
*/ */
constructor(playlist, fftSize = 1024) { constructor(playlist, fftSize = 1024) {
super(playlist); super(playlist);
this.#fftSize = fftSize; this.#fftSize = fftSize;
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer()); this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentMusicVisualizer());
} }
/** /**
@ -32,44 +32,44 @@ export default class VisualizedSongPlayer extends SongPlayer {
*/ */
set playlist(playlist) { set playlist(playlist) {
super.playlist = playlist; super.playlist = playlist;
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer()); this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentMusicVisualizer());
} }
/** /**
* *
* @returns {boolean} true if and only if successful in changing to the next song. * @returns {boolean} true if and only if successful in changing to the next music.
*/ */
next() { next() {
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners(); const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
if (!super.next()) return false; if (!super.next()) return false;
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer()); this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentMusicVisualizer());
this.#visualizerUpdateManager.setBinnedListeners(updateListeners); this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
return true; return true;
} }
/** /**
* Jumps to the previous song if possible. * Jumps to the previous music if possible.
* *
* @returns {boolean} true if and only if successful in switching to the previous song in the playlist. * @returns {boolean} true if and only if successful in switching to the previous music in the playlist.
*/ */
previous() { previous() {
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners(); const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
if (!super.previous()) return false; if (!super.previous()) return false;
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer()); this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentMusicVisualizer());
this.#visualizerUpdateManager.setBinnedListeners(updateListeners); this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
return true; return true;
} }
/** /**
* *
* @param {number} index the index of the song to change to. * @param {number} index the index of the music to change to.
* @returns {boolean} true if and only if successful in jumping to the given index. * @returns {boolean} true if and only if successful in jumping to the given index.
*/ */
changeCurrent(index) { changeCurrent(index) {
const updateListeners = this.VisualizerUpdateManager.getBinnedListeners(); const updateListeners = this.VisualizerUpdateManager.getBinnedListeners();
if (!super.changeCurrent(index)) return false; if (!super.changeCurrent(index)) return false;
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer()); this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentMusicVisualizer());
this.#visualizerUpdateManager.setBinnedListeners = updateListeners(); this.#visualizerUpdateManager.setBinnedListeners = updateListeners();
return true; return true;
} }
@ -82,9 +82,9 @@ export default class VisualizedSongPlayer extends SongPlayer {
} }
/** /**
* @returns {Visualizer} The current song's visualizer. * @returns {Visualizer} The current music's visualizer.
*/ */
get currentSongVisualizer() { get currentMusicVisualizer() {
return this.getCurrentSong().getVisualizer(); return this.getCurrentMusic().getVisualizer();
} }
} }

View File

@ -1,7 +1,7 @@
import SongPlayer from "./SongPlayer.js"; import MusicPlayer from "./MusicPlayer.js";
import SongPlaylist from "./SongPlaylist.js"; import MusicPlaylist from "./MusicPlaylist.js";
import VisualizedSongPlayer from "./VisualizedSongPlayer.js"; import VisualizedMusicPlayer from "./VisualizedMusicPlayer.js";
import PlayListSong from "./PlaylistSong.js"; import Music from "./Music.js";
/**@module */ /**@module */
export { SongPlayer, SongPlaylist, VisualizedSongPlayer, PlayListSong }; export { MusicPlayer, MusicPlaylist, VisualizedMusicPlayer, Music };

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="./assets/css/prism.css" />
<script src="./assets/js/prism.js" defer></script>
</head>
<body>
<script src="./assets/js/audioshowkit.js"></script>
<div>
<h1>Creating a Music Player</h1>
<p>One of the most basic principles behind AudioShowKit is the {@link module:player/MusicPlayer}. The song player acts as an easy way for developers to set up a list of songs with simple generated controls while exposing more complex events if needed.</p>
</div>
<div>
<h2>Instantiating a Music Player</h2>
<p>Instantiating a music player requires a {@link module:player/MusicPlaylist}. See the [MusicPlaylist tutorial]{@tutorial MusicPlaylist} for information on how to get one of those! Once you have one, you can proceed with instantiating a Music Player.</p>
<pre><code class="language-js">
const ask = window.audioshowkit; // Get a reference to the audioshowkit stuff.
const playlist = previousPlaylist; // We are assuming you have a playlist ready.
const player = new ask.player.MusicPlayer(playlist) // Creates a new music player with the playlist.
</code></pre>
</div>
<div>
<h2>Display a Play Button</h2>
<p>We need some user interactive element to start playing music. You can set this up yourself, or use a generated one by us! We'll show you how to do the latter, but feel free to read the documentation on all the methods available that can be used in event listeners to see how to do the former.</p>
<pre><code class="language-js">
// Still using the same context from the instantiation example...
const playElement = player.generatePlayElement();
document.getElementById("musicplayer-playbtn-demo").appendChild(playElement); // Assuming this element exists.
</code></pre>
<h3>The play button</h3>
<p>Go ahead and hit the play button!</p>
<div class="result">
<div id="musicplayer-playbtn-demo">
</div>
</div>
</div>
<div>
<h2>Similarly, Next and Previous Buttons</h2>
<p>We can also traverse the playlist by the player via a previous and next button. These buttons can be generated as well as programmed.</p>
<pre><code class="language-js">
// Still using the same context from the instantiation example...
const nextElement = player.generateNextElement();
document.getElementById("musicplayer-nextbtn-demo").appendChild(nextElement);
const prevElem = player.generatePreviousElement();
document.getElementById("musicplayer-prevbtn-demo").appendChild(prevElem);
</code></pre>
<h3>The next button</h3>
<div class="result">
<div id="musicplayer-nextbtn-demo">
</div>
</div>
<h3>The previous button</h3>
<div class="result">
<div id="musicplayer-prevbtn-demo">
</div>
</div>
</div>
<script>
const ask = window.audioshowkit;
const playlist = new ask.player.MusicPlaylist("Awesome Music");
playlist.add("https://res.sys.reslate.xyz/dl/audio/moments.mp3", "Moments", "Lost Identities x Robbie Rosen");
playlist.add("https://res.sys.reslate.xyz/dl/audio/XXI.mp3", "XXI", "QR");
const player = new ask.player.MusicPlayer(playlist);
const playElem = player.generatePlayElement();
document.getElementById("musicplayer-playbtn-demo").appendChild(playElem);
const nextElem = player.generateNextElement();
document.getElementById("musicplayer-nextbtn-demo").appendChild(nextElem);
const prevElem = player.generatePreviousElement();
document.getElementById("musicplayer-prevbtn-demo").appendChild(prevElem);
</script>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="./assets/css/prism.css" />
<script src="./assets/js/prism.js" defer></script>
</head>
<body>
<script src="./assets/js/audioshowkit.js"></script>
<div>
<h1>Creating a Music Playlist</h1>
<p>
A music playlist is a list of {@link module:player/Music} which maintains metadata about the playlist, such as the length, and the name of the playlist. It also keeps track of an index for a position in the playlist.
</p>
</div>
<div>
<h2>Instantiation</h2>
<pre><code class="language-js">
// Get the entry point to all of AudioShowKit's glory.
const ask = window.audioshowkit;
// The only parameter is the name of the playlist.
const playlist = new ask.player.MusicPlaylist("Awesome Music");
</code></pre>
</div>
<div>
<h2>Adding Music</h2>
<p>A music playlist is no good if it doesn't have music. Lets add some.</p>
<pre><code class="language-js">
// Still using the same context from the instantiation example...
playlist.add("/assets/audio/moments.mp3", "Moments", "Lost Identities x Robbie Rosen"); // It's that easy.
playlist.add("/assets/audio/XXI.mp3", "XXI", "QR"); // Let's add another one.
</code></pre>
<p>If for whatever reason, you decided to instantiate your own {@link module:player/Music}, that's okay too!</p>
<pre><code class="language-js">
// Still using the same context from the instantiation example...
// Either instantiate, or otherwise have this Music object.
const pathetiqueMusic = new ask.player.Music("/assets/audio/pathetique.mp3", "Sonata No. 8", "Beethoven");
playlist.addExisting(pathetiqueMusic); // Wow, a special function!
</code></pre>
</div>
<div>
<h2>Display</h2>
<p>In the case you want to show the playlist without coding your own display, we provide a simple generator for you!</p>
<pre><code class="language-js">
// Still using the same context from the instantiation example...
const playlistElem = playlist.generatePlaylistElement(); // Returns an HTMLElement!
// Assuming we do have an acceptable element that can take a child element.
document.getElementById("musicplaylist-demo").appendChild(playlistElem);
</code></pre>
<div class="result">
<h3>Result</h3>
<div id="musicplaylist-demo">
</div>
</div>
<script>
const ask = window.audioshowkit;
const playlist = new ask.player.MusicPlaylist("Awesome Music");
playlist.add("/assets/audio/moments.mp3", "Moments", "Lost Identities x Robbie Rosen"); // It's that easy.
playlist.add("/assets/audio/XXI.mp3", "XXI", "QR"); // Let's add another one.
const pathetiqueMusic = new ask.player.Music("/assets/audio/pathetique.mp3", "Sonata No. 8", "Beethoven");
playlist.addExisting(pathetiqueMusic);
const playlistElem = playlist.generatePlaylistElement();
document.getElementById("musicplaylist-demo").appendChild(playlistElem);
</script>
</div>
</body>
</html>

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Creating a Song Player</title>
</head>
<body>
<div>
<h1>Creating a Song Player</h1>
<p>One of the most basic principles behind AudioShowKit is the {@link module:player/SongPlayer}. The song player acts as an easy way for developers to set up a list of songs and play them back with simple controls.</p>
</div>
</body>
</html>

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
</body>
</html>

Binary file not shown.

View File

@ -0,0 +1,3 @@
/* PrismJS 1.28.0
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript */
code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,9 @@ const webpackCommon = require("./webpack.common.cjs");
const devConfig = { const devConfig = {
mode: "development", mode: "development",
devtool: "eval-source-map", devtool: "eval-source-map",
watchOptions: {
ignored: "./tutorials/**"
}
}; };
module.exports = merge(webpackCommon, devConfig); module.exports = merge(webpackCommon, devConfig);