updated plugin AudioIgniter
version 2.0.1
This commit is contained in:
parent
fdfbf76539
commit
d652fac5a4
@ -1 +0,0 @@
|
||||
player/build
|
@ -1,106 +0,0 @@
|
||||
//
|
||||
// Mixins
|
||||
//
|
||||
@mixin clearfix() {
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ai-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin spinner($color: #fff, $opacity: .35, $size: 40px) {
|
||||
border: 6px solid rgba($color, $opacity);
|
||||
border-top-color: rgba($color, $opacity*2.5);
|
||||
border-radius: 100%;
|
||||
height: $size;
|
||||
width: $size;
|
||||
animation: ai-spin .8s infinite linear;
|
||||
}
|
||||
|
||||
@mixin btn-reset {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
border: 0;
|
||||
appearance: none;
|
||||
text-align: center;
|
||||
box-shadow: none;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
border-radius: 0;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
min-height: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes backgroundPosition {
|
||||
0% {
|
||||
background-position: -140px 0
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 140px 0
|
||||
}
|
||||
}
|
||||
|
||||
@mixin animatedBackground($width: 140px, $height: 8px, $top: 0, $left: 0) {
|
||||
content: '';
|
||||
width: $width;
|
||||
height: $height;
|
||||
background: linear-gradient(to right, $control-color 8%, lighten($control-color, 6%) 18%, $control-color 33%);
|
||||
background-size: 500px;
|
||||
position: absolute;
|
||||
top: $top;
|
||||
left: $left;
|
||||
opacity: 1;
|
||||
animation-duration: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: backgroundPosition;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@mixin sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@mixin dashicon($icon) {
|
||||
content: $icon;
|
||||
display: inline-block;
|
||||
font: 400 20px/1 dashicons;
|
||||
speak: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-decoration: none !important;
|
||||
}
|
@ -1,680 +0,0 @@
|
||||
@import 'mixins';
|
||||
|
||||
$brand-color: #1c4866 !default;
|
||||
$brand-secondary-color: #ffcc00 !default;
|
||||
$lighter-grey: #f1f1f1 !default;
|
||||
$light-grey: #eeeeee !default;
|
||||
$medium-grey: #d7d7d7 !default;
|
||||
$dark-grey: #cccccc !default;
|
||||
$white: #ffffff !default;
|
||||
$blue: #0073aa !default;
|
||||
$red: #ff0000 !default;
|
||||
$green: #14b552 !default;
|
||||
$info-box-bg-color: #fffce6 !default;
|
||||
$info-box-border-color: #eeeac9 !default;
|
||||
$info-box-text-color: #948832 !default;
|
||||
$base-pad: 15px !default;
|
||||
$transition-time: .18s !default;
|
||||
$border-color: $lighter-grey !default;
|
||||
|
||||
.sr-only {
|
||||
@include sr-only;
|
||||
}
|
||||
|
||||
.ai-row {
|
||||
@include clearfix;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
[class^="ai-col"] {
|
||||
float: left;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ai-btn {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
border-radius: 2px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
background-image: none;
|
||||
padding: 11px 20px 11px;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
background-color: $brand-color;
|
||||
color: $white;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $white;
|
||||
background-color: darken($brand-color, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.ai-btn-green {
|
||||
background-color: $green;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $white;
|
||||
background-color: darken($green, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Brand Modules
|
||||
//
|
||||
.ai-brand-module {
|
||||
background-color: $brand-color;
|
||||
padding: $base-pad;
|
||||
color: $white;
|
||||
font-size: 12px;
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
a:not(.ai-btn) {
|
||||
color: $brand-secondary-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-brand-module-actions {
|
||||
text-align: right;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Header
|
||||
//
|
||||
.ai-header {
|
||||
margin: 12px 0 -12px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.ai-header-actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ai-logo {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
|
||||
img {
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Footer
|
||||
//
|
||||
.ai-note {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.ai-list-inline {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-footer-links {
|
||||
a {
|
||||
&::after {
|
||||
content: "\007c";
|
||||
color: $white;
|
||||
opacity: .5;
|
||||
margin: 0 7px;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
&:last-child {
|
||||
a::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// General
|
||||
//
|
||||
.ai-module {
|
||||
@include clearfix;
|
||||
border: 1px solid $light-grey;
|
||||
margin-top: 12px;
|
||||
padding: $base-pad;
|
||||
}
|
||||
|
||||
.ai-container {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
//
|
||||
// Field Controls
|
||||
//
|
||||
.ai-field-controls-wrap {
|
||||
@include clearfix;
|
||||
padding: $base-pad;
|
||||
border: 1px solid $light-grey;
|
||||
}
|
||||
|
||||
.ai-field-controls {
|
||||
float: left;
|
||||
|
||||
.button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-field-controls-visibility {
|
||||
float: right;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.ai-field-controls-visibility {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-fields-expand-all {
|
||||
margin-right: 8px;
|
||||
padding-right: 6px;
|
||||
border-right: 1px solid $lighter-grey;
|
||||
}
|
||||
|
||||
//
|
||||
// Fields general structure
|
||||
//
|
||||
.ai-fields-container {
|
||||
padding: $base-pad;
|
||||
border-left: 1px solid $light-grey;
|
||||
border-right: 1px solid $light-grey;
|
||||
}
|
||||
|
||||
.ai-field-repeatable {
|
||||
margin-bottom: $base-pad;
|
||||
border: 1px solid $medium-grey;
|
||||
box-shadow: 1px 1px 2px rgba(black, .07);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:only-child {
|
||||
.ai-remove-field {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-field-container {
|
||||
@include clearfix;
|
||||
padding: $base-pad;
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
.ai-field-container-links {
|
||||
display: flex;
|
||||
grid-gap: 20px;
|
||||
|
||||
.ai-field-split {
|
||||
width: 50%;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-field-head {
|
||||
@include clearfix;
|
||||
padding: 8px $base-pad 5px;
|
||||
line-height: normal;
|
||||
background-color: $medium-grey;
|
||||
background: linear-gradient(to bottom, $lighter-grey, $medium-grey);
|
||||
border-bottom: 1px solid $dark-grey;
|
||||
|
||||
.toggle-indicator {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.ai-fields-sortable & {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-field-sort-handle {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
color: $blue;
|
||||
|
||||
.dashicons {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-field-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.05em;
|
||||
margin-left: 8px;
|
||||
padding-top: 3px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 80%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ai-field-toggle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ai-field-cover {
|
||||
float: left;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-right: $base-pad;
|
||||
background-color: $light-grey;
|
||||
border: 1px solid $dark-grey;
|
||||
}
|
||||
|
||||
.ai-field-split {
|
||||
float: left;
|
||||
width: calc(50% - 71px);
|
||||
margin-right: $base-pad;
|
||||
|
||||
&:nth-child(2n + 1) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Form elements
|
||||
//
|
||||
.ai-container,
|
||||
.ai-module {
|
||||
.button {
|
||||
.dashicons {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-form-field-group {
|
||||
padding: $base-pad;
|
||||
border: 1px solid $border-color;
|
||||
margin-bottom: $base-pad;
|
||||
|
||||
:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-form-field {
|
||||
margin-bottom: $base-pad;
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="url"],
|
||||
input[type="search"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="number"],
|
||||
input[type="tel"],
|
||||
input[type="date"],
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-module-settings {
|
||||
.ai-form-field {
|
||||
input[type="text"],
|
||||
input[type="url"],
|
||||
input[type="search"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="number"],
|
||||
input[type="tel"],
|
||||
input[type="date"],
|
||||
textarea,
|
||||
select {
|
||||
width: 200px;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-form-field-addon {
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
padding-right: 80px;
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-field-help {
|
||||
margin: 5px 0 0;
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.ai-form-field-checkbox-secondary {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.ai-remove-field {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ai-field-upload-cover {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-decoration: none;
|
||||
color: initial;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-has-cover {
|
||||
.ai-remove-cover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ai-field-cover-placeholder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-field-cover-placeholder {
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
font-size: .9em;
|
||||
opacity: .8;
|
||||
padding-top: 28px;
|
||||
|
||||
&::before {
|
||||
@include dashicon($icon: "\f128");
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ai-track-loading & {
|
||||
&::before {
|
||||
content: "\f463";
|
||||
animation: rotation 1.2s infinite linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-remove-cover {
|
||||
color: $white;
|
||||
background-color: $red;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
opacity: .9;
|
||||
transition: opacity $transition-time ease-in;
|
||||
display: none;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dashicons {
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-remove-all-fields,
|
||||
.ai-remove-field {
|
||||
.dashicons {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-add-field-batch,
|
||||
.ai-add-field {
|
||||
.dashicons {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-info-box {
|
||||
background: $info-box-bg-color;
|
||||
color: $info-box-text-color;
|
||||
font-size: 12px;
|
||||
border: solid 1px $info-box-border-color;
|
||||
padding: 15px;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.ai-player-type-message {
|
||||
display: none;
|
||||
}
|
||||
|
||||
//
|
||||
// Sortable specific
|
||||
//
|
||||
.ai-drop-placeholder {
|
||||
background-color: $lighter-grey;
|
||||
border: 2px dashed $dark-grey;
|
||||
opacity: 0.5;
|
||||
margin-bottom: $base-pad;
|
||||
}
|
||||
|
||||
//
|
||||
// Collapsible
|
||||
//
|
||||
.ai-collapsed {
|
||||
.ai-field-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-indicator {
|
||||
&::before {
|
||||
content: "\f140" !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Shortcode field
|
||||
//
|
||||
.ai-module-shortcode {
|
||||
.code {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 3px;
|
||||
padding: 10px 10px 8px;
|
||||
font-weight: bold;
|
||||
background: $lighter-grey;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Soundcloud module
|
||||
//
|
||||
.ai-sync-soundcloud.button { // Overcoming specificity
|
||||
display: none;
|
||||
|
||||
&::before {
|
||||
content: "\f463";
|
||||
color: #d54e21;
|
||||
display: inline-block;
|
||||
font: 400 19px/1 dashicons;
|
||||
speak: none;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: 4px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.ai-track-loading & {
|
||||
&::before {
|
||||
animation: rotation 1.2s infinite linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-soundcloud-track {
|
||||
.ai-sync-soundcloud {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ai-upload {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Media queries
|
||||
//
|
||||
@media (max-width: 1100px) {
|
||||
.ai-field-controls,
|
||||
.ai-field-controls-visibility {
|
||||
margin: 0;
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ai-field-controls {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.ai-field-container-links {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ai-field-container-links .ai-field-split,
|
||||
.ai-field-split {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ai-field-cover {
|
||||
margin-bottom: $base-pad;
|
||||
}
|
||||
|
||||
.ai-footer {
|
||||
text-align: center;
|
||||
|
||||
.ai-brand-module-actions {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
[class^="ai-col"] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 782px) {
|
||||
.ai-container,
|
||||
.ai-module {
|
||||
.button {
|
||||
.dashicons {
|
||||
line-height: 1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-form-field-addon {
|
||||
.button {
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.ai-field-controls {
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-header {
|
||||
text-align: center;
|
||||
|
||||
.ai-brand-module-actions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ai-btn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[class^="ai-col"] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
$box-shadow-base: 0px 2px 0px rgba(0, 0, 0, 0.04);
|
||||
$border-color-base: #D8D8D8;
|
||||
$text-color-base: #646970;
|
||||
$text-color-dark: #1D2327;
|
||||
$background-color: #f2f2f2;
|
||||
|
||||
/* General */
|
||||
.ai-settings-box {
|
||||
background-color: #ffffff;
|
||||
padding: 25px;
|
||||
border: 1px solid $border-color-base;
|
||||
box-shadow: $box-shadow-base;
|
||||
|
||||
> :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Header / Nav */
|
||||
.ai-settings-main-content-nav-header {
|
||||
margin: 15px 0 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ai-settings-main-content-nav {
|
||||
display: flex;
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
||||
.ai-settings-main-content-nav-link {
|
||||
color: $text-color-base;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
|
||||
&.is-active {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&.is-active,
|
||||
&:hover {
|
||||
color: $text-color-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-settings-main-content-nav-filters {
|
||||
margin-left: auto;
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
* Description: AudioIgniter lets you create music playlists and embed them in your WordPress posts, pages or custom post types and serve your audio content in style!
|
||||
* Author: The CSSIgniter Team
|
||||
* Author URI: https://www.cssigniter.com
|
||||
* Version: 2.0.0
|
||||
* Version: 2.0.1
|
||||
* Text Domain: audioigniter
|
||||
* Domain Path: languages
|
||||
*
|
||||
@ -121,7 +121,7 @@ class AudioIgniter {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
$plugin_data = get_plugin_data( __FILE__ );
|
||||
$plugin_data = get_plugin_data( __FILE__, true, false );
|
||||
|
||||
$this->version = $plugin_data['Version'];
|
||||
}
|
||||
@ -129,7 +129,9 @@ class AudioIgniter {
|
||||
self::$plugin_url = plugin_dir_url( __FILE__ );
|
||||
self::$plugin_path = plugin_dir_path( __FILE__ );
|
||||
|
||||
load_plugin_textdomain( 'audioigniter', false, dirname( self::plugin_basename() ) . '/languages' );
|
||||
add_action( 'init', function() {
|
||||
load_plugin_textdomain( 'audioigniter', false, dirname( self::plugin_basename() ) . '/languages' );
|
||||
} );
|
||||
|
||||
require_once untrailingslashit( $this->plugin_path() ) . '/inc/class-audioigniter-sanitizer.php';
|
||||
$this->sanitizer = new AudioIgniter_Sanitizer();
|
||||
@ -1218,7 +1220,7 @@ class AudioIgniter {
|
||||
|
||||
$post = get_post( $id );
|
||||
|
||||
$params = apply_filters( 'audioigniter_shortcode_data_attributes_array', $this->get_playlist_data_attributes_array( $id ), $id, $post );
|
||||
$params = apply_filters( 'audioigniter_shortcode_data_attributes_array', $this->get_playlist_data_attributes_array( $id ), $id, $post, $atts );
|
||||
$params = array_filter( $params, array( $this->sanitizer, 'array_filter_empty_null' ) );
|
||||
$params = $this->sanitizer->html_data_attributes_array( $params );
|
||||
|
||||
@ -1306,6 +1308,9 @@ class AudioIgniter {
|
||||
$track_response['cover'] = $cover_url;
|
||||
|
||||
$track_response = apply_filters( 'audioigniter_playlist_endpoint_track', $track_response, $track, $playlist_id, $post );
|
||||
if ( false === $track_response ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response[] = $track_response;
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
name: 'audioigniter',
|
||||
paths: {
|
||||
src: {
|
||||
styles: ['./assets/css/**/*.scss'],
|
||||
},
|
||||
},
|
||||
};
|
@ -2,7 +2,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: AudioIgniter\n"
|
||||
"POT-Creation-Date: 2023-04-18 22:08+0300\n"
|
||||
"POT-Creation-Date: 2024-11-19 13:49+0200\n"
|
||||
"PO-Revision-Date: 2016-08-29 19:22+0300\n"
|
||||
"Last-Translator: Anastis Sourgoutsidis <anastis@cssigniter.com>\n"
|
||||
"Language-Team: Anastis Sourgoutsidis <anastis@cssigniter.com>\n"
|
||||
@ -10,7 +10,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
"X-Generator: Poedit 3.5\n"
|
||||
"X-Poedit-Basepath: ..\n"
|
||||
"X-Poedit-WPHeader: audioigniter.php\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
@ -22,353 +22,353 @@ msgstr ""
|
||||
"X-Poedit-SearchPathExcluded-0: *.js\n"
|
||||
|
||||
#. translators: %s is the track's title.
|
||||
#: audioigniter.php:218
|
||||
#: audioigniter.php:220
|
||||
#, php-format
|
||||
msgid "Play %s"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s is the track's title.
|
||||
#: audioigniter.php:220
|
||||
#: audioigniter.php:222
|
||||
#, php-format
|
||||
msgid "Pause %s"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:221
|
||||
#: audioigniter.php:223
|
||||
msgid "Previous track"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:222
|
||||
#: audioigniter.php:224
|
||||
msgid "Next track"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:223
|
||||
#: audioigniter.php:225
|
||||
msgid "Toggle track listing repeat"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:224
|
||||
#: audioigniter.php:226
|
||||
msgid "Toggle track repeat"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:225
|
||||
#: audioigniter.php:227
|
||||
msgid "Toggle track listing visibility"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:226
|
||||
#: audioigniter.php:228
|
||||
msgid "Buy this track"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:227
|
||||
#: audioigniter.php:229
|
||||
msgid "Download this track"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:228
|
||||
#: audioigniter.php:230
|
||||
msgid "Volume Up"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:229
|
||||
#: audioigniter.php:231
|
||||
msgid "Volume Down"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:230
|
||||
#: audioigniter.php:232
|
||||
msgid "Open track lyrics"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:231
|
||||
#: audioigniter.php:233
|
||||
msgid "Set playback rate"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:232
|
||||
#: audioigniter.php:234
|
||||
msgid "Skip forward"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:233
|
||||
#: audioigniter.php:235
|
||||
msgid "Skip backward"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:234
|
||||
#: audioigniter.php:236
|
||||
msgid "Shuffle"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:247
|
||||
#: audioigniter.php:249
|
||||
msgid ""
|
||||
"Do you really want to remove all tracks? (This will not delete your audio "
|
||||
"files)."
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:248
|
||||
#: audioigniter.php:250
|
||||
msgid "Select or upload audio media"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:249
|
||||
#: audioigniter.php:251
|
||||
msgid "Select a cover image"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:292
|
||||
#: audioigniter.php:294
|
||||
msgctxt "post type general name"
|
||||
msgid "Playlists"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:293 audioigniter.php:309
|
||||
#: audioigniter.php:295 audioigniter.php:311
|
||||
msgctxt "post type singular name"
|
||||
msgid "Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:294
|
||||
#: audioigniter.php:296
|
||||
msgctxt "admin menu"
|
||||
msgid "AudioIgniter"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:295
|
||||
#: audioigniter.php:297
|
||||
msgctxt "admin menu"
|
||||
msgid "All Playlists"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:296
|
||||
#: audioigniter.php:298
|
||||
msgctxt "add new on admin bar"
|
||||
msgid "Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:297 audioigniter.php:298
|
||||
#: audioigniter.php:299 audioigniter.php:300
|
||||
msgid "Add New Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:299
|
||||
#: audioigniter.php:301
|
||||
msgid "Edit Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:300
|
||||
#: audioigniter.php:302
|
||||
msgid "New Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:301
|
||||
#: audioigniter.php:303
|
||||
msgid "View Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:302
|
||||
#: audioigniter.php:304
|
||||
msgid "Search Playlists"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:303
|
||||
#: audioigniter.php:305
|
||||
msgid "No playlists found"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:304
|
||||
#: audioigniter.php:306
|
||||
msgid "No playlists found in the trash"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:329 audioigniter.php:855
|
||||
#: audioigniter.php:331 audioigniter.php:857
|
||||
msgid "Tracks"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:330
|
||||
#: audioigniter.php:332
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:331 audioigniter.php:1320
|
||||
#: audioigniter.php:333 audioigniter.php:1325
|
||||
msgid "Shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:389
|
||||
#: audioigniter.php:391
|
||||
msgid "AudioIgniter Logo"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:398
|
||||
#: audioigniter.php:400
|
||||
msgid "Upgrade to Pro"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:422
|
||||
#: audioigniter.php:424
|
||||
msgid "Support"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:426
|
||||
#: audioigniter.php:428
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:430
|
||||
#: audioigniter.php:432
|
||||
msgid "Rate this plugin"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s is a URL.
|
||||
#: audioigniter.php:453
|
||||
#: audioigniter.php:455
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Thank you for creating with <a href=\"%s\" target=\"_blank\">AudioIgniter</a>"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:499
|
||||
#: audioigniter.php:501
|
||||
msgid "Toggle track visibility"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:510
|
||||
#: audioigniter.php:512
|
||||
msgid "Remove Cover Image"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:523
|
||||
#: audioigniter.php:525
|
||||
msgid "Upload Cover"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:541 audioigniter.php:548
|
||||
#: audioigniter.php:543 audioigniter.php:550
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:556 audioigniter.php:563
|
||||
#: audioigniter.php:558 audioigniter.php:565
|
||||
msgid "Artist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:572 audioigniter.php:579
|
||||
#: audioigniter.php:574 audioigniter.php:581
|
||||
msgid "Buy link"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:592 audioigniter.php:601
|
||||
#: audioigniter.php:594 audioigniter.php:603
|
||||
msgid "Audio file or radio stream"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:605
|
||||
#: audioigniter.php:607
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:616 audioigniter.php:623
|
||||
#: audioigniter.php:618 audioigniter.php:625
|
||||
msgid "Download URL"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:637
|
||||
#: audioigniter.php:639
|
||||
msgid "Remove Track"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:652
|
||||
#: audioigniter.php:654
|
||||
msgid "Add Track"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:659
|
||||
#: audioigniter.php:661
|
||||
msgid "Clear Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:665
|
||||
#: audioigniter.php:667
|
||||
msgid "Expand All"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:668
|
||||
#: audioigniter.php:670
|
||||
msgid "Collapse All"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:707
|
||||
#: audioigniter.php:709
|
||||
msgid "Player & Track listing"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:712
|
||||
#: audioigniter.php:714
|
||||
msgid "Player Type"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:743
|
||||
#: audioigniter.php:745
|
||||
msgid "Show track listing by default"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:757
|
||||
#: audioigniter.php:759
|
||||
msgid "Show track listing visibility toggle button"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:771
|
||||
#: audioigniter.php:773
|
||||
msgid "Reverse track order"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:777
|
||||
#: audioigniter.php:779
|
||||
msgid "Starting volume"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:788
|
||||
#: audioigniter.php:790
|
||||
msgid "0-100"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:793
|
||||
#: audioigniter.php:795
|
||||
msgid "Enter a value between 0 and 100 in increments of 10"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:807
|
||||
#: audioigniter.php:809
|
||||
msgid "Limit track listing height"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:813 audioigniter.php:823
|
||||
#: audioigniter.php:815 audioigniter.php:825
|
||||
msgid "Track listing height"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:828
|
||||
#: audioigniter.php:830
|
||||
msgid "Set a number of pixels"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:834
|
||||
#: audioigniter.php:836
|
||||
msgid "Maximum player width"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:842
|
||||
#: audioigniter.php:844
|
||||
msgid "Automatic width"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:847
|
||||
#, php-format
|
||||
#: audioigniter.php:849
|
||||
#, no-php-format
|
||||
msgid ""
|
||||
"Set a number of pixels, or leave empty to automatically cover 100% of the "
|
||||
"available area (recommended)."
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:867
|
||||
#: audioigniter.php:869
|
||||
msgid "Show track numbers in tracklist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:881
|
||||
#: audioigniter.php:883
|
||||
msgid "Show track covers in tracklist"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:895
|
||||
#: audioigniter.php:897
|
||||
msgid "Show active track's cover"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:909
|
||||
#: audioigniter.php:911
|
||||
msgid "Show artist names"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:923
|
||||
#: audioigniter.php:925
|
||||
msgid "Show track extra buttons (buy link, download button etc)"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:937
|
||||
#: audioigniter.php:939
|
||||
msgid "Open buy links in new window"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:945
|
||||
#: audioigniter.php:947
|
||||
msgid "Track & Track listing repeat"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:957
|
||||
#: audioigniter.php:959
|
||||
msgid "Repeat track listing enabled by default"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:971
|
||||
#: audioigniter.php:973
|
||||
msgid "Show track listing repeat toggle button"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:988
|
||||
#: audioigniter.php:990
|
||||
msgid "Show \"Powered by AudioIgniter\" link"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:992
|
||||
#: audioigniter.php:994
|
||||
msgid ""
|
||||
"We've put a great deal of effort into building this plugin. If you feel like "
|
||||
"it, let others know about it by enabling this option."
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:1012
|
||||
#: audioigniter.php:1014
|
||||
msgid "Grab the shortcode"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:1046
|
||||
#: audioigniter.php:1048
|
||||
msgid "Full Player"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:1051
|
||||
#: audioigniter.php:1053
|
||||
msgid "Simple Player"
|
||||
msgstr ""
|
||||
|
||||
#: audioigniter.php:1274
|
||||
#: audioigniter.php:1276
|
||||
msgid "ID doesn't match a playlist"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": [
|
||||
"last 2 versions, >1%"
|
||||
]
|
||||
},
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator"
|
||||
]
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
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
|
@ -1,2 +0,0 @@
|
||||
build/
|
||||
node_modules/
|
@ -1,53 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@babel/eslint-parser",
|
||||
"plugins": [
|
||||
"babel",
|
||||
"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,
|
||||
"import/no-cycle": 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/function-component-definition": 0,
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
"react/button-has-type": 0,
|
||||
"react/jsx-fragments": 0,
|
||||
"react/jsx-no-constructed-context-values": 0,
|
||||
"jsx-a11y/label-has-for": 0,
|
||||
"jsx-a11y/click-events-have-key-events": 0
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
16.14.2
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "never",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,59 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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",
|
||||
"downloadFilename": "sunrise.mp3",
|
||||
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/CyberSDF-Flame-and-Go.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",
|
||||
"downloadFilename": "fisherman.mp3",
|
||||
"downloadUrl": "https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3",
|
||||
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/Thoribass-Sunrise.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/demos/audioigniter/wp-content/uploads/sites/48/2016/08/The-Fisherman-Another-Day.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": "Deep House Radio",
|
||||
"subtitle": "",
|
||||
"audio": "https://deephouseradio.radioca.st/stream/1/",
|
||||
"buyUrl": "https://www.cssigniter.com",
|
||||
"cover": "https://www.cssigniter.com/demos/audioigniter/wp-content/uploads/sites/48/2016/08/MegaEnx-Tomorrow.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/demos/audioigniter/wp-content/uploads/sites/48/2016/08/BitBurner-We-Get-Mental.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/demos/audioigniter/wp-content/uploads/sites/48/2016/08/Kxmode-Flash-of-Light.jpg"
|
||||
}
|
||||
]
|
@ -1,72 +0,0 @@
|
||||
{
|
||||
"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",
|
||||
"webpack-profile": "webpack --json > stats.json",
|
||||
"babel-lala": "babel --plugins transform-react-remove-prop-types build/app.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"audio",
|
||||
"audio player",
|
||||
"react"
|
||||
],
|
||||
"author": "vmasto",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.17.10",
|
||||
"@babel/core": "^7.18.5",
|
||||
"@babel/eslint-parser": "^7.18.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.17.12",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.18.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.17.12",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-react": "^7.17.12",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-loader": "^8.2.5",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.30.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"postcss-loader": "^7.0.0",
|
||||
"precss": "^4.0.0",
|
||||
"prettier": "^1.16.4",
|
||||
"sass": "^1.52.3",
|
||||
"sass-loader": "11.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"terser-webpack-plugin": "^5.3.3",
|
||||
"webpack": "5.72.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.2",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.6",
|
||||
"classnames": "2.3.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^18.2.0",
|
||||
"react-custom-scrollbars": "^4.1.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-modal": "^3.8.1",
|
||||
"react-sound": "^1.2.0",
|
||||
"soundmanager2": "^2.97.20170602",
|
||||
"sprintf-js": "1.1.1"
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
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;
|
@ -1,194 +0,0 @@
|
||||
<!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
|
||||
id="audioigniter-01"
|
||||
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="true"
|
||||
data-tracks-delay="5"
|
||||
data-timer-countdown="false"
|
||||
data-shuffle="true"
|
||||
data-shuffle-default="true"
|
||||
data-soundcloud-client-id=""
|
||||
data-remember-last="true"
|
||||
data-player-buttons='[
|
||||
{
|
||||
"title": "CSSIgniter",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
|
||||
},
|
||||
{
|
||||
"title": "",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
|
||||
},
|
||||
{
|
||||
"title": "Another title",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": ""
|
||||
}
|
||||
]
|
||||
'
|
||||
></div>
|
||||
|
||||
<div
|
||||
id="audioigniter-02"
|
||||
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=""
|
||||
data-player-buttons='[
|
||||
{
|
||||
"title": "CSSIgniter",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
|
||||
},
|
||||
{
|
||||
"title": "",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
|
||||
},
|
||||
{
|
||||
"title": "Another title",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": ""
|
||||
}
|
||||
]
|
||||
'
|
||||
></div>
|
||||
|
||||
<div
|
||||
id="audioigniter-03"
|
||||
class="audioigniter-root"
|
||||
|
||||
data-track='{"title":"Sunrise","subtitle":"Thoribass","audio":"https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3","buyUrl":"https:\/\/google.com","downloadUrl":"https:\/\/www.cssigniter.com\/assets\/audioigniter\/sunrise.mp3","cover":"https:\/\/www.cssigniter.com\/demos\/audioigniter\/wp-content\/uploads\/sites\/48\/2016\/08\/CyberSDF-Flame-and-Go.jpg","lyrics":"Some lyrics"}'
|
||||
data-display-active-cover="false"
|
||||
data-display-credits="true"
|
||||
data-allow-playback-rate="false"
|
||||
data-display-buy-buttons="true"
|
||||
data-volume="100"
|
||||
data-skip-amount="0"
|
||||
data-stop-on-finish="true"
|
||||
data-timer-countdown="false"
|
||||
|
||||
data-player-type="full"
|
||||
data-tracks-url=""
|
||||
data-display-tracklist="false"
|
||||
data-allow-tracklist-toggle="true"
|
||||
data-allow-tracklist-loop="true"
|
||||
data-allow-track-loop="true"
|
||||
data-display-track-no="false"
|
||||
data-display-artist-names="true"
|
||||
data-buy-buttons-target="true"
|
||||
data-cycle-tracks="false"
|
||||
data-limit-tracklist-height="false"
|
||||
data-tracklist-height="185"
|
||||
data-reverse-track-order="false"
|
||||
data-max-width="600px"
|
||||
data-initial-track="1"
|
||||
data-tracks-delay="0"
|
||||
data-shuffle="false"
|
||||
data-shuffle-default="false"
|
||||
data-remember-last="false"
|
||||
></div>
|
||||
|
||||
<div
|
||||
id="audioigniter-04"
|
||||
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=""
|
||||
data-remember-last="true"
|
||||
data-player-buttons='[
|
||||
{
|
||||
"title": "CSSIgniter",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": "<svg width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 35.9789 8.77641 45.908 20.25 47.7084V30.9375H14.1562V24H20.25V18.7125C20.25 12.6975 23.8331 9.375 29.3152 9.375C31.9402 9.375 34.6875 9.84375 34.6875 9.84375V15.75H31.6613C28.68 15.75 27.75 17.6002 27.75 19.5V24H34.4062L33.3422 30.9375H27.75V47.7084C39.2236 45.908 48 35.9789 48 24Z\" fill=\"black\"/>\n</svg>"
|
||||
},
|
||||
{
|
||||
"title": "",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": "<svg width=\"48\" height=\"40\" viewBox=\"0 0 48 40\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M15.1003 39.5001C33.2091 39.5001 43.1166 24.4935 43.1166 11.4838C43.1166 11.0619 43.1072 10.6307 43.0884 10.2088C45.0157 8.81501 46.679 7.0886 48 5.11068C46.205 5.90929 44.2993 6.43085 42.3478 6.65756C44.4026 5.4259 45.9411 3.49103 46.6781 1.21162C44.7451 2.3572 42.6312 3.16531 40.4269 3.60131C38.9417 2.02321 36.978 0.97832 34.8394 0.62819C32.7008 0.278059 30.5064 0.642189 28.5955 1.66428C26.6846 2.68637 25.1636 4.3095 24.2677 6.28271C23.3718 8.25592 23.1509 10.4693 23.6391 12.5807C19.725 12.3843 15.8959 11.3675 12.4 9.59628C8.90405 7.82507 5.81939 5.33896 3.34594 2.29912C2.0888 4.46657 1.70411 7.03138 2.27006 9.47227C2.83601 11.9132 4.31013 14.047 6.39281 15.4401C4.82926 15.3904 3.29995 14.9694 1.93125 14.2119V14.3338C1.92985 16.6084 2.7162 18.8133 4.15662 20.5736C5.59704 22.334 7.60265 23.5412 9.8325 23.9901C8.38411 24.3863 6.86396 24.4441 5.38969 24.1588C6.01891 26.115 7.24315 27.8259 8.89154 29.0528C10.5399 30.2796 12.5302 30.9613 14.5847 31.0026C11.0968 33.7423 6.78835 35.2283 2.35313 35.2213C1.56657 35.2201 0.780798 35.1719 0 35.0769C4.50571 37.9676 9.74706 39.5029 15.1003 39.5001Z\" fill=\"black\"/>\n</svg>"
|
||||
},
|
||||
{
|
||||
"title": "Another title",
|
||||
"url": "https://cssigniter.com",
|
||||
"icon": ""
|
||||
}
|
||||
]
|
||||
'
|
||||
></div>
|
||||
</body>
|
||||
</html>
|
@ -1,104 +0,0 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
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',
|
||||
};
|
||||
|
||||
window.aiStats = {
|
||||
apiUrl: '',
|
||||
};
|
||||
}
|
||||
|
||||
const nodes = document.getElementsByClassName('audioigniter-root');
|
||||
|
||||
function renderApp(node) {
|
||||
const type = node.getAttribute('data-player-type');
|
||||
|
||||
const props = {
|
||||
playerId: node.getAttribute('id'),
|
||||
tracksUrl: node.getAttribute('data-tracks-url'),
|
||||
track: node.getAttribute('data-track'),
|
||||
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-default')),
|
||||
shuffleEnabled: JSON.parse(node.getAttribute('data-shuffle')),
|
||||
countdownTimerByDefault: JSON.parse(
|
||||
node.getAttribute('data-timer-countdown'),
|
||||
),
|
||||
rememberLastPosition: JSON.parse(node.getAttribute('data-remember-last')),
|
||||
playerButtons: JSON.parse(node.getAttribute('data-player-buttons')),
|
||||
};
|
||||
|
||||
const root = createRoot(node);
|
||||
root.render(<App type={type} {...props} />);
|
||||
}
|
||||
|
||||
Array.prototype.slice.call(nodes).forEach(node => {
|
||||
renderApp(node);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
window.__CI_AUDIOIGNITER_MANUAL_INIT__ = node => {
|
||||
renderApp(node);
|
||||
};
|
@ -1,354 +0,0 @@
|
||||
import React, { Fragment, useState } 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';
|
||||
import PlayerButtons from './components/PlayerButtons';
|
||||
|
||||
const 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,
|
||||
allowTracklistToggle: PropTypes.bool,
|
||||
allowTracklistLoop: PropTypes.bool,
|
||||
reverseTrackOrder: PropTypes.bool,
|
||||
displayTrackNo: PropTypes.bool,
|
||||
displayTracklist: 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,
|
||||
playerButtons: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
}).isRequired,
|
||||
),
|
||||
playerId: PropTypes.string,
|
||||
};
|
||||
|
||||
const GlobalFooterPlayer = ({
|
||||
tracks,
|
||||
playStatus,
|
||||
activeIndex,
|
||||
volume,
|
||||
position,
|
||||
duration,
|
||||
playbackRate,
|
||||
playerId,
|
||||
|
||||
currentTrack,
|
||||
playTrack,
|
||||
togglePlay,
|
||||
nextTrack,
|
||||
prevTrack,
|
||||
setPosition,
|
||||
setVolume,
|
||||
toggleTracklistCycling,
|
||||
cycleTracks,
|
||||
setTrackCycling,
|
||||
setPlaybackRate,
|
||||
|
||||
allowPlaybackRate,
|
||||
allowTracklistToggle,
|
||||
allowTracklistLoop,
|
||||
allowTrackLoop,
|
||||
reverseTrackOrder,
|
||||
displayTracklist,
|
||||
displayTrackNo,
|
||||
displayTracklistCovers,
|
||||
displayActiveCover,
|
||||
limitTracklistHeight,
|
||||
tracklistHeight,
|
||||
displayBuyButtons,
|
||||
buyButtonsTarget,
|
||||
displayArtistNames,
|
||||
repeatingTrackIndex,
|
||||
skipAmount,
|
||||
skipPosition,
|
||||
countdownTimerByDefault,
|
||||
buffering,
|
||||
playerButtons,
|
||||
}) => {
|
||||
const [isTrackListOpen, setTracklistOpen] = useState(displayTracklist);
|
||||
const toggleTracklist = () => {
|
||||
setTracklistOpen(x => !x);
|
||||
};
|
||||
|
||||
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 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={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}
|
||||
playerId={playerId}
|
||||
/>
|
||||
|
||||
{playerButtons?.length > 0 && <PlayerButtons buttons={playerButtons} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
GlobalFooterPlayer.propTypes = propTypes;
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
});
|
@ -1,402 +0,0 @@
|
||||
import React, { Fragment, useState, useRef } 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';
|
||||
import useComponentSize from '../utils/useComponentSize';
|
||||
import PlayerButtons from './components/PlayerButtons';
|
||||
|
||||
const 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,
|
||||
playerButtons: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
}).isRequired,
|
||||
),
|
||||
playerId: PropTypes.string,
|
||||
};
|
||||
|
||||
const Player = ({
|
||||
tracks,
|
||||
playerId,
|
||||
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,
|
||||
displayTracklist,
|
||||
displayTracklistCovers,
|
||||
displayActiveCover,
|
||||
displayCredits,
|
||||
limitTracklistHeight,
|
||||
tracklistHeight,
|
||||
displayBuyButtons,
|
||||
buyButtonsTarget,
|
||||
displayArtistNames,
|
||||
maxWidth,
|
||||
repeatingTrackIndex,
|
||||
skipAmount,
|
||||
skipPosition,
|
||||
countdownTimerByDefault,
|
||||
buffering,
|
||||
playerButtons,
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
const [isTrackListOpen, setTracklistOpen] = useState(displayTracklist);
|
||||
const { width } = useComponentSize(ref);
|
||||
|
||||
const isNarrowContext = () => {
|
||||
return width != null && width < 480 && window.innerWidth > 480;
|
||||
};
|
||||
|
||||
const toggleTracklist = () => {
|
||||
setTracklistOpen(x => !x);
|
||||
};
|
||||
|
||||
const classes = classNames({
|
||||
'ai-wrap': true,
|
||||
'ai-type-full': true,
|
||||
'ai-is-loading': !tracks.length,
|
||||
'ai-narrow': 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} 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={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}
|
||||
playerId={playerId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{playerButtons?.length > 0 && <PlayerButtons buttons={playerButtons} />}
|
||||
|
||||
{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 = propTypes;
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
});
|
@ -1,140 +0,0 @@
|
||||
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';
|
||||
import PlayerButtons from './components/PlayerButtons';
|
||||
|
||||
const propTypes = {
|
||||
tracks: PropTypes.arrayOf(PropTypes.object),
|
||||
playerId: PropTypes.string,
|
||||
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,
|
||||
playerButtons: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
}).isRequired,
|
||||
),
|
||||
};
|
||||
|
||||
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}
|
||||
playerId={props.playerId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{props.playerButtons?.length > 0 && (
|
||||
<PlayerButtons buttons={props.playerButtons} />
|
||||
)}
|
||||
|
||||
{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 = propTypes;
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
ariaLabel: PropTypes.string,
|
||||
ariaPressed: PropTypes.bool,
|
||||
ariaExpanded: PropTypes.bool,
|
||||
ariaControls: PropTypes.string,
|
||||
};
|
||||
|
||||
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 = propTypes;
|
||||
|
||||
export default Button;
|
@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { MusicNoteIcon } from './Icons';
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
src: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
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 = propTypes;
|
||||
|
||||
export default Cover;
|
@ -1,105 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
button: PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
const PlayerButton = ({ className, button }) => {
|
||||
const classes = classNames({
|
||||
'ai-btn': true,
|
||||
'ai-player-button': true,
|
||||
'ai-player-button-icon-only': !button.title,
|
||||
[className]: !!className,
|
||||
});
|
||||
|
||||
return (
|
||||
<a
|
||||
href={button.url}
|
||||
className={classes}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{button.icon && (
|
||||
<span
|
||||
className="ai-player-button-icon"
|
||||
dangerouslySetInnerHTML={{ __html: button.icon }}
|
||||
/>
|
||||
)}
|
||||
{button.title && (
|
||||
<span className="ai-player-button-title">{button.title}</span>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
PlayerButton.propTypes = propTypes;
|
||||
|
||||
export default PlayerButton;
|
@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import PlayerButton from './PlayerButton';
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
buttons: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
}).isRequired,
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
const PlayerButtons = ({ className, buttons }) => {
|
||||
const classes = classNames({
|
||||
'ai-player-buttons': true,
|
||||
[className]: !!className,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
{buttons.map((button, index) => {
|
||||
return <PlayerButton key={index} button={button} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PlayerButtons.propTypes = propTypes;
|
||||
|
||||
export default PlayerButtons;
|
@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
setPosition: PropTypes.func,
|
||||
position: PropTypes.number.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const ProgressBar = ({ position, duration, setPosition }) => {
|
||||
const handleClick = event => {
|
||||
if (setPosition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetX =
|
||||
event.pageX - event.currentTarget.getBoundingClientRect().left;
|
||||
const posX = offsetX / event.currentTarget.offsetWidth;
|
||||
|
||||
setPosition(posX * duration);
|
||||
};
|
||||
|
||||
return (
|
||||
<span onClick={handleClick} className="ai-track-progress-bar">
|
||||
<span
|
||||
className="ai-track-progress"
|
||||
style={{ width: `${(position * 100) / duration}%` }}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
ProgressBar.propTypes = propTypes;
|
||||
|
||||
export default ProgressBar;
|
@ -1,62 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
position: PropTypes.number.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
countdown: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const Time = ({ countdown, position, duration }) => {
|
||||
const [showRemaining, setShowRemaining] = useState(countdown || false);
|
||||
|
||||
/**
|
||||
* Pretty prints time remaining/elapsed
|
||||
*
|
||||
* @returns {string} - Time pretty formatted
|
||||
*/
|
||||
const renderFormattedTime = () => {
|
||||
if (!duration) {
|
||||
return '00:00';
|
||||
}
|
||||
|
||||
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 (Number.isInteger(parseInt(sec, 10))) {
|
||||
if (hours) {
|
||||
time = `${hours}:${min}:${sec}`;
|
||||
} else {
|
||||
time = `${min}:${sec}`;
|
||||
}
|
||||
}
|
||||
|
||||
return showRemaining ? `-${time}` : time;
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
if (!duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowRemaining(x => !x);
|
||||
};
|
||||
|
||||
return (
|
||||
<span className="ai-track-time" onClick={handleClick}>
|
||||
{renderFormattedTime()}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
Time.propTypes = propTypes;
|
||||
|
||||
export default Time;
|
@ -1,160 +0,0 @@
|
||||
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 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,
|
||||
downloadFilename: 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,
|
||||
playerId: PropTypes.string,
|
||||
};
|
||||
|
||||
const Track = ({
|
||||
track,
|
||||
index,
|
||||
trackNo,
|
||||
isActive,
|
||||
playStatus,
|
||||
duration,
|
||||
position,
|
||||
setPosition,
|
||||
isStandalone,
|
||||
buyButtonsTarget,
|
||||
displayArtistNames,
|
||||
displayCovers,
|
||||
displayBuyButtons,
|
||||
onTrackClick,
|
||||
onTrackLoop,
|
||||
className,
|
||||
isLooping,
|
||||
playbackRate,
|
||||
setPlaybackRate,
|
||||
allowPlaybackRate,
|
||||
buffering,
|
||||
playerId,
|
||||
}) => {
|
||||
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}
|
||||
track={track}
|
||||
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}
|
||||
playerId={playerId}
|
||||
/>
|
||||
|
||||
{hasProgressBar && (
|
||||
<ProgressBar
|
||||
setPosition={setPosition}
|
||||
duration={duration}
|
||||
position={position}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
Track.propTypes = propTypes;
|
||||
|
||||
export default Track;
|
@ -1,149 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { CartIcon, DownloadIcon, LyricsIcon, RefreshIcon } from './Icons';
|
||||
import events, { EVENT, normalizePlayerId } from '../services/events';
|
||||
|
||||
const 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,
|
||||
track: PropTypes.shape({
|
||||
audio: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
playerId: PropTypes.string,
|
||||
};
|
||||
|
||||
const TrackButtons = ({
|
||||
buyButtonsTarget,
|
||||
buyUrl,
|
||||
downloadUrl,
|
||||
downloadFilename,
|
||||
onTrackLoop,
|
||||
isLooping,
|
||||
displayBuyButtons,
|
||||
onOpenTrackLyrics,
|
||||
setPlaybackRate,
|
||||
playbackRate,
|
||||
allowPlaybackRate,
|
||||
isPlaying,
|
||||
track,
|
||||
playerId,
|
||||
}) => {
|
||||
if (
|
||||
buyUrl == null &&
|
||||
downloadUrl == null &&
|
||||
!onTrackLoop &&
|
||||
!onOpenTrackLyrics
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ai-track-control-buttons">
|
||||
{buyUrl && displayBuyButtons && (
|
||||
// eslint-disable-next-line react/jsx-no-target-blank
|
||||
<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"
|
||||
onClick={() => {
|
||||
events.eventTrack({
|
||||
event: EVENT.DOWNLOAD,
|
||||
trackUrl: track.audio,
|
||||
playerId: normalizePlayerId(playerId),
|
||||
});
|
||||
}}
|
||||
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();
|
||||
}}
|
||||
>
|
||||
×{playbackRate}
|
||||
</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 = propTypes;
|
||||
|
||||
export default TrackButtons;
|
@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
if (document.querySelector('.audioigniter-root')) {
|
||||
Modal.setAppElement('.audioigniter-root');
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
TrackLyricsModal.propTypes = propTypes;
|
||||
|
||||
export default TrackLyricsModal;
|
@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const 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,
|
||||
};
|
||||
|
||||
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 = propTypes;
|
||||
|
||||
export default TrackTitle;
|
@ -1,83 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sound from 'react-sound';
|
||||
|
||||
import Track from './Track';
|
||||
|
||||
const 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,
|
||||
repeatingTrackIndex: PropTypes.bool,
|
||||
playerId: PropTypes.string,
|
||||
};
|
||||
|
||||
const Tracklist = ({ ...props }) => {
|
||||
const { tracks } = props;
|
||||
|
||||
return (
|
||||
<ul className={props.className}>
|
||||
{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}
|
||||
playerId={props.playerId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
Tracklist.propTypes = propTypes;
|
||||
|
||||
export default Tracklist;
|
@ -1,111 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Scrollbars } from 'react-custom-scrollbars';
|
||||
|
||||
import Tracklist from './Tracklist';
|
||||
|
||||
const 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,
|
||||
playerId: PropTypes.string,
|
||||
};
|
||||
|
||||
const TracklistWrap = ({
|
||||
isOpen,
|
||||
limitTracklistHeight,
|
||||
tracklistHeight,
|
||||
tracks,
|
||||
activeTrackIndex,
|
||||
onTrackClick,
|
||||
onTrackLoop,
|
||||
className,
|
||||
reverseTrackOrder,
|
||||
trackClassName,
|
||||
displayTrackNo,
|
||||
displayBuyButtons,
|
||||
buyButtonsTarget,
|
||||
displayCovers,
|
||||
displayArtistNames,
|
||||
repeatingTrackIndex,
|
||||
playerId,
|
||||
}) => {
|
||||
const scrollbarRef = useRef(null);
|
||||
|
||||
const isTrackVisible = trackIndex => {
|
||||
const trackHeight = scrollbarRef.current.getScrollHeight() / tracks.length;
|
||||
const trackPosition = trackHeight * trackIndex;
|
||||
const scrollTop = scrollbarRef.current.getScrollTop();
|
||||
const scrollBottom = scrollTop + scrollbarRef.current.getClientHeight();
|
||||
|
||||
return !(trackPosition < scrollTop || trackPosition > scrollBottom);
|
||||
};
|
||||
|
||||
const scrollToTrack = trackIndex => {
|
||||
const trackHeight = scrollbarRef.current.getScrollHeight() / tracks.length;
|
||||
|
||||
if (!isTrackVisible(trackIndex)) {
|
||||
scrollbarRef.current.scrollTop(trackHeight * trackIndex);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (limitTracklistHeight) {
|
||||
scrollToTrack(activeTrackIndex);
|
||||
}
|
||||
}, [activeTrackIndex, limitTracklistHeight]);
|
||||
|
||||
const renderTracklist = () => {
|
||||
return (
|
||||
<Tracklist
|
||||
tracks={tracks}
|
||||
activeTrackIndex={activeTrackIndex}
|
||||
onTrackClick={onTrackClick}
|
||||
className={className}
|
||||
trackClassName={trackClassName}
|
||||
reverseTrackOrder={reverseTrackOrder}
|
||||
displayTrackNo={displayTrackNo}
|
||||
displayBuyButtons={displayBuyButtons}
|
||||
buyButtonsTarget={buyButtonsTarget}
|
||||
displayCovers={displayCovers}
|
||||
displayArtistNames={displayArtistNames}
|
||||
onTrackLoop={onTrackLoop}
|
||||
repeatingTrackIndex={repeatingTrackIndex}
|
||||
playerId={playerId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="tracklisting" style={{ display: isOpen ? 'block' : 'none' }}>
|
||||
{limitTracklistHeight ? (
|
||||
<Scrollbars
|
||||
className="ai-scroll-wrap"
|
||||
ref={scrollbarRef} // eslint-disable-line no-return-assign
|
||||
style={{ height: tracklistHeight }}
|
||||
>
|
||||
{renderTracklist()}
|
||||
</Scrollbars>
|
||||
) : (
|
||||
renderTracklist()
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TracklistWrap.propTypes = propTypes;
|
||||
|
||||
export default TracklistWrap;
|
@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Button from './Button';
|
||||
import { VolumeUpIcon, VolumeDownIcon } from './Icons';
|
||||
|
||||
const propTypes = {
|
||||
volume: PropTypes.number.isRequired,
|
||||
setVolume: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const VolumeControl = ({ volume, setVolume }) => {
|
||||
const renderVolumeBars = () => {
|
||||
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)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ai-audio-volume-control">
|
||||
<div className="ai-audio-volume-bars">{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 = propTypes;
|
||||
|
||||
export default VolumeControl;
|
@ -1,144 +0,0 @@
|
||||
/* global aiStats */
|
||||
|
||||
import isStreamTrack from '../../utils/isStreamTrack';
|
||||
|
||||
/**
|
||||
* @enum EVENT
|
||||
* @type {{PAUSE: string, PLAY: string, STOP: string, DOWNLOAD: string, SEEK: string}}
|
||||
*/
|
||||
export const EVENT = {
|
||||
PLAY: 'PLAY',
|
||||
PLAYING: 'PLAYING',
|
||||
PAUSE: 'PAUSE',
|
||||
STOP: 'STOP',
|
||||
SEEK: 'SEEK',
|
||||
DOWNLOAD: 'DOWNLOAD',
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes a player ID.
|
||||
*
|
||||
* @param {String} playerId The player ID
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export const normalizePlayerId = playerId => {
|
||||
return playerId?.replace('audioigniter-', '') ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes state and props from soundProvider and returns the formatted event data.
|
||||
* @param state
|
||||
* @param props
|
||||
* @returns {{duration, position, trackUrl: *, playerId: *}}
|
||||
*/
|
||||
export const getEventMeta = (state, props) => {
|
||||
const { activeIndex, tracks, position, duration } = state;
|
||||
const { playerId } = props;
|
||||
const track = tracks[activeIndex];
|
||||
const { title, subtitle, audio } = track ?? {};
|
||||
|
||||
return {
|
||||
trackUrl: audio,
|
||||
// trackName: subtitle ? `${title} - ${subtitle}` : title,
|
||||
trackTitle: title,
|
||||
trackArtist: subtitle ?? '',
|
||||
playerId: normalizePlayerId(playerId),
|
||||
position,
|
||||
duration,
|
||||
isStream: isStreamTrack(audio),
|
||||
};
|
||||
};
|
||||
|
||||
class AudioIgniterEvents {
|
||||
constructor() {
|
||||
this.clientId = null;
|
||||
this.queue = [];
|
||||
|
||||
if (!window.aiStats?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventQueueTimer();
|
||||
this.initializeFingerprint();
|
||||
|
||||
// Flush the entire queue when the user ends their session.
|
||||
window.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
this.eventQueueFlush();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initializeFingerprint = async () => {
|
||||
const FingerprintJS = await import(/* webpackChunkName: "fingerprintjs" */ '@fingerprintjs/fingerprintjs');
|
||||
const fingerprint = await FingerprintJS.load();
|
||||
const result = await fingerprint.get();
|
||||
this.clientId = result.visitorId;
|
||||
};
|
||||
|
||||
fetch = async () => {
|
||||
const headers = {
|
||||
type: 'application/json',
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(this.queue)], headers);
|
||||
navigator.sendBeacon(`${aiStats.apiUrl}/log`, blob);
|
||||
};
|
||||
|
||||
eventQueueTimer = () => {
|
||||
setInterval(() => {
|
||||
if (this.queue.length > 0) {
|
||||
this.eventQueueFlush();
|
||||
}
|
||||
}, 15000);
|
||||
};
|
||||
|
||||
eventQueueFlush = async () => {
|
||||
await this.fetch();
|
||||
this.queue = [];
|
||||
};
|
||||
|
||||
eventTrack = ({
|
||||
event,
|
||||
trackUrl,
|
||||
// trackName,
|
||||
trackTitle,
|
||||
trackArtist,
|
||||
playerId,
|
||||
position,
|
||||
oldPosition,
|
||||
duration,
|
||||
isStream,
|
||||
}) => {
|
||||
if (!window.aiStats?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Failsafe for multi sound pausing, some tracks
|
||||
// can be paused before they start due to external
|
||||
// soundManager pausing (see playTrack event in soundProvider.js).
|
||||
if (event === EVENT.PAUSE && position === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.queue.push({
|
||||
event,
|
||||
track_url: trackUrl,
|
||||
// track_name: trackName,
|
||||
track_title: trackTitle,
|
||||
track_artist: trackArtist,
|
||||
playlist_id: parseInt(playerId, 10),
|
||||
timestamp: new Date().getTime(),
|
||||
referrer_url: window.location.href,
|
||||
event_data: {
|
||||
position: Math.floor(position / 1000) ?? null,
|
||||
old_position:
|
||||
oldPosition != null ? Math.floor(oldPosition / 1000) : null,
|
||||
duration: duration ? Math.floor(duration / 1000) : null,
|
||||
},
|
||||
client_fingerprint: this.clientId,
|
||||
is_stream: isStream,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default new AudioIgniterEvents();
|
@ -1,536 +0,0 @@
|
||||
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';
|
||||
import playerStorage from '../utils/playerStorage';
|
||||
import aiEvents, { EVENT, getEventMeta } from './services/events';
|
||||
import throttle from '../utils/throttle';
|
||||
|
||||
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 {
|
||||
playerId,
|
||||
volume,
|
||||
cycleTracks,
|
||||
defaultShuffle,
|
||||
shuffleEnabled,
|
||||
} = this.props;
|
||||
|
||||
const initialData = playerStorage.get(playerId);
|
||||
|
||||
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: initialData?.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);
|
||||
this.aiEventTrackThrottled = throttle(options => {
|
||||
aiEvents.eventTrack(options);
|
||||
}, 60 * 1000);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
playerId,
|
||||
tracksUrl,
|
||||
soundcloudClientId,
|
||||
reverseTrackOrder,
|
||||
initialTrack,
|
||||
rememberLastPosition,
|
||||
track,
|
||||
} = this.props;
|
||||
const { shuffle } = this.state;
|
||||
const initialData = playerStorage.get(playerId);
|
||||
|
||||
// We have a standalone track (from the shortcode).
|
||||
if (track) {
|
||||
try {
|
||||
this.setState({
|
||||
tracks: [JSON.parse(track)],
|
||||
});
|
||||
|
||||
return;
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const tracksPromised = fetch(tracksUrl).then(res => res.json());
|
||||
|
||||
if (!soundcloudClientId) {
|
||||
tracksPromised.then(tracks => {
|
||||
const { trackQueue, activeIndex } = getInitialTrackQueueAndIndex({
|
||||
tracks,
|
||||
initialTrack:
|
||||
initialData?.activeIndex && rememberLastPosition
|
||||
? initialData.activeIndex + 1
|
||||
: 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 }) {
|
||||
const { activeIndex } = this.state;
|
||||
const { playerId, rememberLastPosition } = this.props;
|
||||
|
||||
if (position > 60000) {
|
||||
// Only start calling this after 1 minute into the track
|
||||
this.aiEventTrackThrottled({
|
||||
event: EVENT.PLAYING,
|
||||
...getEventMeta(this.state, this.props),
|
||||
});
|
||||
}
|
||||
|
||||
this.setState(
|
||||
() => ({ duration, position }),
|
||||
() => {
|
||||
if (events && events.onPlaying) {
|
||||
events.onPlaying(this.getFinalProps());
|
||||
}
|
||||
|
||||
if (
|
||||
playerId &&
|
||||
rememberLastPosition &&
|
||||
// Store last position on every 5th second or at the beginning of the track (tiny position num).
|
||||
(position % 5000 < 300 || position < 350)
|
||||
) {
|
||||
playerStorage.set(playerId, {
|
||||
position,
|
||||
activeIndex,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onFinishedPlaying() {
|
||||
const { stopOnTrackFinish, delayBetweenTracks = 0 } = this.props;
|
||||
const delayBetweenTracksMs = delayBetweenTracks * 1000;
|
||||
this.setState(
|
||||
() => ({ playStatus: Sound.status.STOPPED }),
|
||||
() => {
|
||||
aiEvents.eventTrack({
|
||||
event: EVENT.STOP,
|
||||
...getEventMeta(this.state, this.props),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
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) {
|
||||
const currentPosition = this.state.position;
|
||||
|
||||
this.setState(
|
||||
() => ({ position }),
|
||||
() => {
|
||||
aiEvents.eventTrack({
|
||||
event: EVENT.SEEK,
|
||||
...getEventMeta(this.state, this.props),
|
||||
oldPosition: currentPosition,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
playStatus,
|
||||
} = this.state;
|
||||
|
||||
if (isMultiSoundDisabled) {
|
||||
window.soundManager.pauseAll();
|
||||
}
|
||||
|
||||
if (playStatus === Sound.status.PLAYING) {
|
||||
aiEvents.eventTrack({
|
||||
event: EVENT.STOP,
|
||||
...getEventMeta(this.state, this.props),
|
||||
});
|
||||
}
|
||||
|
||||
this.setState(
|
||||
() => ({
|
||||
activeIndex: index,
|
||||
position: 0,
|
||||
playStatus: Sound.status.PLAYING,
|
||||
}),
|
||||
() => {
|
||||
aiEvents.eventTrack({
|
||||
event: EVENT.PLAY,
|
||||
...getEventMeta(
|
||||
{
|
||||
...this.state,
|
||||
duration: null,
|
||||
},
|
||||
this.props,
|
||||
),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Reset repeating 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 }),
|
||||
() => {
|
||||
aiEvents.eventTrack({
|
||||
event: EVENT.PAUSE,
|
||||
...getEventMeta(this.state, this.props),
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
},
|
||||
() => {
|
||||
aiEvents.eventTrack({
|
||||
event:
|
||||
this.state.playStatus === Sound.status.PLAYING
|
||||
? EVENT.PLAY
|
||||
: EVENT.PAUSE,
|
||||
...getEventMeta(this.state, this.props),
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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 = {
|
||||
playerId: PropTypes.string,
|
||||
volume: PropTypes.number,
|
||||
cycleTracks: PropTypes.bool,
|
||||
tracksUrl: PropTypes.string,
|
||||
track: 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,
|
||||
rememberLastPosition: PropTypes.bool,
|
||||
};
|
||||
|
||||
return EnhancedPlayer;
|
||||
};
|
||||
|
||||
export default soundProvider;
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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;
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* 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;
|
@ -1,76 +0,0 @@
|
||||
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;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Determines whether a given url is that of a stream or not.
|
||||
*
|
||||
* @param {string} url The url.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isStreamTrack = url => {
|
||||
const extensions = ['.mp3', '.flac', '.amr', '.aac', '.oga', '.wav', '.wma'];
|
||||
return !extensions.some(extension => url.includes(extension));
|
||||
};
|
||||
|
||||
export default isStreamTrack;
|
@ -1,8 +0,0 @@
|
||||
const multiSoundDisabled = () => {
|
||||
return (
|
||||
window.ai_pro_front_scripts &&
|
||||
!!window.ai_pro_front_scripts.multi_sound_disabled
|
||||
);
|
||||
};
|
||||
|
||||
export default multiSoundDisabled;
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Very basic local storage facade with the ability to store objects.
|
||||
*
|
||||
* @type {{set: playerStorage.set, get: ((function(*): (undefined))|*)}}
|
||||
*/
|
||||
const playerStorage = {
|
||||
set: (key, value) => {
|
||||
if (!key || !value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
} else {
|
||||
window.localStorage.setItem(key, value);
|
||||
}
|
||||
},
|
||||
get: key => {
|
||||
const value = localStorage.getItem(key);
|
||||
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
return parsed;
|
||||
}
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
export default playerStorage;
|
@ -1,88 +0,0 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Simple throttling function.
|
||||
*
|
||||
* @param {Function} fn The function to throttle.
|
||||
* @param {number} limit The limit in milliseconds.
|
||||
* @returns {(function(*): void)|*}
|
||||
*/
|
||||
const throttle = (fn, limit) => {
|
||||
let waiting = false;
|
||||
return function throttleCallback(...args) {
|
||||
if (!waiting) {
|
||||
fn.apply(this, args);
|
||||
waiting = true;
|
||||
setTimeout(() => {
|
||||
waiting = false;
|
||||
}, limit);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default throttle;
|
@ -1,8 +0,0 @@
|
||||
const typographyDisabled = () => {
|
||||
return (
|
||||
window.ai_pro_front_scripts &&
|
||||
!!window.ai_pro_front_scripts.typography_disabled
|
||||
);
|
||||
};
|
||||
|
||||
export default typographyDisabled;
|
@ -1,62 +0,0 @@
|
||||
/**
|
||||
* @file React hook for determining the size of a component.
|
||||
*
|
||||
* Forked from https://github.com/rehooks/component-size.
|
||||
*/
|
||||
|
||||
import { useCallback, useState, useLayoutEffect } from 'react';
|
||||
|
||||
const getSize = el => {
|
||||
if (!el) {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight,
|
||||
};
|
||||
};
|
||||
|
||||
const useComponentSize = ref => {
|
||||
const [ComponentSize, setComponentSize] = useState(
|
||||
getSize(ref ? ref.current : {}),
|
||||
);
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
if (ref.current) {
|
||||
setComponentSize(getSize(ref.current));
|
||||
}
|
||||
}, [ref]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!ref.current) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
handleResize();
|
||||
|
||||
if (typeof ResizeObserver === 'function') {
|
||||
// eslint-disable-next-line no-undef
|
||||
let resizeObserver = new ResizeObserver(() => handleResize());
|
||||
resizeObserver.observe(ref.current);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect(ref.current);
|
||||
resizeObserver = null;
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, [ref.current]);
|
||||
|
||||
return ComponentSize;
|
||||
};
|
||||
|
||||
export default useComponentSize;
|
File diff suppressed because it is too large
Load Diff
@ -1,85 +0,0 @@
|
||||
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: {
|
||||
static: {
|
||||
directory: path.resolve('assets'),
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
let config;
|
||||
|
||||
// Detect how npm is run and branch based on that
|
||||
switch (TARGET) {
|
||||
case 'build': {
|
||||
config = merge(
|
||||
common,
|
||||
{
|
||||
mode: 'production',
|
||||
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,
|
||||
{
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
},
|
||||
parts.setupSass(PATHS.style),
|
||||
parts.devServer({
|
||||
host: process.env.HOST,
|
||||
port: process.env.PORT,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = config;
|
@ -1,86 +0,0 @@
|
||||
const webpack = require('webpack');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const TerserPlugin = require('terser-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: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [
|
||||
autoprefixer({
|
||||
browsers: ['last 2 versions', '>1%'],
|
||||
cascade: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
},
|
||||
],
|
||||
include: paths,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
exports.devServer = options => ({
|
||||
devServer: {
|
||||
static:{
|
||||
directory: __dirname,
|
||||
},
|
||||
historyApiFallback: true,
|
||||
hot: false,
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
},
|
||||
});
|
||||
|
||||
exports.minify = () => ({
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
exports.setFreeVariable = (key, value) => {
|
||||
const env = {};
|
||||
env[key] = JSON.stringify(value);
|
||||
|
||||
return {
|
||||
plugins: [new webpack.DefinePlugin(env)],
|
||||
};
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
||||
=== AudioIgniter Music Player ===
|
||||
Contributors: cssigniterteam, anastis, silencerius, tsiger
|
||||
Tags: audio, podcast, audio player, html5 player, mp3 player, music player, music, radio stream, radio player, sound player, player, podcast player
|
||||
Requires at least: 5.0
|
||||
Tested up to: 6.2
|
||||
Stable tag: 2.0.0
|
||||
Tags: audio, audio player, html5 player, mp3 player, podcast
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.8
|
||||
Stable tag: 2.0.1
|
||||
License: GPLv2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
@ -96,6 +96,9 @@ You can expect the same level of support for both the free and pro version of ou
|
||||
**JOIN OUR COMMUNITY**
|
||||
Join our [Facebook group](https://www.facebook.com/groups/2601788933169108) to discuss new features and stay up to date on our latest releases.
|
||||
|
||||
**Contribute**
|
||||
Visit the [GitHub repository](https://github.com/cssigniter/audioigniter) for full source code and to report any bugs.
|
||||
|
||||
== Installation ==
|
||||
1. Upload the plugin files to the `/wp-content/plugins/audioigniter` directory, or install the plugin through the WordPress plugins screen directly.
|
||||
2. Activate the plugin through the "Plugins" screen in WordPress
|
||||
@ -109,6 +112,11 @@ Join our [Facebook group](https://www.facebook.com/groups/2601788933169108) to d
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 2.0.1 =
|
||||
Fixed issue where a PHP notice would get thrown (in WP 6.7) regarding translations loading too early.
|
||||
Fixed issue where the player would break when repeating a single track via the [ai_track] shortcode (pro).
|
||||
Allow short-circuiting individual tracks from appearing in the endpoint, by returning false in the audioigniter_playlist_endpoint_track filter.
|
||||
|
||||
= 2.0.0 =
|
||||
* Added AudioIgniter top level menu.
|
||||
* Fixed an issue where downloaded tracks would get the full URL as a filename, when the "Use the track URL as the download URL" was checked.
|
||||
|
Loading…
x
Reference in New Issue
Block a user