updated plugin AudioIgniter version 2.0.1

This commit is contained in:
KawaiiPunk 2025-04-29 21:19:08 +00:00 committed by Gitium
parent fdfbf76539
commit d652fac5a4
53 changed files with 108 additions and 11318 deletions

View File

@ -1 +0,0 @@
player/build

View File

@ -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;
}

View File

@ -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%;
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,8 +0,0 @@
module.exports = {
name: 'audioigniter',
paths: {
src: {
styles: ['./assets/css/**/*.scss'],
},
},
};

View File

@ -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 &amp; 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 &amp; 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 ""

View File

@ -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"
]
}

View File

@ -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

View File

@ -1,2 +0,0 @@
build/
node_modules/

View File

@ -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
}
}

View File

@ -1 +0,0 @@
16.14.2

View File

@ -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

View File

@ -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"
}
]

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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);
};

View File

@ -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>&times;{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();
}
},
});

View File

@ -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>&times;{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();
}
},
});

View File

@ -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();
}
},
});

View File

@ -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;

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();
}}
>
&times;{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;

View File

@ -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}
>
&times;
</button>
</div>
<div className="ai-modal-content">{children}</div>
</div>
</Modal>
);
};
TrackLyricsModal.propTypes = propTypes;
export default TrackLyricsModal;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,8 +0,0 @@
const multiSoundDisabled = () => {
return (
window.ai_pro_front_scripts &&
!!window.ai_pro_front_scripts.multi_sound_disabled
);
};
export default multiSoundDisabled;

View File

@ -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;

View File

@ -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;
});
}
}

View File

@ -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;

View File

@ -1,8 +0,0 @@
const typographyDisabled = () => {
return (
window.ai_pro_front_scripts &&
!!window.ai_pro_front_scripts.typography_disabled
);
};
export default typographyDisabled;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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.