installed plugin AudioIgniter
version 1.7.3
This commit is contained in:
7
wp-content/plugins/audioigniter/player/.babelrc
Normal file
7
wp-content/plugins/audioigniter/player/.babelrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react",
|
||||
"stage-2"
|
||||
]
|
||||
}
|
18
wp-content/plugins/audioigniter/player/.editorconfig
Normal file
18
wp-content/plugins/audioigniter/player/.editorconfig
Normal file
@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.php]
|
||||
indent_style = tab
|
||||
|
||||
[*.js]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
indent_size = 4
|
2
wp-content/plugins/audioigniter/player/.eslintignore
Normal file
2
wp-content/plugins/audioigniter/player/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
build/
|
||||
node_modules/
|
38
wp-content/plugins/audioigniter/player/.eslintrc
Normal file
38
wp-content/plugins/audioigniter/player/.eslintrc
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/react"
|
||||
],
|
||||
"plugins": [
|
||||
"import"
|
||||
],
|
||||
"globals": {
|
||||
"aiStrings": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"arrow-body-style": 0,
|
||||
"no-confusing-arrow": 0,
|
||||
"global-require": 0,
|
||||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
|
||||
"import/prefer-default-export": 0,
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react/require-default-props": 0,
|
||||
"react/forbid-prop-types": 0,
|
||||
"react/default-props-match-prop-types": 0,
|
||||
"react/prefer-stateless-function": 0,
|
||||
"react/jsx-curly-spacing": [2, {
|
||||
"when": "never",
|
||||
"children": true
|
||||
}],
|
||||
"react/no-array-index-key": 0,
|
||||
"jsx-a11y/anchor-is-valid": 0,
|
||||
"jsx-a11y/no-static-element-interactions": 0,
|
||||
"react/destructuring-assignment": 0,
|
||||
"react/button-has-type": 0,
|
||||
"jsx-a11y/label-has-for": 0
|
||||
}
|
||||
}
|
1
wp-content/plugins/audioigniter/player/.nvmrc
Normal file
1
wp-content/plugins/audioigniter/player/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
v8.12.0
|
13
wp-content/plugins/audioigniter/player/.prettierrc
Normal file
13
wp-content/plugins/audioigniter/player/.prettierrc
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "never",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false
|
||||
}
|
1
wp-content/plugins/audioigniter/player/build/app.js
Normal file
1
wp-content/plugins/audioigniter/player/build/app.js
Normal file
File diff suppressed because one or more lines are too long
100
wp-content/plugins/audioigniter/player/build/index.html
Normal file
100
wp-content/plugins/audioigniter/player/build/index.html
Normal file
@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>AudioIgniter</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
</style>
|
||||
<link href="style.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<div
|
||||
class="audioigniter-root"
|
||||
data-player-type="full"
|
||||
data-tracks-url="/dev-tracks.json"
|
||||
data-display-active-cover="true"
|
||||
data-display-tracklist-covers="true"
|
||||
data-display-credits="true"
|
||||
data-display-tracklist="true"
|
||||
data-allow-tracklist-toggle="true"
|
||||
data-allow-tracklist-loop="true"
|
||||
data-allow-track-loop="true"
|
||||
data-allow-playback-rate="true"
|
||||
data-display-track-no="true"
|
||||
data-display-artist-names="true"
|
||||
data-display-buy-buttons="true"
|
||||
data-buy-buttons-target="true"
|
||||
data-volume="50"
|
||||
data-cycle-tracks="true"
|
||||
data-limit-tracklist-height="true"
|
||||
data-tracklist-height="185"
|
||||
data-reverse-track-order="false"
|
||||
data-skip-amount="15"
|
||||
data-max-width="600px"
|
||||
data-initial-track="1"
|
||||
data-stop-on-finish="false"
|
||||
data-tracks-delay="5"
|
||||
data-timer-countdown="false"
|
||||
data-shuffle="true"
|
||||
data-shuffle-default="false"
|
||||
data-soundcloud-client-id=""
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="audioigniter-root"
|
||||
data-player-type="simple"
|
||||
data-tracks-url="/dev-tracks.json"
|
||||
data-display-credits="true"
|
||||
data-display-track-no="true"
|
||||
data-allow-playback-rate="true"
|
||||
data-display-artist-names="true"
|
||||
data-display-buy-buttons="true"
|
||||
data-buy-buttons-target="true"
|
||||
data-allow-track-loop="true"
|
||||
data-volume="50"
|
||||
data-reverse-track-order="false"
|
||||
data-max-width="600px"
|
||||
data-initial-track="3"
|
||||
data-stop-on-finish="false"
|
||||
data-tracks-delay="0"
|
||||
data-timer-countdown="false"
|
||||
data-shuffle="true"
|
||||
data-soundcloud-client-id=""
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="audioigniter-root"
|
||||
data-player-type="global-footer"
|
||||
data-tracks-url="/dev-tracks.json"
|
||||
data-display-active-cover="true"
|
||||
data-display-tracklist-covers="true"
|
||||
data-display-credits="true"
|
||||
data-display-tracklist="false"
|
||||
data-allow-tracklist-toggle="true"
|
||||
data-allow-tracklist-loop="true"
|
||||
data-allow-track-loop="true"
|
||||
data-display-track-no="true"
|
||||
data-allow-playback-rate="true"
|
||||
data-display-artist-names="true"
|
||||
data-display-buy-buttons="true"
|
||||
data-buy-buttons-target="true"
|
||||
data-volume="50"
|
||||
data-skip-amount="15"
|
||||
data-cycle-tracks="false"
|
||||
data-limit-tracklist-height="true"
|
||||
data-tracklist-height="185"
|
||||
data-reverse-track-order="false"
|
||||
data-max-width="600px"
|
||||
data-initial-track="1"
|
||||
data-stop-on-finish="false"
|
||||
data-tracks-delay="0"
|
||||
data-timer-countdown="true"
|
||||
data-shuffle="true"
|
||||
data-soundcloud-client-id=""
|
||||
></div>
|
||||
<script type="text/javascript" src="app.js"></script><script type="text/javascript" src="style.js"></script></body>
|
||||
</html>
|
1
wp-content/plugins/audioigniter/player/build/style.css
Normal file
1
wp-content/plugins/audioigniter/player/build/style.css
Normal file
File diff suppressed because one or more lines are too long
1
wp-content/plugins/audioigniter/player/build/style.js
Normal file
1
wp-content/plugins/audioigniter/player/build/style.js
Normal file
@ -0,0 +1 @@
|
||||
!function(n){function r(e){if(t[e])return t[e].exports;var o=t[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var t={};r.m=n,r.c=t,r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e})},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},r.p="",r(r.s=28)}({28:function(n,r){}});
|
64
wp-content/plugins/audioigniter/player/dev-tracks.json
Normal file
64
wp-content/plugins/audioigniter/player/dev-tracks.json
Normal file
@ -0,0 +1,64 @@
|
||||
[
|
||||
{
|
||||
"title": "Sunrise",
|
||||
"subtitle": "Thoribass",
|
||||
"audio": "https://www.cssigniter.com/assets/audioigniter/sunrise.mp3",
|
||||
"buyUrl": "https://www.cssigniter.com",
|
||||
"downloadUrl": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3",
|
||||
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/Thoribass-Sunrise.jpg",
|
||||
"lyrics": "Here in my mind\nYou know you might find\nSomething that you\n\nYou thought you once knew\nBut now it's all gone\nAnd you know it's no fun\n\nYeah I know it's no fun\nOh I know it's no fun\nI'm free to be whatever I\nWhatever I choose\nAnd I'll sing the blues if I want\nI'm free to be whatever I\nWhatever I choose\nAnd I'll sing the blues if I want\nWhatever you do\nWhatever you say\nYeah I know it's alright"
|
||||
},
|
||||
{
|
||||
"title": "Seriously long title lorem ipsum dolor sit amet, consectetur adipiscing elit. Non est enim.",
|
||||
"subtitle": "The Fisherman",
|
||||
"audio": "https://www.cssigniter.com/assets/audioigniter/sunrise.mp3",
|
||||
"buyUrl": "https://www.cssigniter.com",
|
||||
"downloadUrl": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3",
|
||||
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/The-Fisherman-Another-Day.jpg"
|
||||
},
|
||||
{
|
||||
"title": "Remix Safety Guide",
|
||||
"subtitle": "Rocavaco",
|
||||
"audio": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/remix.mp3",
|
||||
"buyUrl": "https://www.cssigniter.com",
|
||||
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/Rocavaco-Remix-Safety-Guide.jpg",
|
||||
"lyrics": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. At eius hic illo natus vitae. Assumenda commodi eaque eos est eum excepturi fugiat provident, quidem saepe? Aut doloremque, unde? Delectus, dolorum."
|
||||
},
|
||||
{
|
||||
"title": "Tomorrow",
|
||||
"subtitle": "MegaEnx",
|
||||
"audio": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/tomorrow.mp3",
|
||||
"buyUrl": "",
|
||||
"downloadUrl": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3",
|
||||
"cover": ""
|
||||
},
|
||||
{
|
||||
"title": "Is It Because I'm Black (David August Reconstruction)",
|
||||
"subtitle": "Syl Johnson (SoundCloud)",
|
||||
"audio": "https://soundcloud.com/enterofficial/maceo-plex-b2b-richie-hawtin-enterweek-11-sake-bar-space-ibiza-september-10th-2015",
|
||||
"buyUrl": "",
|
||||
"cover": "https://www.cssigniter.com/preview/audioigniter/files/2016/08/artworks-000103551140-ez6k4x-t500x500.jpg"
|
||||
},
|
||||
{
|
||||
"title": "Deep House Radio",
|
||||
"subtitle": "",
|
||||
"audio": "https://deephouseradio.radioca.st/stream/1/",
|
||||
"buyUrl": "https://www.cssigniter.com",
|
||||
"cover": "https://www.cssigniter.com/preview/audioigniter/files/2016/08/Rocavaco-Remix-Safety-Guide.jpg"
|
||||
},
|
||||
{
|
||||
"title": "Flash of Light",
|
||||
"subtitle": "Kxmode",
|
||||
"audio": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/flashlight.mp3",
|
||||
"buyUrl": "https://www.cssigniter.com",
|
||||
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/Kxmode-Flash-of-Light.jpg",
|
||||
"lyrics": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. At eius hic illo natus vitae. Assumenda commodi eaque eos est eum excepturi fugiat provident, quidem saepe? Aut doloremque, unde? Delectus, dolorum."
|
||||
},
|
||||
{
|
||||
"title": "We Get Mental",
|
||||
"subtitle": "BitBurner",
|
||||
"audio": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/mental.mp3",
|
||||
"buyUrl": "",
|
||||
"cover": "https:\/\/www.cssigniter.com\/preview\/audioigniter\/files\/2016\/08\/BitBurner-We-Get-Mental.jpg"
|
||||
}
|
||||
]
|
62
wp-content/plugins/audioigniter/player/package.json
Normal file
62
wp-content/plugins/audioigniter/player/package.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "audioigniter",
|
||||
"version": "1.6.1",
|
||||
"description": "React audio player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"build": "rm -rf ./build && webpack",
|
||||
"lint": "eslint ./src --ext .js --ext .jsx --cache || true",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"audio",
|
||||
"audio player",
|
||||
"react"
|
||||
],
|
||||
"author": "vmasto",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.25.0",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-react-hmre": "1.1.1",
|
||||
"babel-preset-stage-1": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"css-loader": "^0.28.4",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-config-airbnb": "15.0.2",
|
||||
"eslint-config-airbnb-base": "^11.2.0",
|
||||
"eslint-config-prettier": "^4.1.0",
|
||||
"eslint-plugin-import": "2.7.0",
|
||||
"eslint-plugin-jsx-a11y": "5.1.1",
|
||||
"eslint-plugin-prettier": "^3.0.1",
|
||||
"eslint-plugin-react": "7.1.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"html-webpack-plugin": "^2.29.0",
|
||||
"node-sass": "^4.5.3",
|
||||
"postcss-loader": "^2.0.6",
|
||||
"precss": "^2.0.0",
|
||||
"prettier": "^1.16.4",
|
||||
"sass-loader": "^6.0.6",
|
||||
"style-loader": "^0.18.2",
|
||||
"webpack": "^3.2.0",
|
||||
"webpack-dev-server": "^2.5.1",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "2.3.0",
|
||||
"es6-promise": "^4.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.3",
|
||||
"react-custom-scrollbars": "^4.1.2",
|
||||
"react-dom": "^16.8.3",
|
||||
"react-modal": "^3.8.1",
|
||||
"react-sound": "^1.2.0",
|
||||
"soundmanager2": "^2.97.20170602",
|
||||
"sprintf-js": "1.1.1",
|
||||
"whatwg-fetch": "0.11.1"
|
||||
}
|
||||
}
|
64
wp-content/plugins/audioigniter/player/src/App.js
Normal file
64
wp-content/plugins/audioigniter/player/src/App.js
Normal file
@ -0,0 +1,64 @@
|
||||
import React, { Fragment, useState, createContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Player from './player/Player';
|
||||
import SimplePlayer from './player/SimplePlayer';
|
||||
import GlobalFooterPlayer from './player/GlobalFooterPlayer';
|
||||
import TrackLyricsModal from './player/components/TrackLyricsModal';
|
||||
|
||||
export const AppContext = createContext();
|
||||
|
||||
const App = ({ type, ...props }) => {
|
||||
const [modal, setModalState] = useState({
|
||||
open: false,
|
||||
track: null,
|
||||
});
|
||||
|
||||
const toggleLyricsModal = (open, track) =>
|
||||
setModalState(prevState => ({
|
||||
...prevState,
|
||||
track,
|
||||
open,
|
||||
}));
|
||||
|
||||
const { track, open } = modal;
|
||||
|
||||
const PlayerActual = (() => {
|
||||
if (type === 'simple') {
|
||||
return SimplePlayer;
|
||||
}
|
||||
|
||||
if (type === 'global-footer') {
|
||||
return GlobalFooterPlayer;
|
||||
}
|
||||
|
||||
return Player;
|
||||
})();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
toggleLyricsModal,
|
||||
}}
|
||||
>
|
||||
<PlayerActual {...props} />
|
||||
</AppContext.Provider>
|
||||
|
||||
{track && track.lyrics && (
|
||||
<TrackLyricsModal
|
||||
isOpen={open}
|
||||
closeModal={() => toggleLyricsModal(false)}
|
||||
>
|
||||
{track && track.lyrics}
|
||||
</TrackLyricsModal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
App.propTypes = {
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export default App;
|
100
wp-content/plugins/audioigniter/player/src/index.ejs
Normal file
100
wp-content/plugins/audioigniter/player/src/index.ejs
Normal file
@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
class="audioigniter-root"
|
||||
data-player-type="full"
|
||||
data-tracks-url="/dev-tracks.json"
|
||||
data-display-active-cover="true"
|
||||
data-display-tracklist-covers="true"
|
||||
data-display-credits="true"
|
||||
data-display-tracklist="true"
|
||||
data-allow-tracklist-toggle="true"
|
||||
data-allow-tracklist-loop="true"
|
||||
data-allow-track-loop="true"
|
||||
data-allow-playback-rate="true"
|
||||
data-display-track-no="true"
|
||||
data-display-artist-names="true"
|
||||
data-display-buy-buttons="true"
|
||||
data-buy-buttons-target="true"
|
||||
data-volume="50"
|
||||
data-cycle-tracks="true"
|
||||
data-limit-tracklist-height="true"
|
||||
data-tracklist-height="185"
|
||||
data-reverse-track-order="false"
|
||||
data-skip-amount="15"
|
||||
data-max-width="600px"
|
||||
data-initial-track="1"
|
||||
data-stop-on-finish="false"
|
||||
data-tracks-delay="5"
|
||||
data-timer-countdown="false"
|
||||
data-shuffle="true"
|
||||
data-shuffle-default="false"
|
||||
data-soundcloud-client-id=""
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="audioigniter-root"
|
||||
data-player-type="simple"
|
||||
data-tracks-url="/dev-tracks.json"
|
||||
data-display-credits="true"
|
||||
data-display-track-no="true"
|
||||
data-allow-playback-rate="true"
|
||||
data-display-artist-names="true"
|
||||
data-display-buy-buttons="true"
|
||||
data-buy-buttons-target="true"
|
||||
data-allow-track-loop="true"
|
||||
data-volume="50"
|
||||
data-reverse-track-order="false"
|
||||
data-max-width="600px"
|
||||
data-initial-track="3"
|
||||
data-stop-on-finish="false"
|
||||
data-tracks-delay="0"
|
||||
data-timer-countdown="false"
|
||||
data-shuffle="true"
|
||||
data-soundcloud-client-id=""
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="audioigniter-root"
|
||||
data-player-type="global-footer"
|
||||
data-tracks-url="/dev-tracks.json"
|
||||
data-display-active-cover="true"
|
||||
data-display-tracklist-covers="true"
|
||||
data-display-credits="true"
|
||||
data-display-tracklist="false"
|
||||
data-allow-tracklist-toggle="true"
|
||||
data-allow-tracklist-loop="true"
|
||||
data-allow-track-loop="true"
|
||||
data-display-track-no="true"
|
||||
data-allow-playback-rate="true"
|
||||
data-display-artist-names="true"
|
||||
data-display-buy-buttons="true"
|
||||
data-buy-buttons-target="true"
|
||||
data-volume="50"
|
||||
data-skip-amount="15"
|
||||
data-cycle-tracks="false"
|
||||
data-limit-tracklist-height="true"
|
||||
data-tracklist-height="185"
|
||||
data-reverse-track-order="false"
|
||||
data-max-width="600px"
|
||||
data-initial-track="1"
|
||||
data-stop-on-finish="false"
|
||||
data-tracks-delay="0"
|
||||
data-timer-countdown="true"
|
||||
data-shuffle="true"
|
||||
data-soundcloud-client-id=""
|
||||
></div>
|
||||
</body>
|
||||
</html>
|
97
wp-content/plugins/audioigniter/player/src/index.js
Normal file
97
wp-content/plugins/audioigniter/player/src/index.js
Normal file
@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import 'es6-promise/auto';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import App from './App';
|
||||
|
||||
// Set up translatable strings here
|
||||
// for development purposes only. The production build
|
||||
// gets them from WordPress's injection
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.aiStrings = {
|
||||
play_title: 'Play %s',
|
||||
pause_title: 'Pause %s',
|
||||
previous: 'Previous track',
|
||||
next: 'Next track',
|
||||
toggle_list_repeat: 'Toggle track listing repeat',
|
||||
toggle_list_visible: 'Toggle track listing visibility',
|
||||
toggle_track_repeat: 'Toggle track repeat',
|
||||
buy_track: 'Buy this track',
|
||||
download_track: 'Download this track',
|
||||
volume_up: 'Volume Up',
|
||||
volume_down: 'Volume Down',
|
||||
open_track_lyrics: 'Open track lyrics',
|
||||
set_playback_rate: 'Set playback rate',
|
||||
skip_forward: 'Skip forward',
|
||||
skip_backward: 'Skip backward',
|
||||
shuffle: 'Shuffle',
|
||||
};
|
||||
}
|
||||
|
||||
const nodes = document.getElementsByClassName('audioigniter-root');
|
||||
|
||||
function renderApp(node) {
|
||||
const type = node.getAttribute('data-player-type');
|
||||
|
||||
const props = {
|
||||
tracksUrl: node.getAttribute('data-tracks-url'),
|
||||
displayTracklistCovers: JSON.parse(
|
||||
node.getAttribute('data-display-tracklist-covers'),
|
||||
),
|
||||
displayActiveCover: JSON.parse(
|
||||
node.getAttribute('data-display-active-cover'),
|
||||
),
|
||||
displayCredits: JSON.parse(node.getAttribute('data-display-credits')),
|
||||
displayTracklist: JSON.parse(node.getAttribute('data-display-tracklist')),
|
||||
allowTracklistToggle: JSON.parse(
|
||||
node.getAttribute('data-allow-tracklist-toggle'),
|
||||
),
|
||||
allowPlaybackRate: JSON.parse(
|
||||
node.getAttribute('data-allow-playback-rate'),
|
||||
),
|
||||
allowTracklistLoop: JSON.parse(
|
||||
node.getAttribute('data-allow-tracklist-loop'),
|
||||
),
|
||||
allowTrackLoop: JSON.parse(node.getAttribute('data-allow-track-loop')),
|
||||
displayTrackNo: JSON.parse(node.getAttribute('data-display-track-no')),
|
||||
displayBuyButtons: JSON.parse(
|
||||
node.getAttribute('data-display-buy-buttons'),
|
||||
),
|
||||
buyButtonsTarget: JSON.parse(node.getAttribute('data-buy-buttons-target')),
|
||||
volume: parseInt(node.getAttribute('data-volume'), 10),
|
||||
displayArtistNames: JSON.parse(
|
||||
node.getAttribute('data-display-artist-names'),
|
||||
),
|
||||
cycleTracks: JSON.parse(node.getAttribute('data-cycle-tracks')),
|
||||
limitTracklistHeight: JSON.parse(
|
||||
node.getAttribute('data-limit-tracklist-height'),
|
||||
),
|
||||
tracklistHeight: parseInt(node.getAttribute('data-tracklist-height'), 10),
|
||||
reverseTrackOrder: JSON.parse(
|
||||
node.getAttribute('data-reverse-track-order'),
|
||||
),
|
||||
maxWidth: node.getAttribute('data-max-width'),
|
||||
soundcloudClientId: node.getAttribute('data-soundcloud-client-id'),
|
||||
skipAmount: parseInt(node.getAttribute('data-skip-amount'), 10),
|
||||
initialTrack: parseInt(node.getAttribute('data-initial-track'), 10),
|
||||
delayBetweenTracks: parseInt(node.getAttribute('data-tracks-delay'), 10),
|
||||
stopOnTrackFinish: JSON.parse(node.getAttribute('data-stop-on-finish')),
|
||||
defaultShuffle: JSON.parse(node.getAttribute('data-shuffle')),
|
||||
shuffleEnabled: JSON.parse(node.getAttribute('data-shuffle')),
|
||||
countdownTimerByDefault: JSON.parse(
|
||||
node.getAttribute('data-timer-countdown'),
|
||||
),
|
||||
};
|
||||
|
||||
render(<App type={type} {...props} />, node);
|
||||
}
|
||||
|
||||
Array.prototype.slice.call(nodes).forEach(node => {
|
||||
renderApp(node);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
window.__CI_AUDIOIGNITER_MANUAL_INIT__ = node => {
|
||||
renderApp(node);
|
||||
};
|
@ -0,0 +1,357 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sound from 'react-sound';
|
||||
import { sprintf } from 'sprintf-js';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import soundProvider from './soundProvider';
|
||||
import Cover from './components/Cover';
|
||||
import Button from './components/Button';
|
||||
import ProgressBar from './components/ProgressBar';
|
||||
import Time from './components/Time';
|
||||
import VolumeControl from './components/VolumeControl';
|
||||
import TracklistWrap from './components/TracklistWrap';
|
||||
import {
|
||||
PlayIcon,
|
||||
PauseIcon,
|
||||
NextIcon,
|
||||
PreviousIcon,
|
||||
PlaylistIcon,
|
||||
RefreshIcon,
|
||||
LyricsIcon,
|
||||
} from './components/Icons';
|
||||
import { AppContext } from '../App';
|
||||
import typographyDisabled from '../utils/typography-disabled';
|
||||
|
||||
class GlobalFooterPlayer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isTrackListOpen: this.props.displayTracklist,
|
||||
};
|
||||
|
||||
this.toggleTracklist = this.toggleTracklist.bind(this);
|
||||
}
|
||||
|
||||
toggleTracklist() {
|
||||
this.setState(state => ({
|
||||
isTrackListOpen: !state.isTrackListOpen,
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isTrackListOpen } = this.state;
|
||||
|
||||
const {
|
||||
tracks,
|
||||
playStatus,
|
||||
activeIndex,
|
||||
volume,
|
||||
position,
|
||||
duration,
|
||||
playbackRate,
|
||||
|
||||
currentTrack,
|
||||
playTrack,
|
||||
togglePlay,
|
||||
nextTrack,
|
||||
prevTrack,
|
||||
setPosition,
|
||||
setVolume,
|
||||
toggleTracklistCycling,
|
||||
cycleTracks,
|
||||
setTrackCycling,
|
||||
setPlaybackRate,
|
||||
|
||||
allowPlaybackRate,
|
||||
allowTracklistToggle,
|
||||
allowTracklistLoop,
|
||||
allowTrackLoop,
|
||||
reverseTrackOrder,
|
||||
displayTrackNo,
|
||||
displayTracklistCovers,
|
||||
displayActiveCover,
|
||||
limitTracklistHeight,
|
||||
tracklistHeight,
|
||||
displayBuyButtons,
|
||||
buyButtonsTarget,
|
||||
displayArtistNames,
|
||||
repeatingTrackIndex,
|
||||
skipAmount,
|
||||
skipPosition,
|
||||
countdownTimerByDefault,
|
||||
buffering,
|
||||
} = this.props;
|
||||
|
||||
const classes = classNames({
|
||||
'ai-wrap': true,
|
||||
'ai-type-global-footer': true,
|
||||
'ai-is-loading': !tracks.length,
|
||||
'ai-with-typography': !typographyDisabled(),
|
||||
});
|
||||
|
||||
const audioControlClasses = classNames({
|
||||
'ai-audio-control': true,
|
||||
'ai-audio-playing': playStatus === Sound.status.PLAYING,
|
||||
'ai-audio-loading': buffering,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref => (this.root = ref)} // eslint-disable-line no-return-assign
|
||||
className={classes}
|
||||
>
|
||||
<div className="ai-control-wrap">
|
||||
{displayActiveCover && (
|
||||
<Cover
|
||||
className="ai-thumb ai-control-wrap-thumb"
|
||||
src={currentTrack.cover}
|
||||
alt={currentTrack.title}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="ai-control-wrap-controls">
|
||||
<ProgressBar
|
||||
setPosition={setPosition}
|
||||
duration={duration}
|
||||
position={position}
|
||||
/>
|
||||
|
||||
<div className="ai-audio-controls-main">
|
||||
<Button
|
||||
onClick={togglePlay}
|
||||
className={audioControlClasses}
|
||||
ariaLabel={
|
||||
playStatus === Sound.status.PLAYING
|
||||
? sprintf(aiStrings.pause_title, currentTrack.title)
|
||||
: sprintf(aiStrings.play_title, currentTrack.title)
|
||||
}
|
||||
ariaPressed={playStatus === Sound.status.PLAYING}
|
||||
>
|
||||
{playStatus === Sound.status.PLAYING ? (
|
||||
<PauseIcon />
|
||||
) : (
|
||||
<PlayIcon />
|
||||
)}
|
||||
|
||||
<span className="ai-control-spinner" />
|
||||
</Button>
|
||||
|
||||
<div className="ai-audio-controls-meta">
|
||||
{tracks.length > 1 && (
|
||||
<Button
|
||||
className="ai-btn ai-tracklist-prev"
|
||||
onClick={prevTrack}
|
||||
ariaLabel={aiStrings.previous}
|
||||
>
|
||||
<PreviousIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{tracks.length > 1 && (
|
||||
<Button
|
||||
className="ai-btn ai-tracklist-next"
|
||||
onClick={nextTrack}
|
||||
ariaLabel={aiStrings.next}
|
||||
>
|
||||
<NextIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<VolumeControl
|
||||
volume={volume}
|
||||
// eslint-disable-next-line no-shadow
|
||||
setVolume={setVolume}
|
||||
/>
|
||||
|
||||
{allowTracklistLoop && (
|
||||
<Button
|
||||
className={`ai-btn ai-btn-repeat ${cycleTracks &&
|
||||
'ai-btn-active'}`}
|
||||
onClick={toggleTracklistCycling}
|
||||
ariaLabel={aiStrings.toggle_list_repeat}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{allowPlaybackRate && (
|
||||
<Button
|
||||
className="ai-btn ai-btn-playback-rate"
|
||||
onClick={setPlaybackRate}
|
||||
ariaLabel={aiStrings.set_playback_rate}
|
||||
>
|
||||
<Fragment>×{playbackRate}</Fragment>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{skipAmount > 0 && (
|
||||
<Fragment>
|
||||
<Button
|
||||
className="ai-btn ai-btn-skip-position"
|
||||
onClick={() => skipPosition(-1)}
|
||||
ariaLabel={aiStrings.skip_backward}
|
||||
>
|
||||
-{skipAmount}s
|
||||
</Button>
|
||||
<Button
|
||||
className="ai-btn ai-btn-skip-position"
|
||||
onClick={() => skipPosition(1)}
|
||||
ariaLabel={aiStrings.skip_forward}
|
||||
>
|
||||
+{skipAmount}s
|
||||
</Button>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{currentTrack && currentTrack.lyrics && !isTrackListOpen && (
|
||||
<AppContext.Consumer>
|
||||
{({ toggleLyricsModal }) => (
|
||||
<Button
|
||||
className="ai-btn ai-lyrics"
|
||||
onClick={() => toggleLyricsModal(true, currentTrack)}
|
||||
ariaLabel={aiStrings.open_track_lyrics}
|
||||
title={aiStrings.open_track_lyrics}
|
||||
>
|
||||
<LyricsIcon />
|
||||
</Button>
|
||||
)}
|
||||
</AppContext.Consumer>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="ai-track-info">
|
||||
<p className="ai-track-title">
|
||||
<span>{currentTrack.title}</span>
|
||||
</p>
|
||||
{(tracks.length === 0 || currentTrack.subtitle) &&
|
||||
displayArtistNames && (
|
||||
<p className="ai-track-subtitle">
|
||||
<span>{currentTrack.subtitle}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="ai-audio-controls-meta-right">
|
||||
<Time
|
||||
duration={duration}
|
||||
position={position}
|
||||
countdown={countdownTimerByDefault}
|
||||
/>
|
||||
|
||||
{allowTracklistToggle && (
|
||||
<Button
|
||||
className="ai-btn ai-tracklist-toggle"
|
||||
onClick={this.toggleTracklist}
|
||||
ariaLabel={aiStrings.toggle_list_visible}
|
||||
>
|
||||
<PlaylistIcon />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`ai-tracklist-wrap ${
|
||||
isTrackListOpen ? 'ai-tracklist-open' : ''
|
||||
}`}
|
||||
style={{ display: isTrackListOpen ? 'block' : 'none' }}
|
||||
>
|
||||
<TracklistWrap
|
||||
className="ai-tracklist"
|
||||
trackClassName="ai-track"
|
||||
tracks={tracks}
|
||||
activeTrackIndex={activeIndex}
|
||||
isOpen={isTrackListOpen}
|
||||
displayTrackNo={displayTrackNo}
|
||||
displayCovers={displayTracklistCovers}
|
||||
displayBuyButtons={displayBuyButtons}
|
||||
buyButtonsTarget={buyButtonsTarget}
|
||||
displayArtistNames={displayArtistNames}
|
||||
reverseTrackOrder={reverseTrackOrder}
|
||||
limitTracklistHeight={limitTracklistHeight}
|
||||
tracklistHeight={tracklistHeight}
|
||||
onTrackClick={playTrack}
|
||||
onTrackLoop={allowTrackLoop ? setTrackCycling : undefined}
|
||||
repeatingTrackIndex={repeatingTrackIndex}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GlobalFooterPlayer.propTypes = {
|
||||
tracks: PropTypes.arrayOf(PropTypes.object),
|
||||
playStatus: PropTypes.oneOf([
|
||||
Sound.status.PLAYING,
|
||||
Sound.status.PAUSED,
|
||||
Sound.status.STOPPED,
|
||||
]),
|
||||
activeIndex: PropTypes.number,
|
||||
volume: PropTypes.number,
|
||||
position: PropTypes.number,
|
||||
duration: PropTypes.number,
|
||||
currentTrack: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
playTrack: PropTypes.func.isRequired,
|
||||
togglePlay: PropTypes.func.isRequired,
|
||||
nextTrack: PropTypes.func.isRequired,
|
||||
prevTrack: PropTypes.func.isRequired,
|
||||
setPosition: PropTypes.func.isRequired,
|
||||
setVolume: PropTypes.func.isRequired,
|
||||
toggleTracklistCycling: PropTypes.func.isRequired,
|
||||
cycleTracks: PropTypes.bool.isRequired,
|
||||
displayTracklist: PropTypes.bool,
|
||||
allowTracklistToggle: PropTypes.bool,
|
||||
allowTracklistLoop: PropTypes.bool,
|
||||
reverseTrackOrder: PropTypes.bool,
|
||||
displayTrackNo: PropTypes.bool,
|
||||
displayActiveCover: PropTypes.bool,
|
||||
displayTracklistCovers: PropTypes.bool,
|
||||
limitTracklistHeight: PropTypes.bool,
|
||||
tracklistHeight: PropTypes.number,
|
||||
displayBuyButtons: PropTypes.bool,
|
||||
buyButtonsTarget: PropTypes.bool,
|
||||
displayArtistNames: PropTypes.bool,
|
||||
setTrackCycling: PropTypes.func.isRequired,
|
||||
repeatingTrackIndex: PropTypes.number,
|
||||
allowTrackLoop: PropTypes.bool,
|
||||
playbackRate: PropTypes.number,
|
||||
setPlaybackRate: PropTypes.func,
|
||||
skipAmount: PropTypes.number,
|
||||
skipPosition: PropTypes.func.isRequired,
|
||||
countdownTimerByDefault: PropTypes.bool,
|
||||
allowPlaybackRate: PropTypes.bool,
|
||||
buffering: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default soundProvider(GlobalFooterPlayer, {
|
||||
onFinishedPlaying(props) {
|
||||
const {
|
||||
repeatingTrackIndex,
|
||||
cycleTracks,
|
||||
nextTrack,
|
||||
activeIndex,
|
||||
playTrack,
|
||||
trackQueue,
|
||||
} = props;
|
||||
|
||||
if (repeatingTrackIndex != null) {
|
||||
playTrack(repeatingTrackIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cycleTracks) {
|
||||
nextTrack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if not the last track
|
||||
if (activeIndex !== trackQueue[trackQueue.length - 1]) {
|
||||
nextTrack();
|
||||
}
|
||||
},
|
||||
});
|
403
wp-content/plugins/audioigniter/player/src/player/Player.js
Normal file
403
wp-content/plugins/audioigniter/player/src/player/Player.js
Normal file
@ -0,0 +1,403 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sound from 'react-sound';
|
||||
import { sprintf } from 'sprintf-js';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import TracklistWrap from './components/TracklistWrap';
|
||||
import ProgressBar from './components/ProgressBar';
|
||||
import Time from './components/Time';
|
||||
import VolumeControl from './components/VolumeControl';
|
||||
import Button from './components/Button';
|
||||
import Cover from './components/Cover';
|
||||
import {
|
||||
PlayIcon,
|
||||
PauseIcon,
|
||||
NextIcon,
|
||||
PreviousIcon,
|
||||
PlaylistIcon,
|
||||
RefreshIcon,
|
||||
LyricsIcon,
|
||||
ShuffleIcon,
|
||||
} from './components/Icons';
|
||||
import soundProvider from './soundProvider';
|
||||
import { AppContext } from '../App';
|
||||
import typographyDisabled from '../utils/typography-disabled';
|
||||
|
||||
class Player extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isTrackListOpen: this.props.displayTracklist,
|
||||
};
|
||||
|
||||
this.toggleTracklist = this.toggleTracklist.bind(this);
|
||||
this.isNarrowContext = this.isNarrowContext.bind(this);
|
||||
}
|
||||
|
||||
isNarrowContext() {
|
||||
return this.root && this.root.offsetWidth < 480 && window.innerWidth > 480;
|
||||
}
|
||||
|
||||
toggleTracklist() {
|
||||
this.setState(state => ({
|
||||
isTrackListOpen: !state.isTrackListOpen,
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isTrackListOpen } = this.state;
|
||||
|
||||
const {
|
||||
tracks,
|
||||
playStatus,
|
||||
activeIndex,
|
||||
volume,
|
||||
position,
|
||||
duration,
|
||||
playbackRate,
|
||||
shuffle,
|
||||
shuffleEnabled,
|
||||
|
||||
currentTrack,
|
||||
playTrack,
|
||||
togglePlay,
|
||||
nextTrack,
|
||||
prevTrack,
|
||||
setPosition,
|
||||
setVolume,
|
||||
setPlaybackRate,
|
||||
toggleTracklistCycling,
|
||||
cycleTracks,
|
||||
toggleShuffle,
|
||||
|
||||
allowTracklistToggle,
|
||||
allowTracklistLoop,
|
||||
allowPlaybackRate,
|
||||
allowTrackLoop,
|
||||
setTrackCycling,
|
||||
reverseTrackOrder,
|
||||
displayTrackNo,
|
||||
displayTracklistCovers,
|
||||
displayActiveCover,
|
||||
displayCredits,
|
||||
limitTracklistHeight,
|
||||
tracklistHeight,
|
||||
displayBuyButtons,
|
||||
buyButtonsTarget,
|
||||
displayArtistNames,
|
||||
maxWidth,
|
||||
repeatingTrackIndex,
|
||||
skipAmount,
|
||||
skipPosition,
|
||||
countdownTimerByDefault,
|
||||
buffering,
|
||||
} = this.props;
|
||||
|
||||
const classes = classNames({
|
||||
'ai-wrap': true,
|
||||
'ai-type-full': true,
|
||||
'ai-is-loading': !tracks.length,
|
||||
'ai-narrow': this.isNarrowContext(),
|
||||
'ai-with-typography': !typographyDisabled(),
|
||||
});
|
||||
|
||||
const audioControlClasses = classNames({
|
||||
'ai-audio-control': true,
|
||||
'ai-audio-playing': playStatus === Sound.status.PLAYING,
|
||||
'ai-audio-loading': buffering,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref => (this.root = ref)} // eslint-disable-line no-return-assign
|
||||
className={classes}
|
||||
style={{ maxWidth }}
|
||||
>
|
||||
<div className="ai-control-wrap">
|
||||
{displayActiveCover && (
|
||||
<Cover
|
||||
className="ai-thumb ai-control-wrap-thumb"
|
||||
src={currentTrack.cover}
|
||||
alt={currentTrack.title}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="ai-control-wrap-controls">
|
||||
<div className="ai-audio-controls-main">
|
||||
<Button
|
||||
onClick={togglePlay}
|
||||
className={audioControlClasses}
|
||||
ariaLabel={
|
||||
playStatus === Sound.status.PLAYING
|
||||
? sprintf(aiStrings.pause_title, currentTrack.title)
|
||||
: sprintf(aiStrings.play_title, currentTrack.title)
|
||||
}
|
||||
ariaPressed={playStatus === Sound.status.PLAYING}
|
||||
>
|
||||
{playStatus === Sound.status.PLAYING ? (
|
||||
<PauseIcon />
|
||||
) : (
|
||||
<PlayIcon />
|
||||
)}
|
||||
|
||||
<span className="ai-control-spinner" />
|
||||
</Button>
|
||||
|
||||
<div className="ai-track-info">
|
||||
<p className="ai-track-title">
|
||||
<span>{currentTrack.title}</span>
|
||||
</p>
|
||||
{(tracks.length === 0 || currentTrack.subtitle) &&
|
||||
displayArtistNames && (
|
||||
<p className="ai-track-subtitle">
|
||||
<span>{currentTrack.subtitle}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ai-audio-controls-progress">
|
||||
<ProgressBar
|
||||
setPosition={setPosition}
|
||||
duration={duration}
|
||||
position={position}
|
||||
/>
|
||||
|
||||
<Time
|
||||
duration={duration}
|
||||
position={position}
|
||||
countdown={countdownTimerByDefault}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ai-audio-controls-meta">
|
||||
{tracks.length > 1 && (
|
||||
<Button
|
||||
className="ai-btn ai-tracklist-prev"
|
||||
onClick={prevTrack}
|
||||
ariaLabel={aiStrings.previous}
|
||||
title={aiStrings.previous}
|
||||
>
|
||||
<PreviousIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{tracks.length > 1 && (
|
||||
<Button
|
||||
className="ai-btn ai-tracklist-next"
|
||||
onClick={nextTrack}
|
||||
ariaLabel={aiStrings.next}
|
||||
title={aiStrings.next}
|
||||
>
|
||||
<NextIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<VolumeControl
|
||||
volume={volume}
|
||||
// eslint-disable-next-line no-shadow
|
||||
setVolume={setVolume}
|
||||
/>
|
||||
|
||||
{allowTracklistLoop && (
|
||||
<Button
|
||||
className={`ai-btn ai-btn-repeat ${cycleTracks &&
|
||||
'ai-btn-active'}`}
|
||||
onClick={toggleTracklistCycling}
|
||||
ariaLabel={aiStrings.toggle_list_repeat}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{shuffleEnabled && (
|
||||
<Button
|
||||
className={`ai-btn ai-btn-shuffle ${shuffle &&
|
||||
'ai-btn-active'}`}
|
||||
onClick={toggleShuffle}
|
||||
ariaLabel={aiStrings.shuffle}
|
||||
>
|
||||
<ShuffleIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{allowPlaybackRate && (
|
||||
<Button
|
||||
className="ai-btn ai-btn-playback-rate"
|
||||
onClick={setPlaybackRate}
|
||||
ariaLabel={aiStrings.set_playback_rate}
|
||||
>
|
||||
<Fragment>×{playbackRate}</Fragment>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{skipAmount > 0 && (
|
||||
<Fragment>
|
||||
<Button
|
||||
className="ai-btn ai-btn-skip-position"
|
||||
onClick={() => skipPosition(-1)}
|
||||
ariaLabel={aiStrings.skip_backward}
|
||||
>
|
||||
-{skipAmount}s
|
||||
</Button>
|
||||
<Button
|
||||
className="ai-btn ai-btn-skip-position"
|
||||
onClick={() => skipPosition(1)}
|
||||
ariaLabel={aiStrings.skip_forward}
|
||||
>
|
||||
+{skipAmount}s
|
||||
</Button>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{currentTrack && currentTrack.lyrics && !isTrackListOpen && (
|
||||
<AppContext.Consumer>
|
||||
{({ toggleLyricsModal }) => (
|
||||
<Button
|
||||
className="ai-btn ai-lyrics"
|
||||
onClick={() => toggleLyricsModal(true, currentTrack)}
|
||||
ariaLabel={aiStrings.open_track_lyrics}
|
||||
title={aiStrings.open_track_lyrics}
|
||||
>
|
||||
<LyricsIcon />
|
||||
</Button>
|
||||
)}
|
||||
</AppContext.Consumer>
|
||||
)}
|
||||
|
||||
{allowTracklistToggle && (
|
||||
<Button
|
||||
className="ai-btn ai-tracklist-toggle"
|
||||
onClick={this.toggleTracklist}
|
||||
ariaLabel={aiStrings.toggle_list_visible}
|
||||
ariaExpanded={isTrackListOpen}
|
||||
>
|
||||
<PlaylistIcon />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`ai-tracklist-wrap ${
|
||||
isTrackListOpen ? 'ai-tracklist-open' : ''
|
||||
}`}
|
||||
>
|
||||
<TracklistWrap
|
||||
className="ai-tracklist"
|
||||
trackClassName="ai-track"
|
||||
tracks={tracks}
|
||||
activeTrackIndex={activeIndex}
|
||||
isOpen={isTrackListOpen}
|
||||
displayTrackNo={displayTrackNo}
|
||||
displayCovers={displayTracklistCovers}
|
||||
displayBuyButtons={displayBuyButtons}
|
||||
buyButtonsTarget={buyButtonsTarget}
|
||||
displayArtistNames={displayArtistNames}
|
||||
reverseTrackOrder={reverseTrackOrder}
|
||||
limitTracklistHeight={limitTracklistHeight}
|
||||
tracklistHeight={tracklistHeight}
|
||||
onTrackClick={playTrack}
|
||||
onTrackLoop={allowTrackLoop ? setTrackCycling : undefined}
|
||||
repeatingTrackIndex={repeatingTrackIndex}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{displayCredits && (
|
||||
<div className="ai-footer">
|
||||
<p>
|
||||
Powered by{' '}
|
||||
<a
|
||||
href="https://www.cssigniter.com/plugins/audioigniter?utm_source=player&utm_medium=link&utm_content=audioigniter&utm_campaign=footer-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
AudioIgniter
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Player.propTypes = {
|
||||
tracks: PropTypes.arrayOf(PropTypes.object),
|
||||
playStatus: PropTypes.oneOf([
|
||||
Sound.status.PLAYING,
|
||||
Sound.status.PAUSED,
|
||||
Sound.status.STOPPED,
|
||||
]),
|
||||
activeIndex: PropTypes.number,
|
||||
volume: PropTypes.number,
|
||||
position: PropTypes.number,
|
||||
duration: PropTypes.number,
|
||||
currentTrack: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
playTrack: PropTypes.func.isRequired,
|
||||
togglePlay: PropTypes.func.isRequired,
|
||||
nextTrack: PropTypes.func.isRequired,
|
||||
prevTrack: PropTypes.func.isRequired,
|
||||
setPosition: PropTypes.func.isRequired,
|
||||
setVolume: PropTypes.func.isRequired,
|
||||
toggleTracklistCycling: PropTypes.func.isRequired,
|
||||
setTrackCycling: PropTypes.func.isRequired,
|
||||
cycleTracks: PropTypes.bool.isRequired,
|
||||
displayTracklist: PropTypes.bool,
|
||||
allowTracklistToggle: PropTypes.bool,
|
||||
allowTracklistLoop: PropTypes.bool,
|
||||
allowTrackLoop: PropTypes.bool,
|
||||
reverseTrackOrder: PropTypes.bool,
|
||||
displayTrackNo: PropTypes.bool,
|
||||
displayCredits: PropTypes.bool,
|
||||
displayActiveCover: PropTypes.bool,
|
||||
displayTracklistCovers: PropTypes.bool,
|
||||
limitTracklistHeight: PropTypes.bool,
|
||||
tracklistHeight: PropTypes.number,
|
||||
displayBuyButtons: PropTypes.bool,
|
||||
buyButtonsTarget: PropTypes.bool,
|
||||
displayArtistNames: PropTypes.bool,
|
||||
maxWidth: PropTypes.string,
|
||||
repeatingTrackIndex: PropTypes.number,
|
||||
playbackRate: PropTypes.number,
|
||||
setPlaybackRate: PropTypes.func,
|
||||
skipAmount: PropTypes.number,
|
||||
skipPosition: PropTypes.func.isRequired,
|
||||
countdownTimerByDefault: PropTypes.bool,
|
||||
allowPlaybackRate: PropTypes.bool,
|
||||
buffering: PropTypes.bool,
|
||||
shuffleEnabled: PropTypes.bool,
|
||||
shuffle: PropTypes.bool,
|
||||
toggleShuffle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default soundProvider(Player, {
|
||||
onFinishedPlaying(props) {
|
||||
const {
|
||||
repeatingTrackIndex,
|
||||
cycleTracks,
|
||||
nextTrack,
|
||||
activeIndex,
|
||||
playTrack,
|
||||
trackQueue,
|
||||
} = props;
|
||||
|
||||
if (repeatingTrackIndex != null) {
|
||||
playTrack(repeatingTrackIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cycleTracks) {
|
||||
nextTrack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if not the last track
|
||||
if (activeIndex !== trackQueue[trackQueue.length - 1]) {
|
||||
nextTrack();
|
||||
}
|
||||
},
|
||||
});
|
@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sound from 'react-sound';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import soundProvider from './soundProvider';
|
||||
import Tracklist from './components/Tracklist';
|
||||
import typographyDisabled from '../utils/typography-disabled';
|
||||
|
||||
const SimplePlayer = props => {
|
||||
const { playStatus } = props;
|
||||
const activeIndex =
|
||||
playStatus === Sound.status.PLAYING || playStatus === Sound.status.PAUSED
|
||||
? props.activeIndex
|
||||
: undefined;
|
||||
|
||||
const classes = classNames({
|
||||
'ai-wrap': true,
|
||||
'ai-type-simple': true,
|
||||
'ai-with-typography': !typographyDisabled(),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes} style={{ maxWidth: props.maxWidth }}>
|
||||
<div className="ai-tracklist ai-tracklist-open">
|
||||
<Tracklist
|
||||
tracks={props.tracks}
|
||||
playStatus={props.playStatus}
|
||||
activeTrackIndex={activeIndex}
|
||||
onTrackClick={props.togglePlay}
|
||||
setPosition={props.setPosition}
|
||||
duration={props.duration}
|
||||
position={props.position}
|
||||
playbackRate={props.playbackRate}
|
||||
className="ai-tracklist"
|
||||
trackClassName="ai-track"
|
||||
reverseTrackOrder={props.reverseTrackOrder}
|
||||
displayTrackNo={props.displayTrackNo}
|
||||
displayBuyButtons={props.displayBuyButtons}
|
||||
buyButtonsTarget={props.buyButtonsTarget}
|
||||
displayArtistNames={props.displayArtistNames}
|
||||
standaloneTracks
|
||||
onTrackLoop={props.allowTrackLoop ? props.setTrackCycling : undefined}
|
||||
repeatingTrackIndex={props.repeatingTrackIndex}
|
||||
setPlaybackRate={props.setPlaybackRate}
|
||||
allowPlaybackRate={props.allowPlaybackRate}
|
||||
buffering={props.buffering}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{props.displayCredits && (
|
||||
<div className="ai-footer">
|
||||
<p>
|
||||
Powered by{' '}
|
||||
<a
|
||||
href="https://www.cssigniter.com/plugins/audioigniter?utm_source=player&utm_medium=link&utm_content=audioigniter&utm_campaign=footer-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
AudioIgniter
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SimplePlayer.propTypes = {
|
||||
tracks: PropTypes.arrayOf(PropTypes.object),
|
||||
playStatus: PropTypes.oneOf([
|
||||
Sound.status.PLAYING,
|
||||
Sound.status.PAUSED,
|
||||
Sound.status.STOPPED,
|
||||
]),
|
||||
activeIndex: PropTypes.number,
|
||||
position: PropTypes.number,
|
||||
duration: PropTypes.number,
|
||||
setPosition: PropTypes.func.isRequired,
|
||||
togglePlay: PropTypes.func.isRequired,
|
||||
setTrackCycling: PropTypes.func.isRequired,
|
||||
allowTrackLoop: PropTypes.bool,
|
||||
|
||||
maxWidth: PropTypes.string,
|
||||
reverseTrackOrder: PropTypes.bool,
|
||||
displayTrackNo: PropTypes.bool,
|
||||
buyButtonsTarget: PropTypes.bool,
|
||||
displayArtistNames: PropTypes.bool,
|
||||
displayBuyButtons: PropTypes.bool,
|
||||
displayCredits: PropTypes.bool,
|
||||
repeatingTrackIndex: PropTypes.number,
|
||||
playbackRate: PropTypes.number,
|
||||
setPlaybackRate: PropTypes.func,
|
||||
allowPlaybackRate: PropTypes.bool,
|
||||
buffering: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default soundProvider(SimplePlayer, {
|
||||
onFinishedPlaying(props) {
|
||||
const {
|
||||
repeatingTrackIndex,
|
||||
cycleTracks,
|
||||
nextTrack,
|
||||
activeIndex,
|
||||
playTrack,
|
||||
trackQueue,
|
||||
} = props;
|
||||
|
||||
if (repeatingTrackIndex != null) {
|
||||
playTrack(repeatingTrackIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cycleTracks) {
|
||||
nextTrack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if not the last track
|
||||
if (activeIndex !== trackQueue[trackQueue.length - 1]) {
|
||||
nextTrack();
|
||||
}
|
||||
},
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Button = ({
|
||||
className,
|
||||
onClick,
|
||||
children,
|
||||
ariaLabel,
|
||||
ariaPressed,
|
||||
ariaExpanded,
|
||||
ariaControls,
|
||||
}) => (
|
||||
<button
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
aria-label={ariaLabel}
|
||||
aria-pressed={ariaPressed}
|
||||
aria-expanded={ariaExpanded}
|
||||
aria-controls={ariaControls}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
Button.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
ariaLabel: PropTypes.string,
|
||||
ariaPressed: PropTypes.bool,
|
||||
ariaExpanded: PropTypes.bool,
|
||||
ariaControls: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Button;
|
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MusicNoteIcon } from './Icons';
|
||||
|
||||
const Cover = ({ className, title, src, onClick }) => (
|
||||
<div
|
||||
className={className + (src ? '' : ' ai-track-no-thumb')}
|
||||
onClick={onClick}
|
||||
>
|
||||
{src ? <img src={src} alt={title || ''} /> : <MusicNoteIcon />}
|
||||
</div>
|
||||
);
|
||||
|
||||
Cover.propTypes = {
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
src: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Cover;
|
@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
|
||||
export const PlayIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 24">
|
||||
<path d="M18 12c0 .712-.37 1.355-.99 1.72L3.159 23.625C2.757 23.889 2.382 24 2 24c-1.103 0-2-.897-2-2V2C0 .897.897 0 2 0c.385 0 .76.111 1.085.323l13.962 9.981c.583.34.953.983.953 1.695z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const PauseIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M9 2v20c0 1.103-.897 2-2 2H2c-1.103 0-2-.897-2-2V2C0 .897.897 0 2 0h5c1.103 0 2 .897 2 2zm13-2h-5c-1.103 0-2 .897-2 2v20c0 1.103.897 2 2 2h5c1.103 0 2-.897 2-2V2c0-1.103-.897-2-2-2z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const NextIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M24 1.999v19.989c0 1.102-.897 1.999-2 1.999h-5c-1.103 0-2-.897-2-1.999v-6.837L3.16 23.612C1.597 24.635 0 23.472 0 21.988V1.999C0 .897.897 0 2 0c.384 0 .76.111 1.085.322L15 8.837V1.999C15 .897 15.897 0 17 0h5c1.103 0 2 .897 2 1.999z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const PreviousIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M24 2.014v19.987C24 23.103 23.103 24 22 24c-.385 0-.76-.111-1.085-.323L9 15.164v6.838c0 1.102-.897 1.999-2 1.999H2c-1.103 0-2-.897-2-1.999V2.015C0 .913.897.016 2 .016h5c1.103 0 2 .897 2 1.999v6.837L20.841.391C22.41-.636 24 .533 24 2.016z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlaylistIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M.871 5h10.758c.488 0 .871-.439.871-1s-.383-1-.871-1H.871C.383 3 0 3.439 0 4s.383 1 .871 1zM.871 10.25h10.758c.488 0 .871-.439.871-1s-.383-1-.871-1H.871c-.488 0-.871.439-.871 1s.383 1 .871 1zM23.595 3.129l-.002-.001c-.254-.156-.574-.17-.833-.036l-7.449 3.756c-.291.148-.472.442-.472.77v8.259c-.5-.234-1.055-.356-1.626-.356-1.841 0-3.339 1.229-3.339 2.74s1.498 2.74 3.339 2.74 3.338-1.229 3.338-2.74V8.15l5.736-2.893v8.116c-.5-.233-1.056-.355-1.627-.355-1.841 0-3.338 1.229-3.338 2.739s1.497 2.74 3.338 2.74 3.339-1.229 3.339-2.74V3.862c0-.3-.151-.574-.405-.733zM8.129 13.5H.871c-.488 0-.871.439-.871 1s.383 1 .871 1h7.258c.488 0 .871-.439.871-1s-.383-1-.871-1z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const VolumeUpIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M24 11v2c0 1.103-.897 2-2 2h-7v7c0 1.103-.897 2-2 2h-2c-1.103 0-2-.897-2-2v-7H2c-1.103 0-2-.897-2-2v-2c0-1.103.897-2 2-2h7V2c0-1.103.897-2 2-2h2c1.103 0 2 .897 2 2v7h7c1.103 0 2 .897 2 2z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const VolumeDownIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 24">
|
||||
<path d="M24 11v2c0 1.103-.897 2-2 2H2c-1.103 0-2-.897-2-2v-2c0-1.103.897-2 2-2h20c1.103 0 2 .897 2 2z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const MusicNoteIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 24">
|
||||
<path d="M18 2v16c0 1.654-1.794 3-4 3s-4-1.346-4-3 1.794-3 4-3V4.5L8 6.374V21c0 1.654-1.794 3-4 3s-4-1.346-4-3 1.794-3 4-3V5c0-.966.691-1.793 1.645-1.966L15.238.157c.204-.097.481-.157.763-.157 1.103 0 2 .897 2 2z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const CartIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M8.707 15h9.898c1.042 0 1.985-.657 2.346-1.636l2.94-7.979c.072-.196.109-.402.109-.616 0-.976-.794-1.77-1.77-1.77H5.734l-.339-1.188C5.09.744 4.101-.001 2.991-.001H.5c-.276 0-.5.224-.5.5s.224.5.5.5h2.491c.666 0 1.259.447 1.442 1.088l3.505 12.267-2.379 2.379c-.361.36-.56.841-.56 1.356 0 1.054.857 1.91 1.91 1.91h15.59c.276 0 .5-.224.5-.5s-.224-.5-.5-.5H6.909c-.502 0-.91-.408-.91-.916 0-.243.095-.472.267-.644l2.44-2.44zM18 12h-7.5c-.276 0-.5-.224-.5-.5s.224-.5.5-.5H18c.276 0 .5.224.5.5s-.224.5-.5.5zm.5-2.5H10c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h8.5c.276 0 .5.224.5.5s-.224.5-.5.5zM9.5 6H20c.276 0 .5.224.5.5s-.224.5-.5.5H9.5c-.276 0-.5-.224-.5-.5s.224-.5.5-.5zM21 20c1.103 0 2 .897 2 2s-.897 2-2 2-2-.897-2-2 .897-2 2-2zM8 20c1.103 0 2 .897 2 2s-.897 2-2 2-2-.897-2-2 .897-2 2-2z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const RefreshIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M24 12c0 2.756-2.243 4.999-5 4.999-.004 0-.02.001-.047.001-.295 0-1.919-.082-3.953-1.398v.397c0 .553-.447 1-1 1s-1-.447-1-1v-2.5c0-.553.447-1 1-1h2.5c.553 0 1 .447 1 1 0 .403-.241.745-.584.903 1.193.589 2.011.604 2.055.597 1.683 0 3.028-1.345 3.028-3s-1.346-3-3-3c-2.151 0-4.213 1.832-6.396 3.772-2.338 2.078-4.756 4.227-7.604 4.227-2.757 0-5-2.243-5-4.999S2.242 7 4.999 7c.046-.002 1.777-.044 4 1.394V8c0-.553.447-1 1-1s1 .447 1 1v2.5c0 .553-.447 1-1 1h-2.5c-.553 0-1-.447-1-1 0-.403.241-.746.585-.904-1.186-.587-1.997-.6-2.056-.596C3.345 9 2 10.346 2 12s1.346 3 3 3c2.089 0 4.122-1.807 6.275-3.722C13.641 9.176 16.087 7.001 19 7.001c2.757 0 5 2.243 5 4.999z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const DownloadIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M24 15c0 2.757-2.243 5-5 5h-.183c-.177 0-.333-.092-.422-.23-.05-.078-.078-.17-.078-.269 0-.078.018-.153.05-.219.419-.882.632-1.819.632-2.782 0-3.584-2.916-6.5-6.5-6.5s-6.5 2.916-6.5 6.5c0 .923.196 1.823.583 2.676.074.087.119.2.119.324 0 .276-.224.5-.5.5-.005.001-.013 0-.02 0h-.183c-3.309 0-6-2.691-6-6 0-2.158 1.143-4.121 3.003-5.193C3.104 5.036 6.203 2 9.998 2c2.759 0 5.205 1.58 6.35 4.062.227-.042.439-.063.65-.063 2.206 0 4 1.794 4 4 0 .142-.008.283-.024.428 1.825.785 3.024 2.572 3.024 4.572zm-6 1.5c0 3.032-2.468 5.5-5.5 5.5S7 19.532 7 16.5 9.468 11 12.5 11s5.5 2.468 5.5 5.5zm-3.146.646c-.195-.195-.512-.195-.707 0l-1.146 1.146v-4.793c0-.276-.224-.5-.5-.5s-.5.224-.5.5v4.793l-1.146-1.146c-.195-.195-.512-.195-.707 0s-.195.512 0 .707l2 2c.046.046.1.083.161.108.059.025.124.038.192.038.065 0 .129-.013.19-.038h.002c.002-.001.003-.003.005-.004.057-.024.111-.058.157-.105l2-2c.195-.195.195-.512 0-.707z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const LyricsIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M0 4.5C0 3.673.673 3 1.5 3h21c.827 0 1.5.673 1.5 1.5S23.327 6 22.5 6h-21C.673 6 0 5.327 0 4.5zM1.5 11h15c.827 0 1.5-.673 1.5-1.5S17.327 8 16.5 8h-15C.673 8 0 8.673 0 9.5S.673 11 1.5 11zm15 7h-15c-.827 0-1.5.673-1.5 1.5S.673 21 1.5 21h15c.827 0 1.5-.673 1.5-1.5s-.673-1.5-1.5-1.5zm6-5h-21c-.827 0-1.5.673-1.5 1.5S.673 16 1.5 16h21c.827 0 1.5-.673 1.5-1.5s-.673-1.5-1.5-1.5z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ShuffleIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M23.927 16.827c.098.23.098.504-.004.743-.044.111-.119.223-.212.314l-2.876 2.833c-.184.182-.428.282-.688.282s-.506-.101-.69-.283c-.187-.183-.289-.428-.289-.689s.103-.506.29-.69l1.188-1.171h-.86c-1.881 0-3.649-.722-4.979-2.034l-2.14-2.107c-.187-.185-.289-.43-.289-.69 0-.176.062-.336.149-.484l-2.372 2.337c-1.329 1.312-3.098 2.034-4.979 2.034H.98c-.54 0-.979-.436-.979-.972s.438-.972.979-.972h4.196c1.36 0 2.639-.522 3.599-1.469l2.354-2.319c-.148.086-.308.146-.484.146-.26 0-.505-.1-.689-.282l-1.179-1.163c-.962-.947-2.24-1.469-3.601-1.469H.98c-.54 0-.979-.436-.979-.972s.438-.972.979-.972h4.196c1.88 0 3.648.722 4.979 2.033l1.179 1.163c.188.184.29.429.29.69 0 .177-.063.339-.152.487l3.333-3.284c1.33-1.312 3.099-2.034 4.979-2.034h.86l-1.188-1.171c-.188-.184-.29-.429-.29-.69s.103-.506.29-.69c.379-.375.998-.375 1.379.001l2.874 2.833c.096.094.168.202.217.323.098.231.098.505-.004.743-.044.111-.116.219-.21.312l-2.878 2.835c-.363.363-1.013.365-1.38-.001-.186-.182-.288-.428-.288-.689s.104-.506.29-.69l1.188-1.17h-.86c-1.36 0-2.639.521-3.601 1.469l-3.313 3.265c.374-.215.855-.181 1.174.134l2.139 2.108c.963.947 2.241 1.469 3.602 1.469h.86l-1.188-1.171c-.188-.184-.29-.429-.29-.69s.104-.506.29-.69c.379-.374.998-.375 1.379.001l2.877 2.834c.094.094.166.202.214.321z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class ProgressBar extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleClick(event) {
|
||||
const { duration, setPosition } = this.props;
|
||||
|
||||
if (setPosition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetX =
|
||||
event.pageX - event.currentTarget.getBoundingClientRect().left;
|
||||
const posX = offsetX / event.currentTarget.offsetWidth;
|
||||
|
||||
setPosition(posX * duration);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { position, duration } = this.props;
|
||||
|
||||
return (
|
||||
<span onClick={this.handleClick} className="ai-track-progress-bar">
|
||||
<span
|
||||
className="ai-track-progress"
|
||||
style={{ width: `${(position * 100) / duration}%` }}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProgressBar.propTypes = {
|
||||
setPosition: PropTypes.func,
|
||||
position: PropTypes.number.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
};
|
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class Time extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { countdown } = this.props;
|
||||
|
||||
this.state = {
|
||||
showRemaining: countdown || false,
|
||||
};
|
||||
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty prints time remaining/elapsed
|
||||
*
|
||||
* @param {number} position - Track position in milliseconds
|
||||
* @param {number} duration - Track duration in milliseconds
|
||||
* @returns {string} - Time pretty formatted
|
||||
*/
|
||||
formatTime(position, duration) {
|
||||
const { showRemaining } = this.state;
|
||||
const positionInSeconds = showRemaining
|
||||
? (duration - position) / 1000
|
||||
: position / 1000;
|
||||
const hours = Math.floor(positionInSeconds / 3600);
|
||||
let min = Math.floor((positionInSeconds % 3600) / 60);
|
||||
let sec = Math.floor(positionInSeconds % 60);
|
||||
let time = '00:00';
|
||||
|
||||
min = min >= 10 ? min : `0${min}`;
|
||||
sec = sec >= 10 ? sec : `0${sec}`;
|
||||
|
||||
if (!isNaN(sec)) {
|
||||
if (hours) {
|
||||
time = `${hours}:${min}:${sec}`;
|
||||
} else {
|
||||
time = `${min}:${sec}`;
|
||||
}
|
||||
}
|
||||
|
||||
return showRemaining ? `-${time}` : time;
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const { showRemaining } = this.state;
|
||||
this.setState({ showRemaining: !showRemaining });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { position, duration } = this.props;
|
||||
|
||||
return (
|
||||
<span className="ai-track-time" onClick={this.handleClick}>
|
||||
{this.formatTime(position, duration)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Time.propTypes = {
|
||||
position: PropTypes.number.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
countdown: PropTypes.bool.isRequired,
|
||||
};
|
@ -0,0 +1,153 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sound from 'react-sound';
|
||||
import { sprintf } from 'sprintf-js';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import TrackTitle from './TrackTitle';
|
||||
import Cover from './Cover';
|
||||
import TrackButtons from './TrackButtons';
|
||||
import ProgressBar from './ProgressBar';
|
||||
import { PlayIcon, PauseIcon } from './Icons';
|
||||
import { AppContext } from '../../App';
|
||||
|
||||
const Track = ({
|
||||
track,
|
||||
index,
|
||||
trackNo,
|
||||
isActive,
|
||||
playStatus,
|
||||
duration,
|
||||
position,
|
||||
setPosition,
|
||||
isStandalone,
|
||||
buyButtonsTarget,
|
||||
displayArtistNames,
|
||||
displayCovers,
|
||||
displayBuyButtons,
|
||||
onTrackClick,
|
||||
onTrackLoop,
|
||||
className,
|
||||
isLooping,
|
||||
playbackRate,
|
||||
setPlaybackRate,
|
||||
allowPlaybackRate,
|
||||
buffering,
|
||||
}) => {
|
||||
const { toggleLyricsModal } = useContext(AppContext);
|
||||
const isPlaying = isActive && playStatus === Sound.status.PLAYING;
|
||||
const hasProgressBar =
|
||||
typeof position !== 'undefined' &&
|
||||
typeof duration !== 'undefined' &&
|
||||
isActive &&
|
||||
isStandalone;
|
||||
const classes = classNames({
|
||||
[className]: !!className,
|
||||
'ai-track-active': isActive,
|
||||
'ai-track-loading': isActive && buffering,
|
||||
});
|
||||
|
||||
return (
|
||||
<li className={classes}>
|
||||
{displayCovers && (
|
||||
<Cover
|
||||
className="ai-track-thumb"
|
||||
src={track.cover}
|
||||
alt={track.title}
|
||||
onClick={() => onTrackClick(index)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isStandalone && (
|
||||
<button
|
||||
className={classNames({
|
||||
'ai-track-btn ai-track-inline-play-btn': true,
|
||||
'ai-is-loading': isActive && buffering,
|
||||
})}
|
||||
onClick={() => onTrackClick(index)}
|
||||
aria-label={
|
||||
isPlaying
|
||||
? sprintf(aiStrings.pause_title, track.title)
|
||||
: sprintf(aiStrings.play_title, track.title)
|
||||
}
|
||||
aria-pressed={isPlaying}
|
||||
>
|
||||
{isPlaying ? <PauseIcon /> : <PlayIcon />}
|
||||
<span className="ai-track-spinner" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="ai-track-control" onClick={() => onTrackClick(index)}>
|
||||
<TrackTitle
|
||||
className="ai-track-name"
|
||||
track={track}
|
||||
trackNo={trackNo}
|
||||
displayArtistNames={displayArtistNames}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TrackButtons
|
||||
buyButtonsTarget={buyButtonsTarget}
|
||||
buyUrl={track.buyUrl}
|
||||
downloadUrl={track.downloadUrl}
|
||||
downloadFilename={track.downloadFilename}
|
||||
onTrackLoop={onTrackLoop && (() => onTrackLoop(index))}
|
||||
isLooping={isLooping}
|
||||
displayBuyButtons={displayBuyButtons}
|
||||
onOpenTrackLyrics={
|
||||
track.lyrics && (() => toggleLyricsModal(true, track))
|
||||
}
|
||||
playbackRate={playbackRate}
|
||||
setPlaybackRate={setPlaybackRate}
|
||||
allowPlaybackRate={allowPlaybackRate}
|
||||
isPlaying={isPlaying}
|
||||
/>
|
||||
|
||||
{hasProgressBar && (
|
||||
<ProgressBar
|
||||
setPosition={setPosition}
|
||||
duration={duration}
|
||||
position={position}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
Track.propTypes = {
|
||||
track: PropTypes.shape({
|
||||
audio: PropTypes.string,
|
||||
buyUrl: PropTypes.string,
|
||||
cover: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
lyrics: PropTypes.string,
|
||||
downloadUrl: PropTypes.string,
|
||||
}),
|
||||
index: PropTypes.number.isRequired,
|
||||
trackNo: PropTypes.number,
|
||||
isActive: PropTypes.bool,
|
||||
position: PropTypes.number,
|
||||
duration: PropTypes.number,
|
||||
setPosition: PropTypes.func,
|
||||
playStatus: PropTypes.oneOf([
|
||||
Sound.status.PLAYING,
|
||||
Sound.status.PAUSED,
|
||||
Sound.status.STOPPED,
|
||||
]),
|
||||
onTrackClick: PropTypes.func.isRequired,
|
||||
onTrackLoop: PropTypes.func,
|
||||
className: PropTypes.string.isRequired,
|
||||
isStandalone: PropTypes.bool,
|
||||
buyButtonsTarget: PropTypes.bool,
|
||||
displayArtistNames: PropTypes.bool,
|
||||
displayCovers: PropTypes.bool,
|
||||
displayBuyButtons: PropTypes.bool,
|
||||
isLooping: PropTypes.bool,
|
||||
playbackRate: PropTypes.number,
|
||||
setPlaybackRate: PropTypes.func,
|
||||
allowPlaybackRate: PropTypes.bool,
|
||||
buffering: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Track;
|
@ -0,0 +1,131 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CartIcon, DownloadIcon, LyricsIcon, RefreshIcon } from './Icons';
|
||||
|
||||
const TrackButtons = ({
|
||||
buyButtonsTarget,
|
||||
buyUrl,
|
||||
downloadUrl,
|
||||
downloadFilename,
|
||||
onTrackLoop,
|
||||
isLooping,
|
||||
displayBuyButtons,
|
||||
onOpenTrackLyrics,
|
||||
setPlaybackRate,
|
||||
playbackRate,
|
||||
allowPlaybackRate,
|
||||
isPlaying,
|
||||
}) => {
|
||||
if (
|
||||
buyUrl == null &&
|
||||
downloadUrl == null &&
|
||||
!onTrackLoop &&
|
||||
!onOpenTrackLyrics
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ai-track-control-buttons">
|
||||
{buyUrl && displayBuyButtons && (
|
||||
<a
|
||||
href={buyUrl}
|
||||
className="ai-track-btn"
|
||||
rel={buyButtonsTarget ? 'noopener noreferrer' : undefined}
|
||||
target={buyButtonsTarget ? '_blank' : '_self'}
|
||||
role="button"
|
||||
aria-label={aiStrings.buy_track}
|
||||
title={aiStrings.buy_track}
|
||||
>
|
||||
<CartIcon />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{downloadUrl && downloadFilename && displayBuyButtons && (
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download={downloadFilename}
|
||||
className="ai-track-btn"
|
||||
role="button"
|
||||
aria-label={aiStrings.download_track}
|
||||
title={aiStrings.download_track}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{onOpenTrackLyrics && (
|
||||
// eslint-disable-next-line
|
||||
<a
|
||||
href="#"
|
||||
className="ai-track-btn"
|
||||
role="button"
|
||||
aria-label={aiStrings.open_track_lyrics}
|
||||
title={aiStrings.open_track_lyrics}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
onOpenTrackLyrics();
|
||||
}}
|
||||
>
|
||||
<LyricsIcon />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{allowPlaybackRate && isPlaying && (
|
||||
<a
|
||||
href="#"
|
||||
className="ai-track-btn ai-btn-playback-rate"
|
||||
role="button"
|
||||
aria-label={aiStrings.set_playback_rate}
|
||||
title={aiStrings.set_playback_rate}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
setPlaybackRate();
|
||||
}}
|
||||
>
|
||||
<Fragment>×{playbackRate}</Fragment>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{onTrackLoop && (
|
||||
// eslint-disable-next-line
|
||||
<a
|
||||
href="#"
|
||||
className="ai-track-btn ai-track-btn-repeat"
|
||||
role="button"
|
||||
aria-label={aiStrings.toggle_track_repeat}
|
||||
title={aiStrings.toggle_track_repeat}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
onTrackLoop();
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
opacity: isLooping ? 1 : 0.3,
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TrackButtons.propTypes = {
|
||||
buyButtonsTarget: PropTypes.bool,
|
||||
buyUrl: PropTypes.string,
|
||||
downloadUrl: PropTypes.string,
|
||||
downloadFilename: PropTypes.string,
|
||||
onTrackLoop: PropTypes.func,
|
||||
isLooping: PropTypes.bool,
|
||||
displayBuyButtons: PropTypes.bool,
|
||||
onOpenTrackLyrics: PropTypes.func,
|
||||
playbackRate: PropTypes.number,
|
||||
setPlaybackRate: PropTypes.func,
|
||||
allowPlaybackRate: PropTypes.bool,
|
||||
isPlaying: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default TrackButtons;
|
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
if (document.querySelector('.audioigniter-root')) {
|
||||
Modal.setAppElement('.audioigniter-root');
|
||||
}
|
||||
|
||||
const TrackLyricsModal = ({ isOpen, closeModal, children }) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
closeModal={closeModal}
|
||||
onRequestClose={closeModal}
|
||||
overlayClassName="ai-modal-overlay"
|
||||
className="ai-modal"
|
||||
>
|
||||
<div className="ai-modal-wrap">
|
||||
<div className="ai-modal-header">
|
||||
<button
|
||||
className="ai-modal-dismiss"
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="ai-modal-content">{children}</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
TrackLyricsModal.propTypes = propTypes;
|
||||
|
||||
export default TrackLyricsModal;
|
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const TrackTitle = ({
|
||||
className,
|
||||
style,
|
||||
track,
|
||||
trackNo,
|
||||
displayArtistNames,
|
||||
}) => {
|
||||
let trackTitle = track.title;
|
||||
|
||||
if (displayArtistNames && track.subtitle) {
|
||||
trackTitle = `${track.title} - ${track.subtitle}`;
|
||||
}
|
||||
|
||||
if (trackNo != null) {
|
||||
trackTitle = `${trackNo}. ${trackTitle}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={className} style={style}>
|
||||
{trackTitle}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
TrackTitle.propTypes = {
|
||||
track: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
trackNo: PropTypes.number,
|
||||
style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
className: PropTypes.string,
|
||||
displayArtistNames: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default TrackTitle;
|
@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sound from 'react-sound';
|
||||
import Track from './Track';
|
||||
|
||||
const Tracklist = ({ ...props }) => {
|
||||
const { tracks } = props;
|
||||
|
||||
return (
|
||||
<ul className={props.className} aria-expanded="true">
|
||||
{tracks &&
|
||||
tracks.map((track, index) => {
|
||||
const trackNo = props.reverseTrackOrder
|
||||
? tracks.length - index
|
||||
: index + 1;
|
||||
const isLooping = index === props.repeatingTrackIndex;
|
||||
|
||||
return (
|
||||
<Track
|
||||
key={index}
|
||||
track={track}
|
||||
index={index}
|
||||
trackNo={props.displayTrackNo ? trackNo : undefined}
|
||||
playStatus={props.playStatus}
|
||||
isActive={props.activeTrackIndex === index}
|
||||
buyButtonsTarget={props.buyButtonsTarget}
|
||||
displayArtistNames={props.displayArtistNames}
|
||||
displayBuyButtons={props.displayBuyButtons}
|
||||
displayCovers={props.displayCovers}
|
||||
onTrackClick={props.onTrackClick}
|
||||
onTrackLoop={props.onTrackLoop}
|
||||
setPosition={props.setPosition}
|
||||
duration={props.duration}
|
||||
position={props.position}
|
||||
className={props.trackClassName}
|
||||
isStandalone={props.standaloneTracks}
|
||||
isLooping={isLooping}
|
||||
playbackRate={props.playbackRate}
|
||||
setPlaybackRate={props.setPlaybackRate}
|
||||
allowPlaybackRate={props.allowPlaybackRate}
|
||||
buffering={props.buffering}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
Tracklist.propTypes = {
|
||||
tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
playStatus: PropTypes.oneOf([
|
||||
Sound.status.PLAYING,
|
||||
Sound.status.PAUSED,
|
||||
Sound.status.STOPPED,
|
||||
]),
|
||||
activeTrackIndex: PropTypes.number,
|
||||
position: PropTypes.number,
|
||||
duration: PropTypes.number,
|
||||
setPosition: PropTypes.func,
|
||||
standaloneTracks: PropTypes.bool,
|
||||
onTrackClick: PropTypes.func.isRequired,
|
||||
onTrackLoop: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
trackClassName: PropTypes.string,
|
||||
reverseTrackOrder: PropTypes.bool,
|
||||
displayTrackNo: PropTypes.bool,
|
||||
displayBuyButtons: PropTypes.bool,
|
||||
buyButtonsTarget: PropTypes.bool,
|
||||
displayCovers: PropTypes.bool,
|
||||
displayArtistNames: PropTypes.bool,
|
||||
playbackRate: PropTypes.number,
|
||||
setPlaybackRate: PropTypes.func,
|
||||
allowPlaybackRate: PropTypes.bool,
|
||||
buffering: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Tracklist;
|
@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Scrollbars } from 'react-custom-scrollbars';
|
||||
import Tracklist from './Tracklist';
|
||||
|
||||
export default class TracklistWrap extends React.Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { activeTrackIndex, limitTracklistHeight } = this.props;
|
||||
|
||||
if (
|
||||
activeTrackIndex !== nextProps.activeTrackIndex &&
|
||||
limitTracklistHeight
|
||||
) {
|
||||
this.scrollToTrack(nextProps.activeTrackIndex);
|
||||
}
|
||||
}
|
||||
|
||||
scrollToTrack(trackIndex) {
|
||||
const { tracks } = this.props;
|
||||
const trackHeight = this.scrollbarsRef.getScrollHeight() / tracks.length;
|
||||
|
||||
if (!this.isTrackVisible(trackIndex)) {
|
||||
this.scrollbarsRef.scrollTop(trackHeight * trackIndex);
|
||||
}
|
||||
}
|
||||
|
||||
isTrackVisible(trackIndex) {
|
||||
const { tracks } = this.props;
|
||||
const trackHeight = this.scrollbarsRef.getScrollHeight() / tracks.length;
|
||||
const trackPosition = trackHeight * trackIndex;
|
||||
const scrollTop = this.scrollbarsRef.getScrollTop();
|
||||
const scrollBottom = scrollTop + this.scrollbarsRef.getClientHeight();
|
||||
|
||||
return !(trackPosition < scrollTop || trackPosition > scrollBottom);
|
||||
}
|
||||
|
||||
renderTracklist() {
|
||||
return (
|
||||
<Tracklist
|
||||
tracks={this.props.tracks}
|
||||
activeTrackIndex={this.props.activeTrackIndex}
|
||||
onTrackClick={this.props.onTrackClick}
|
||||
className={this.props.className}
|
||||
trackClassName={this.props.trackClassName}
|
||||
reverseTrackOrder={this.props.reverseTrackOrder}
|
||||
displayTrackNo={this.props.displayTrackNo}
|
||||
displayBuyButtons={this.props.displayBuyButtons}
|
||||
buyButtonsTarget={this.props.buyButtonsTarget}
|
||||
displayCovers={this.props.displayCovers}
|
||||
displayArtistNames={this.props.displayArtistNames}
|
||||
onTrackLoop={this.props.onTrackLoop}
|
||||
repeatingTrackIndex={this.props.repeatingTrackIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isOpen, limitTracklistHeight, tracklistHeight } = this.props;
|
||||
|
||||
return (
|
||||
<div id="tracklisting" style={{ display: isOpen ? 'block' : 'none' }}>
|
||||
{limitTracklistHeight ? (
|
||||
<Scrollbars
|
||||
className="ai-scroll-wrap"
|
||||
ref={ref => (this.scrollbarsRef = ref)} // eslint-disable-line no-return-assign
|
||||
style={{ height: tracklistHeight }}
|
||||
>
|
||||
{this.renderTracklist()}
|
||||
</Scrollbars>
|
||||
) : (
|
||||
this.renderTracklist()
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TracklistWrap.propTypes = {
|
||||
tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
activeTrackIndex: PropTypes.number.isRequired,
|
||||
onTrackClick: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
trackClassName: PropTypes.string,
|
||||
reverseTrackOrder: PropTypes.bool,
|
||||
displayTrackNo: PropTypes.bool,
|
||||
limitTracklistHeight: PropTypes.bool,
|
||||
tracklistHeight: PropTypes.number,
|
||||
displayBuyButtons: PropTypes.bool,
|
||||
buyButtonsTarget: PropTypes.bool,
|
||||
displayCovers: PropTypes.bool,
|
||||
displayArtistNames: PropTypes.bool,
|
||||
onTrackLoop: PropTypes.func,
|
||||
repeatingTrackIndex: PropTypes.number,
|
||||
};
|
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from './Button';
|
||||
import { VolumeUpIcon, VolumeDownIcon } from './Icons';
|
||||
|
||||
export default class VolumeControl extends React.Component {
|
||||
renderVolumeBars() {
|
||||
const { volume, setVolume } = this.props;
|
||||
|
||||
return Array(...Array(11)).map((bar, i) => (
|
||||
<span
|
||||
key={i} // eslint-disable-line react/no-array-index-key
|
||||
className={`ai-volume-bar ${
|
||||
i <= volume / 10 ? 'ai-volume-bar-active' : ''
|
||||
}`}
|
||||
onClick={() => setVolume(i * 10)}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { volume, setVolume } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ai-audio-volume-control">
|
||||
<div className="ai-audio-volume-bars">{this.renderVolumeBars()}</div>
|
||||
|
||||
<div className="ai-audio-volume-control-btns">
|
||||
<Button
|
||||
className="ai-btn"
|
||||
onClick={() => setVolume(volume >= 100 ? volume : volume + 10)}
|
||||
aria-label={aiStrings.volume_up}
|
||||
>
|
||||
<VolumeUpIcon />
|
||||
</Button>
|
||||
<Button
|
||||
className="ai-btn"
|
||||
onClick={() => setVolume(volume <= 0 ? volume : volume - 10)}
|
||||
aria-label={aiStrings.volume_down}
|
||||
>
|
||||
<VolumeDownIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VolumeControl.propTypes = {
|
||||
volume: PropTypes.number.isRequired,
|
||||
setVolume: PropTypes.func.isRequired,
|
||||
};
|
@ -0,0 +1,82 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import WaveSurfer from 'wavesurfer.js';
|
||||
|
||||
const propTypes = {
|
||||
position: PropTypes.number.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
audio: PropTypes.string,
|
||||
setPosition: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const WaveformProgressBar = ({ audio, position, duration, setPosition }) => {
|
||||
const waveFormDomRef = useRef(null);
|
||||
const wavesurfer = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (waveFormDomRef.current && audio) {
|
||||
wavesurfer.current = WaveSurfer.create({
|
||||
container: waveFormDomRef.current,
|
||||
mediaControls: false,
|
||||
height: 40,
|
||||
barWidth: 2,
|
||||
barGap: 2,
|
||||
barRadius: 3,
|
||||
responsive: true,
|
||||
cursorWidth: 0,
|
||||
backgroundColor: 'transparent',
|
||||
progressColor: '#f70f5d',
|
||||
waveColor: '#fff',
|
||||
xhr: {
|
||||
mode: 'no-cors',
|
||||
},
|
||||
});
|
||||
wavesurfer.current.load(audio);
|
||||
wavesurfer.current.on('ready', () => {
|
||||
console.log('wavesurfer loaded');
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (wavesurfer.current) {
|
||||
wavesurfer.current.destroy();
|
||||
}
|
||||
};
|
||||
}, [audio, waveFormDomRef.current]);
|
||||
|
||||
useEffect(() => {
|
||||
// Sync wavesurfer with current playing position
|
||||
const progress = position / duration;
|
||||
|
||||
if (wavesurfer.current && !Number.isNaN(progress)) {
|
||||
wavesurfer.current.seekTo(progress || 0);
|
||||
}
|
||||
}, [position]);
|
||||
|
||||
const handleClick = event => {
|
||||
if (setPosition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetX =
|
||||
event.pageX - event.currentTarget.getBoundingClientRect().left;
|
||||
const posX = offsetX / event.currentTarget.offsetWidth;
|
||||
|
||||
setPosition(posX * duration);
|
||||
};
|
||||
|
||||
if (!audio) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ai-waveform-bar" onClick={handleClick}>
|
||||
<div className="ai-waveform" ref={waveFormDomRef} />
|
||||
<div className="ai-waveform-progress" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
WaveformProgressBar.propTypes = propTypes;
|
||||
|
||||
export default WaveformProgressBar;
|
@ -0,0 +1,418 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sound from 'react-sound';
|
||||
|
||||
import SoundCloud from '../utils/soundcloud';
|
||||
import multiSoundDisabled from '../utils/multi-sound-disabled';
|
||||
import { getInitialTrackQueueAndIndex } from '../utils/getInitialTrackIndex';
|
||||
|
||||
const PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2, 3];
|
||||
|
||||
const soundProvider = (Player, events) => {
|
||||
class EnhancedPlayer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const {
|
||||
volume,
|
||||
cycleTracks,
|
||||
defaultShuffle,
|
||||
shuffleEnabled,
|
||||
} = this.props;
|
||||
|
||||
this.state = {
|
||||
tracks: [],
|
||||
activeIndex: 0, // Determine active track by index
|
||||
|
||||
// trackQueue: List of track indexes that represents the order of the playlist
|
||||
// i.e. [0, 1, 2, 3, 4] will play the 1st, 2nd, 3rd, etc track.
|
||||
// [5, 4, 3, 2, 1] will play the tracks reversed.
|
||||
// [4, 2, 0, ...] will play the 5th track first, 3rd second, then the 1st, etc.
|
||||
trackQueue: [],
|
||||
playStatus: Sound.status.STOPPED,
|
||||
position: 0,
|
||||
duration: 0,
|
||||
playbackRate: 1,
|
||||
volume: volume == null ? 100 : volume,
|
||||
cycleTracks,
|
||||
repeatingTrackIndex: null,
|
||||
isMultiSoundDisabled: multiSoundDisabled(),
|
||||
buffering: false,
|
||||
shuffle: shuffleEnabled && defaultShuffle,
|
||||
};
|
||||
|
||||
this.playTrack = this.playTrack.bind(this);
|
||||
this.pauseTrack = this.pauseTrack.bind(this);
|
||||
this.togglePlay = this.togglePlay.bind(this);
|
||||
this.nextTrack = this.nextTrack.bind(this);
|
||||
this.prevTrack = this.prevTrack.bind(this);
|
||||
this.setPosition = this.setPosition.bind(this);
|
||||
this.setVolume = this.setVolume.bind(this);
|
||||
this.skipPosition = this.skipPosition.bind(this);
|
||||
this.setPlaybackRate = this.setPlaybackRate.bind(this);
|
||||
this.toggleTracklistCycling = this.toggleTracklistCycling.bind(this);
|
||||
this.toggleShuffle = this.toggleShuffle.bind(this);
|
||||
this.setTrackCycling = this.setTrackCycling.bind(this);
|
||||
this.reverseTracks = this.reverseTracks.bind(this);
|
||||
this.getFinalProps = this.getFinalProps.bind(this);
|
||||
this.onPlaying = this.onPlaying.bind(this);
|
||||
this.onFinishedPlaying = this.onFinishedPlaying.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
tracksUrl,
|
||||
soundcloudClientId,
|
||||
reverseTrackOrder,
|
||||
initialTrack,
|
||||
} = this.props;
|
||||
const { shuffle } = this.state;
|
||||
const tracksPromised = fetch(tracksUrl).then(res => res.json());
|
||||
|
||||
if (!soundcloudClientId) {
|
||||
tracksPromised.then(tracks => {
|
||||
const { trackQueue, activeIndex } = getInitialTrackQueueAndIndex({
|
||||
tracks,
|
||||
initialTrack,
|
||||
reverseTrackOrder,
|
||||
shuffle,
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
tracks,
|
||||
activeIndex,
|
||||
trackQueue,
|
||||
},
|
||||
() => {
|
||||
if (reverseTrackOrder) {
|
||||
this.reverseTracks();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const sc = new SoundCloud(soundcloudClientId);
|
||||
const scTracks = tracksPromised
|
||||
.then(tracks => sc.fetchSoundCloudStreams(tracks))
|
||||
.catch(err => console.error(err)); // eslint-disable-line no-console
|
||||
|
||||
// Make sure if SoundCloud fetching fails
|
||||
// we delegate and load our tracks anyway
|
||||
const promiseArray = [tracksPromised, scTracks].map(p =>
|
||||
p.catch(error => ({
|
||||
status: 'error',
|
||||
error,
|
||||
})),
|
||||
);
|
||||
|
||||
Promise.all(promiseArray).then(res => {
|
||||
if (res[1].status === 'error') {
|
||||
return this.setState({ tracks: res[0] });
|
||||
}
|
||||
|
||||
const tracks = sc.mapStreamsToTracks(...res);
|
||||
const { trackQueue, activeIndex } = getInitialTrackQueueAndIndex({
|
||||
tracks,
|
||||
initialTrack,
|
||||
reverseTrackOrder,
|
||||
shuffle,
|
||||
});
|
||||
|
||||
return this.setState(
|
||||
() => ({
|
||||
tracks,
|
||||
activeIndex,
|
||||
trackQueue,
|
||||
}),
|
||||
() => {
|
||||
if (reverseTrackOrder) {
|
||||
this.reverseTracks();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Events
|
||||
onPlaying({ duration, position }) {
|
||||
this.setState(
|
||||
() => ({ duration, position }),
|
||||
() => {
|
||||
if (events && events.onPlaying) {
|
||||
events.onPlaying(this.getFinalProps());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onFinishedPlaying() {
|
||||
const { stopOnTrackFinish, delayBetweenTracks = 0 } = this.props;
|
||||
const delayBetweenTracksMs = delayBetweenTracks * 1000;
|
||||
this.setState(() => ({ playStatus: Sound.status.STOPPED }));
|
||||
|
||||
if (stopOnTrackFinish) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (events && events.onFinishedPlaying) {
|
||||
setTimeout(() => {
|
||||
events.onFinishedPlaying(this.getFinalProps());
|
||||
}, delayBetweenTracksMs);
|
||||
}
|
||||
}
|
||||
|
||||
getFinalProps() {
|
||||
const { tracks, activeIndex } = this.state;
|
||||
const currentTrack = tracks[activeIndex] || {};
|
||||
|
||||
return {
|
||||
playTrack: this.playTrack,
|
||||
pauseTrack: this.pauseTrack,
|
||||
togglePlay: this.togglePlay,
|
||||
nextTrack: this.nextTrack,
|
||||
prevTrack: this.prevTrack,
|
||||
setPosition: this.setPosition,
|
||||
skipPosition: this.skipPosition,
|
||||
setPlaybackRate: this.setPlaybackRate,
|
||||
setVolume: this.setVolume,
|
||||
toggleTracklistCycling: this.toggleTracklistCycling,
|
||||
setTrackCycling: this.setTrackCycling,
|
||||
toggleShuffle: this.toggleShuffle,
|
||||
currentTrack,
|
||||
...this.props,
|
||||
...this.state,
|
||||
};
|
||||
}
|
||||
|
||||
setVolume(volume) {
|
||||
this.setState(() => ({ volume }));
|
||||
}
|
||||
|
||||
setPosition(position) {
|
||||
this.setState(() => ({ position }));
|
||||
}
|
||||
|
||||
setTrackCycling(index, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const { activeIndex, cycleTracks } = this.state;
|
||||
|
||||
if (cycleTracks && index != null) {
|
||||
this.toggleTracklistCycling();
|
||||
}
|
||||
|
||||
this.setState(
|
||||
({ repeatingTrackIndex }) => ({
|
||||
repeatingTrackIndex: repeatingTrackIndex === index ? null : index,
|
||||
}),
|
||||
() => {
|
||||
if (index != null && activeIndex !== index) {
|
||||
this.playTrack(index);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setPlaybackRate() {
|
||||
this.setState(({ playbackRate }) => {
|
||||
const currentIndex = PLAYBACK_RATES.findIndex(
|
||||
rate => rate === playbackRate,
|
||||
);
|
||||
const nextIndex =
|
||||
(PLAYBACK_RATES.length + (currentIndex + 1)) % PLAYBACK_RATES.length;
|
||||
|
||||
return {
|
||||
playbackRate: PLAYBACK_RATES[nextIndex],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
toggleShuffle() {
|
||||
const { initialTrack, reverseTrackOrder } = this.props;
|
||||
const { tracks } = this.state;
|
||||
|
||||
this.setState(
|
||||
prev => ({
|
||||
shuffle: !prev.shuffle,
|
||||
}),
|
||||
() => {
|
||||
this.setState(() => {
|
||||
const { trackQueue } = getInitialTrackQueueAndIndex({
|
||||
tracks,
|
||||
initialTrack,
|
||||
reverseTrackOrder,
|
||||
shuffle: this.state.shuffle,
|
||||
});
|
||||
|
||||
return {
|
||||
trackQueue,
|
||||
};
|
||||
});
|
||||
|
||||
if (this.state.shuffle) {
|
||||
// Shuffle track queue
|
||||
} else {
|
||||
// Unshuffle track queue
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
skipPosition(direction = 1) {
|
||||
const { position } = this.state;
|
||||
const { skipAmount } = this.props;
|
||||
const amount = parseInt(skipAmount, 10) * 1000;
|
||||
|
||||
this.setPosition(position + amount * direction);
|
||||
}
|
||||
|
||||
playTrack(index, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const { repeatingTrackIndex, isMultiSoundDisabled } = this.state;
|
||||
|
||||
if (isMultiSoundDisabled) {
|
||||
window.soundManager.pauseAll();
|
||||
}
|
||||
|
||||
this.setState(() => ({
|
||||
activeIndex: index,
|
||||
position: 0,
|
||||
playStatus: Sound.status.PLAYING,
|
||||
}));
|
||||
|
||||
// Reset repating track index if the track is not the active one.
|
||||
if (index !== repeatingTrackIndex && repeatingTrackIndex != null) {
|
||||
this.setTrackCycling(null);
|
||||
}
|
||||
}
|
||||
|
||||
pauseTrack(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const { playStatus } = this.state;
|
||||
|
||||
if (playStatus === Sound.status.PLAYING) {
|
||||
this.setState(() => ({ playStatus: Sound.status.PAUSED }));
|
||||
}
|
||||
}
|
||||
|
||||
togglePlay(index, event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const { activeIndex } = this.state;
|
||||
|
||||
if (typeof index === 'number' && index !== activeIndex) {
|
||||
this.playTrack(index);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(({ playStatus, isMultiSoundDisabled }) => {
|
||||
if (playStatus !== Sound.status.PLAYING && isMultiSoundDisabled) {
|
||||
window.soundManager.pauseAll();
|
||||
}
|
||||
|
||||
return {
|
||||
playStatus:
|
||||
playStatus === Sound.status.PLAYING
|
||||
? Sound.status.PAUSED
|
||||
: Sound.status.PLAYING,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
nextTrack() {
|
||||
const { trackQueue, activeIndex } = this.state;
|
||||
const currentQueueIndex = trackQueue.indexOf(activeIndex);
|
||||
const nextQueueIndex = (currentQueueIndex + 1) % trackQueue.length;
|
||||
const nextTrackIndex = trackQueue[nextQueueIndex];
|
||||
|
||||
this.playTrack(nextTrackIndex);
|
||||
}
|
||||
|
||||
prevTrack() {
|
||||
const { trackQueue, activeIndex } = this.state;
|
||||
const currentQueueIndex = trackQueue.indexOf(activeIndex);
|
||||
const prevQueueIndex =
|
||||
(currentQueueIndex + trackQueue.length - 1) % trackQueue.length;
|
||||
const prevTrackIndex = trackQueue[prevQueueIndex];
|
||||
|
||||
this.playTrack(prevTrackIndex);
|
||||
}
|
||||
|
||||
toggleTracklistCycling() {
|
||||
const { repeatingTrackIndex } = this.state;
|
||||
|
||||
if (repeatingTrackIndex !== null) {
|
||||
this.setTrackCycling(null);
|
||||
}
|
||||
|
||||
this.setState(state => ({
|
||||
cycleTracks: !state.cycleTracks,
|
||||
}));
|
||||
}
|
||||
|
||||
reverseTracks() {
|
||||
this.setState(state => ({
|
||||
tracks: state.tracks.slice().reverse(),
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { tracks, playStatus, position, volume, playbackRate } = this.state;
|
||||
const finalProps = this.getFinalProps();
|
||||
|
||||
return (
|
||||
<div className="ai-audioigniter">
|
||||
<Player {...finalProps} />
|
||||
|
||||
{tracks.length > 0 && (
|
||||
<Sound
|
||||
url={finalProps.currentTrack.audio}
|
||||
playStatus={playStatus}
|
||||
position={position}
|
||||
volume={volume}
|
||||
onPlaying={this.onPlaying}
|
||||
onFinishedPlaying={this.onFinishedPlaying}
|
||||
onPause={() => this.pauseTrack()}
|
||||
playbackRate={playbackRate}
|
||||
onBufferChange={buffering => {
|
||||
this.setState({ buffering });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EnhancedPlayer.propTypes = {
|
||||
volume: PropTypes.number,
|
||||
cycleTracks: PropTypes.bool,
|
||||
tracksUrl: PropTypes.string,
|
||||
soundcloudClientId: PropTypes.string,
|
||||
reverseTrackOrder: PropTypes.bool,
|
||||
skipAmount: PropTypes.number,
|
||||
stopOnTrackFinish: PropTypes.bool,
|
||||
delayBetweenTracks: PropTypes.number,
|
||||
initialTrack: PropTypes.number,
|
||||
shuffleEnabled: PropTypes.bool,
|
||||
defaultShuffle: PropTypes.bool,
|
||||
};
|
||||
|
||||
return EnhancedPlayer;
|
||||
};
|
||||
|
||||
export default soundProvider;
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Shifts an array to right / left by n positions.
|
||||
*
|
||||
* @param {Array} arr The array.
|
||||
* @param {number} direction The direction - 0 for left 1 for right.
|
||||
* @param {number} n Number of positions to shift by.
|
||||
* @returns {any[]}
|
||||
*/
|
||||
const arrayShift = (arr, direction, n) => {
|
||||
const times = n > arr.length ? n % arr.length : n;
|
||||
return arr.concat(arr.splice(0, direction > 0 ? arr.length - times : times));
|
||||
};
|
||||
|
||||
export default arrayShift;
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Shuffles an array.
|
||||
* Copied from https://github.com/sindresorhus/array-shuffle
|
||||
*
|
||||
* @param {Array} array The array to be shuffled.
|
||||
* @returns {*[]|*}
|
||||
*/
|
||||
const arrayShuffle = array => {
|
||||
if (!Array.isArray(array)) {
|
||||
return array;
|
||||
}
|
||||
|
||||
const clone = [...array];
|
||||
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let index = clone.length - 1; index > 0; index--) {
|
||||
const newIndex = Math.floor(Math.random() * (index + 1));
|
||||
[clone[index], clone[newIndex]] = [clone[newIndex], clone[index]];
|
||||
}
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
export default arrayShuffle;
|
@ -0,0 +1,76 @@
|
||||
import arrayShuffle from './array-shuffle';
|
||||
import arrayShift from './array-shift';
|
||||
|
||||
/**
|
||||
* Fetches the initial track index.
|
||||
*
|
||||
* @param {Object} options The options.
|
||||
* @param {Array} options.tracks The tracks.
|
||||
* @param {number} [options.initialTrack] The initial track index.
|
||||
* @param {boolean} options.reverseTrackOrder Whether the track order is reversed.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const getInitialTrackIndex = ({
|
||||
tracks = [],
|
||||
initialTrack = 1,
|
||||
reverseTrackOrder = false,
|
||||
}) => {
|
||||
// The user provides a 1-index value.
|
||||
const initialTrackIndex = initialTrack - 1;
|
||||
|
||||
if (!tracks.length || !initialTrack || initialTrack > tracks.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (reverseTrackOrder) {
|
||||
return Math.max(tracks.length - initialTrack, 0);
|
||||
}
|
||||
|
||||
return initialTrackIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the initial track index and the initial track queue.
|
||||
*
|
||||
* @param {Object} options The options.
|
||||
* @param {Array} options.tracks The tracks.
|
||||
* @param {Number} options.initialTrack The initial track number (1-indexed).
|
||||
* @param {Boolean} reverseTrackOrder Whether the track order is reversed.
|
||||
* @param {Boolean} shuffle Whether the track queue is shuffled.
|
||||
* @returns {{activeIndex: number, trackQueue: (*[]|*)}|{activeIndex: number, trackQueue: *}}
|
||||
*/
|
||||
export const getInitialTrackQueueAndIndex = ({
|
||||
tracks = [],
|
||||
initialTrack = 1,
|
||||
reverseTrackOrder = false,
|
||||
shuffle = false,
|
||||
}) => {
|
||||
const activeIndex = getInitialTrackIndex({
|
||||
tracks,
|
||||
initialTrack,
|
||||
reverseTrackOrder,
|
||||
});
|
||||
|
||||
const orderedTrackIndexes = tracks.map((_, index) => index);
|
||||
|
||||
if (!shuffle) {
|
||||
const shiftAmount = orderedTrackIndexes.indexOf(activeIndex);
|
||||
return {
|
||||
activeIndex,
|
||||
trackQueue: arrayShift(orderedTrackIndexes, 0, shiftAmount),
|
||||
};
|
||||
}
|
||||
|
||||
const shuffledQueue = arrayShuffle(orderedTrackIndexes);
|
||||
|
||||
// Always bring the initial track (activeIndex) to the front of the queue.
|
||||
shuffledQueue.splice(shuffledQueue.indexOf(activeIndex), 1);
|
||||
shuffledQueue.unshift(activeIndex);
|
||||
|
||||
return {
|
||||
activeIndex,
|
||||
trackQueue: shuffledQueue,
|
||||
};
|
||||
};
|
||||
|
||||
export default getInitialTrackIndex;
|
@ -0,0 +1,8 @@
|
||||
const multiSoundDisabled = () => {
|
||||
return (
|
||||
window.ai_pro_front_scripts &&
|
||||
!!window.ai_pro_front_scripts.multi_sound_disabled
|
||||
);
|
||||
};
|
||||
|
||||
export default multiSoundDisabled;
|
@ -0,0 +1,88 @@
|
||||
export default class SoundCloud {
|
||||
constructor(clientId) {
|
||||
if (!clientId) {
|
||||
throw new Error('SoundCloud client ID is required');
|
||||
}
|
||||
|
||||
this.clientId = clientId;
|
||||
this.baseUrl = 'https://api.soundcloud.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a URL is from SoundCloud
|
||||
*
|
||||
* @param {string} url - URL to be checked
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isSoundCloudUrl(url) {
|
||||
return url.indexOf('soundcloud.com') > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a SoundCloud URL into a track object
|
||||
*
|
||||
* @param {string} url - URL to be resolved
|
||||
*
|
||||
* @returns {Promise.<*>}
|
||||
*/
|
||||
resolve(url) {
|
||||
/*
|
||||
* Tell the SoundCloud API not to serve a redirect. This is to get around
|
||||
* CORS issues on Safari 7+, which likes to send pre-flight requests
|
||||
* before following redirects, which has problems.
|
||||
*
|
||||
* https://github.com/soundcloud/soundcloud-javascript/issues/27
|
||||
*/
|
||||
const statusCodeMap = encodeURIComponent('_status_code_map[302]=200');
|
||||
|
||||
return fetch(
|
||||
`${this.baseUrl}/resolve?url=${url}&client_id=${
|
||||
this.clientId
|
||||
}&${statusCodeMap}`,
|
||||
)
|
||||
.then(res => res.json())
|
||||
.then(res => fetch(res.location))
|
||||
.then(res => res.json());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves and fetches SoundCloud track objects
|
||||
*
|
||||
* @param {Object[]} tracks - Tracks object
|
||||
*
|
||||
* @returns {Promise.<*>}
|
||||
*/
|
||||
fetchSoundCloudStreams(tracks) {
|
||||
const scTracks = tracks
|
||||
.filter(track => SoundCloud.isSoundCloudUrl(track.audio))
|
||||
.map(track => this.resolve(track.audio));
|
||||
|
||||
return Promise.all(scTracks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a SoundCloud tracks object into an AudioIgniter one
|
||||
* by replacing `track.audio` with `sctrack.stream_url`.
|
||||
*
|
||||
* Works *in order* of appearance in the `tracks` object.
|
||||
*
|
||||
* @param {Object[]} tracks - AudioIgniter tracks object
|
||||
* @param {Object[]} scTracks - SoundCloud tracks object
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
mapStreamsToTracks(tracks, scTracks) {
|
||||
let i = 0;
|
||||
|
||||
return tracks.map(track => {
|
||||
if (SoundCloud.isSoundCloudUrl(track.audio)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
track.audio = `${scTracks[i].stream_url}?client_id=${this.clientId}`;
|
||||
i++; // eslint-disable-line no-plusplus
|
||||
}
|
||||
|
||||
return track;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
const typographyDisabled = () => {
|
||||
return (
|
||||
window.ai_pro_front_scripts &&
|
||||
!!window.ai_pro_front_scripts.typography_disabled
|
||||
);
|
||||
};
|
||||
|
||||
export default typographyDisabled;
|
1023
wp-content/plugins/audioigniter/player/styles/style.scss
Normal file
1023
wp-content/plugins/audioigniter/player/styles/style.scss
Normal file
File diff suppressed because it is too large
Load Diff
80
wp-content/plugins/audioigniter/player/webpack.config.js
Normal file
80
wp-content/plugins/audioigniter/player/webpack.config.js
Normal file
@ -0,0 +1,80 @@
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const parts = require('./webpack.parts');
|
||||
|
||||
const TARGET = process.env.npm_lifecycle_event;
|
||||
process.env.BABEL_ENV = TARGET;
|
||||
|
||||
const PATHS = {
|
||||
app: path.join(__dirname, 'src'),
|
||||
build: path.join(__dirname, 'build'),
|
||||
style: path.join(__dirname, 'styles', 'style.scss'),
|
||||
};
|
||||
|
||||
const common = {
|
||||
entry: {
|
||||
style: PATHS.style,
|
||||
app: PATHS.app,
|
||||
},
|
||||
output: {
|
||||
path: PATHS.build,
|
||||
filename: '[name].js',
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'AudioIgniter',
|
||||
template: `${PATHS.app}/index.ejs`,
|
||||
}),
|
||||
],
|
||||
devServer: {
|
||||
contentBase: path.resolve('assets'),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
use: ['babel-loader?cacheDirectory'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
let config;
|
||||
|
||||
// Detect how npm is run and branch based on that
|
||||
switch (TARGET) {
|
||||
case 'build': {
|
||||
config = merge(
|
||||
common,
|
||||
{
|
||||
resolve: {
|
||||
modules: [path.resolve(__dirname), 'node_modules'],
|
||||
extensions: ['.js', '.jsx'],
|
||||
},
|
||||
},
|
||||
parts.minify(),
|
||||
parts.extractCSS(PATHS.style),
|
||||
parts.setFreeVariable('process.env.NODE_ENV', 'production'),
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
config = merge(
|
||||
common,
|
||||
{
|
||||
devtool: 'eval-source-map',
|
||||
},
|
||||
parts.setupSass(PATHS.style),
|
||||
parts.devServer({
|
||||
host: process.env.HOST,
|
||||
port: process.env.PORT,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = config;
|
107
wp-content/plugins/audioigniter/player/webpack.parts.js
Normal file
107
wp-content/plugins/audioigniter/player/webpack.parts.js
Normal file
@ -0,0 +1,107 @@
|
||||
const webpack = require('webpack');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
|
||||
exports.setupSass = paths => ({
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
include: paths,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
exports.extractCSS = paths => ({
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
use: [
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
minimize: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
plugins: () => [
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
'Chrome >= 46',
|
||||
'Firefox ESR',
|
||||
'Edge >= 12',
|
||||
'Explorer >= 9',
|
||||
'iOS >= 8',
|
||||
'Safari >= 8',
|
||||
'Android >= 4',
|
||||
],
|
||||
cascade: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
outputStyle: 'expanded',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
include: paths,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ExtractTextPlugin({
|
||||
filename: '[name].css',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
exports.devServer = options => ({
|
||||
devServer: {
|
||||
contentBase: __dirname,
|
||||
historyApiFallback: true,
|
||||
hot: false,
|
||||
inline: true,
|
||||
stats: 'errors-only',
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
overlay: {
|
||||
warnings: true,
|
||||
errors: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
exports.minify = () => ({
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
drop_console: true,
|
||||
screw_ie8: true,
|
||||
},
|
||||
output: {
|
||||
comments: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
exports.setFreeVariable = (key, value) => {
|
||||
const env = {};
|
||||
env[key] = JSON.stringify(value);
|
||||
|
||||
return {
|
||||
plugins: [new webpack.DefinePlugin(env)],
|
||||
};
|
||||
};
|
5831
wp-content/plugins/audioigniter/player/yarn.lock
Normal file
5831
wp-content/plugins/audioigniter/player/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user