├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── gh-pages.yml │ └── publish.yml ├── .gitignore ├── .prettierrc ├── .prettierrc.json ├── LICENSE ├── README.md ├── config └── CSSStub.ts ├── documentation ├── docs │ ├── .vitepress │ │ ├── components │ │ │ ├── AudioPlayerRefExample.vue │ │ │ ├── AudioPlayerWithRef.jsx │ │ │ └── AudioPlayerWrapper.vue │ │ ├── config.ts │ │ ├── lib │ │ │ └── Music.ts │ │ ├── styles │ │ │ └── app.css │ │ └── theme │ │ │ └── index.ts │ ├── examples.md │ ├── index.md │ ├── public │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── clarity.js │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ └── sponsors.md ├── package-lock.json └── package.json ├── jestconfig.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── assets │ └── loading.png ├── components │ ├── AudioPlayer.tsx │ ├── audioPlay.css │ ├── core.interface.ts │ └── index.ts ├── helpers │ ├── icons │ │ └── icons.ts │ └── utils │ │ ├── formatTime.ts │ │ ├── getDeviceEventNames.ts │ │ └── getRangeBox.ts └── index.ts ├── tests └── common.test.tsx └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["prettier", "plugin:prettier/recommended", "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"], 4 | "parser": "@typescript-eslint/parser", 5 | "plugins": ["@typescript-eslint", "prettier", "react", "react-hooks"], 6 | "rules": { 7 | "react-hooks/rules-of-hooks": "error", 8 | "react-hooks/exhaustive-deps": "warn", 9 | "@typescript-eslint/no-non-null-assertion": "off", 10 | "@typescript-eslint/ban-ts-comment": "off", 11 | "@typescript-eslint/no-explicit-any": "off" 12 | }, 13 | "settings": { 14 | "react": { 15 | "version": "detect" 16 | } 17 | }, 18 | "env": { 19 | "browser": true, 20 | "node": true 21 | }, 22 | "globals": { 23 | "JSX": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation Page 2 | 3 | on: 4 | push: 5 | branches: 6 | - doc-build 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '18' 20 | 21 | - name: Install dependencies 22 | run: npm install 23 | working-directory: ./documentation 24 | 25 | - name: Build site 26 | run: npm run docs:build 27 | working-directory: ./documentation 28 | 29 | - name: Deploy to GitHub Pages 30 | uses: peaceiris/actions-gh-pages@v3 31 | with: 32 | github_token: ${{ secrets.GITHUB_TOKEN }} 33 | publish_dir: ./documentation/docs/.vitepress/dist 34 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup Node 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 16.x 19 | registry-url: https://registry.npmjs.org/ 20 | 21 | - name: Install dependencies 22 | run: npm install --force 23 | 24 | - name: Build 25 | run: npm run build 26 | 27 | - name: Publish 28 | run: npm publish 29 | 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | yarn-error.log 5 | .vscode 6 | .parcel-cache 7 | 8 | /documentation/docs/.vitepress/cache 9 | /documentation/docs/docs -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 200, 4 | "proseWrap": "always", 5 | "tabWidth": 2, 6 | "useTabs": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "semi" : true 10 | } -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "tabWidth": 2, 6 | "semi": false, 7 | "printWidth": 120, 8 | "jsxSingleQuote": true, 9 | "endOfLine": "auto" 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Shahidul Alam Riyad (riyaddecoder) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM](https://img.shields.io/npm/v/react-audio-play.svg)](https://www.npmjs.com/package/react-audio-play) 2 | 3 | 4 | 5 | 6 | ## react-audio-play 7 | 8 | `react-audio-play` is a simple, lightweight and customizable audio player npm package for React applications. It provides an easy-to-use interface to play audio files in your React components with 9 | minimal setup. 10 | 11 | 12 | 13 | ## Features 14 | 15 | - Play audio files in your React application with a single component. 16 | - Control playback with play, pause, stop, and volume adjustment functionality. 17 | - Display track progress with a customizable progress bar. 18 | - Trigger custom actions on audio events like onPlay, onPause, onStop, onEnd, etc. 19 | - Fully customizable appearance to match your application's design. 20 | 21 | ## Demo 22 | 23 | Check [examples](https://riyaddecoder.github.io/react-audio-play/examples.html) 24 | 25 | ## Installation 26 | 27 | You can install `react-audio-play` using npm or yarn: 28 | 29 | ```bash 30 | npm install react-audio-play 31 | ``` 32 | 33 | or 34 | 35 | ```bash 36 | yarn add react-audio-play 37 | ``` 38 | 39 | ## Usage 40 | 41 | To use `react-audio-play`, import the `AudioPlayer` component and provide the necessary props: 42 | 43 | ```js 44 | import React from 'react'; 45 | import { AudioPlayer } from 'react-audio-play'; 46 | 47 | const App = () => { 48 | return ( 49 |
50 |

My Audio Player

51 | 52 |
53 | ); 54 | }; 55 | 56 | export default App; 57 | ``` 58 | 59 | #### Keyboard shortcuts (When audio player focused) 60 | 61 | They can be turned off by setting `hasKeyBindings` prop to `false` 62 | 63 | | Key binding | Action | 64 | | ----------- | ------ | 65 | | Space | Play/Pause | 66 | | ← | Rewind | 67 | | → | Forward | 68 | | ↑ | Volume up | 69 | | ↓ | Volume down | 70 | 71 | ## Props 72 | 73 | `react-audio-play` accepts the following props: 74 | 75 | - `className` (string, optional): A CSS class name for styling the component. 76 | - `src` (string, required): The URL or file path of the audio file to be played. 77 | - `autoPlay` (boolean, optional): Set this to `true` to autoplay the audio. Default is `false`. 78 | - `preload` (string, optional): Specifies the preload behavior for the audio file. Possible values are: 79 | - `auto`: The audio data is loaded as soon as possible. 80 | - `metadata`: Only metadata (e.g., duration) is loaded. 81 | - `none`: No preloading. Audio data is only loaded when requested. 82 | - `loop` (boolean, optional): Set this to `true` to enable looping of the audio playback. Default is `false`. 83 | - `volume` (number, optional): The initial volume level of the audio, ranging from 0 to 100. Default is `100`. 84 | - `hasKeyBindings` (boolean, optional): Specifies whether the `AudioPlayer` component should enable keyboard shortcuts for volume control and seeking. Default is `true`. 85 | - `onPlay` (function, optional): Callback function to execute when the audio starts playing. 86 | - `onPause` (function, optional): Callback function to execute when the audio is paused. 87 | - `onEnd` (function, optional): Callback function to execute when the audio playback ends. 88 | - `onError` (function, optional): Callback function to execute if there's an error loading or playing the audio. 89 | - `backgroundColor` (string, optional): The background color of the audio player. Default is `#fff`. 90 | - `color` (string, optional): The text and icon color of the audio player. Default is `#566574`. 91 | - `sliderColor` (string, optional): The color of the progress slider. Default is `#007FFF`. 92 | - `volumePlacement` (string, optional): Specifies the placement of the volume controls. Possible values are `top` and `bottom`. Default is `top`. 93 | - `width` (string, optional): The width of the audio player. Use this prop to set the width of the player. For example, `"100%"`, `"300px"`, etc. 94 | - `style` (object, optional): An object containing additional inline styles for the component. 95 | 96 | 97 | ## Advanced Usage 98 | 99 | Starting with version `v1.0.4`, you can access certain actions of the `AudioPlayer` component programmatically using a `ref` with the following interface: 100 | 101 | - `play`: Function to start audio playback. 102 | - `pause`: Function to pause audio playback. 103 | - `stop`: Function to stop the audio playback. 104 | - `focus`: Function to focus on the audio player component. 105 | 106 | ## Example usage with ref (available from v1.0.4) 107 | 108 | [Live preview here](https://riyaddecoder.github.io/react-audio-play/examples.html#example-4-usage-with-ref-available-from-v1-0-4) 109 | 110 | ```js 111 | import { useRef } from 'react'; 112 | import { AudioPlayer, AudioPlayerRef } from 'react-audio-play'; 113 | 114 | function App() { 115 | const playerRef = useRef(null); 116 | 117 | const handlePlay = () => { 118 | playerRef.current?.play(); 119 | }; 120 | 121 | const handlePause = () => { 122 | playerRef.current?.pause(); 123 | }; 124 | 125 | const handleStop = () => { 126 | playerRef.current?.stop(); 127 | }; 128 | 129 | const handleFocus = () => { 130 | playerRef.current?.focus(); 131 | }; 132 | 133 | return ( 134 |
135 | 136 | 137 | 138 | 139 | 140 |
141 | ); 142 | } 143 | 144 | ``` 145 | 146 | ## Example with Custom Event Handling 147 | 148 | ```js 149 | import React from 'react'; 150 | import { AudioPlayer } from 'react-audio-play'; 151 | 152 | const App = () => { 153 | const handlePlay = () => { 154 | console.log('Audio started playing'); 155 | }; 156 | 157 | const handlePause = () => { 158 | console.log('Audio paused'); 159 | }; 160 | 161 | return ( 162 |
163 |

My Audio Player

164 | 165 |
166 | ); 167 | }; 168 | 169 | export default App; 170 | ``` 171 | 172 | ## Custom Styling 173 | 174 | You can easily customize the appearance of the audio player by applying your CSS styles to the AudioPlayer component or by overriding the default styles in your project's CSS. Check 175 | [examples](https://riyaddecoder.github.io/react-audio-play/examples.html#example-6-using-style-object) 176 | 177 | ## License 178 | 179 | This package is open-source and distributed under the MIT License. See the [LICENSE](https://github.com/riyaddecoder/react-audio-play/blob/master/LICENSE) file for details. 180 | 181 | ## Contribution 182 | 183 | Contributions are welcome! If you find any issues or have suggestions, feel free to create an issue or submit a pull request on the 184 | [GitHub repository](https://github.com/riyaddecoder/react-audio-play/). Let's build this package together! 185 | 186 | 187 | 188 | Enjoy using react-audio-play in your React applications, and we hope it enhances your audio experience with ease and flexibility. If you have any questions or need further assistance, don't hesitate 189 | to reach out to us! Happy coding! 🎶 190 | -------------------------------------------------------------------------------- /config/CSSStub.ts: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /documentation/docs/.vitepress/components/AudioPlayerRefExample.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | -------------------------------------------------------------------------------- /documentation/docs/.vitepress/components/AudioPlayerWithRef.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { AudioPlayer } from 'react-audio-play'; 3 | 4 | export default function AudioPlayerWithRef(props) { 5 | const playerRef = useRef(null); 6 | 7 | const handlePlay = () => { 8 | playerRef.current?.play(); 9 | }; 10 | 11 | const handlePause = () => { 12 | playerRef.current?.pause(); 13 | }; 14 | 15 | const handleStop = () => { 16 | playerRef.current?.stop(); 17 | }; 18 | 19 | const handleFocus = () => { 20 | playerRef.current?.focus(); 21 | }; 22 | 23 | return ( 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /documentation/docs/.vitepress/components/AudioPlayerWrapper.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 89 | 90 | -------------------------------------------------------------------------------- /documentation/docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | 3 | const basePath = "/react-audio-play"; 4 | const ogDescription = 5 | "React-audio-play is a simple, lightweight and customizable audio player npm package for React applications"; 6 | const ogImage = `${basePath}/android-chrome-192x192.png`; 7 | const ogTitle = "React Audio Play | A Simple Audio Player Package For ReactJS"; 8 | const ogUrl = "https://riyaddecoder.github.io/react-audio-play"; 9 | 10 | export default defineConfig({ 11 | base: `${basePath}/`, 12 | title: `React Audio Play`, 13 | description: 14 | "react-audio-play is a simple, lightweight and customizable audio player npm package for React applications. It provides an easy-to-use interface to play audio files in your React components with minimal setup.", 15 | 16 | head: [ 17 | [ 18 | "link", 19 | { 20 | rel: "icon", 21 | type: "image/png", 22 | sizes: "16x16", 23 | href: `${basePath}/favicon-16x16.png`, 24 | }, 25 | ], 26 | [ 27 | "link", 28 | { 29 | rel: "icon", 30 | type: "image/png", 31 | sizes: "32x32", 32 | href: `${basePath}/favicon-32x32.png`, 33 | }, 34 | ], 35 | [ 36 | "link", 37 | { 38 | rel: "apple-touch-icon", 39 | sizes: "180x180", 40 | href: `${basePath}/apple-touch-icon.png`, 41 | }, 42 | ], 43 | [ 44 | "link", 45 | { 46 | rel: "icon", 47 | type: "image/png", 48 | sizes: "192x192", 49 | href: `${basePath}/android-chrome-192x192.png`, 50 | }, 51 | ], 52 | [ 53 | "link", 54 | { 55 | rel: "icon", 56 | type: "image/png", 57 | sizes: "512x512", 58 | href: `${basePath}/android-chrome-512x512.png`, 59 | }, 60 | ], 61 | ["link", { rel: "preconnect", href: "https://fonts.googleapis.com" }], 62 | [ 63 | "link", 64 | { 65 | rel: "preconnect", 66 | href: "https://fonts.gstatic.com", 67 | crossorigin: "true", 68 | }, 69 | ], 70 | [ 71 | "link", 72 | { 73 | rel: "preload", 74 | href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Manrope:wght@600&family=IBM+Plex+Mono:wght@400&display=swap", 75 | as: "style", 76 | }, 77 | ], 78 | [ 79 | "link", 80 | { 81 | rel: "stylesheet", 82 | href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Manrope:wght@600&family=IBM+Plex+Mono:wght@400&display=swap", 83 | }, 84 | ], 85 | [ 86 | "meta", 87 | { 88 | name: "keywords", 89 | content: 90 | "reactJs, React Package, React Audio Player, Audio Player For React, Simple Audio Player, Audio Player NPM, riyaddecoder", 91 | }, 92 | ], 93 | [ 94 | "meta", 95 | { name: "author", content: "Shahidul Alam Riyad aka riyaddecoder" }, 96 | ], 97 | 98 | ["meta", { property: "og:title", content: ogTitle }], 99 | ["meta", { property: "og:description", content: ogDescription }], 100 | ["meta", { property: "og:image", content: ogImage }], 101 | ["meta", { property: "og:url", content: ogUrl }], 102 | ["meta", { property: "og:type", content: "npmPakcage" }], 103 | ["meta", { property: "og:site_name", content: "react-audio-play" }], 104 | ["meta", { name: "twitter:card", content: "REACT AUDIO PLAY" }], 105 | ["meta", { name: "twitter:site", content: "@riyaddecoder" }], 106 | [ 107 | "meta", 108 | { 109 | name: "twitter:title", 110 | content: "React Audio Play | A Simple Audio Player Package For ReactJS", 111 | }, 112 | ], 113 | [ 114 | "meta", 115 | { 116 | name: "twitter:description", 117 | content: 118 | "react-audio-play is a simple, lightweight and customizable audio player npm package for React applications.", 119 | }, 120 | ], 121 | [ 122 | "meta", 123 | { 124 | name: "twitter:image", 125 | content: `${basePath}/android-chrome-192x192.png`, 126 | }, 127 | ], 128 | [ 129 | "script", 130 | { 131 | src: `${basePath}/clarity.js`, 132 | defer: "", 133 | }, 134 | ], 135 | ], 136 | 137 | themeConfig: { 138 | logo: `/apple-touch-icon.png`, 139 | 140 | editLink: { 141 | pattern: 142 | "https://github.com/riyaddecoder/react-audio-play/edit/master/documentation/docs/:path", 143 | text: "Suggest changes to this page", 144 | }, 145 | 146 | socialLinks: [ 147 | { icon: "npm", link: "https://www.npmjs.com/package/react-audio-play" }, 148 | { icon: "linkedin", link: "https://www.linkedin.com/in/riyaddecoder/" }, 149 | { icon: "discord", link: "https://discord.gg/5THusG85" }, 150 | { 151 | icon: "github", 152 | link: "https://github.com/riyaddecoder/react-audio-play", 153 | }, 154 | ], 155 | 156 | footer: { 157 | message: `Released under the MIT License`, 158 | copyright: 159 | "Copyright © 2022-present Shahidul Alam Riyad", 160 | }, 161 | 162 | nav: [ 163 | { text: "Examples", link: "/examples/", activeMatch: "/examples" }, 164 | { text: "Sponsors", link: "/sponsors/", activeMatch: "/sponsors" }, 165 | ], 166 | 167 | sidebar: { 168 | "/sponsors": [], 169 | "/": [ 170 | { 171 | // text: "Guide", 172 | items: [ 173 | { 174 | text: "Getting Started", 175 | link: "/", 176 | }, 177 | { 178 | text: "Examples", 179 | link: "/examples", 180 | }, 181 | ], 182 | }, 183 | ], 184 | }, 185 | 186 | outline: { 187 | level: [2, 3], 188 | }, 189 | }, 190 | transformPageData(pageData) { 191 | const canonicalUrl = `${ogUrl}/${pageData.relativePath}` 192 | .replace(/\/index\.md$/, "/") 193 | .replace(/\.md$/, "/"); 194 | pageData.frontmatter.head ??= []; 195 | pageData.frontmatter.head.unshift( 196 | ["link", { rel: "canonical", href: canonicalUrl }], 197 | ["meta", { property: "og:title", content: pageData.title }] 198 | ); 199 | return pageData; 200 | }, 201 | }); 202 | -------------------------------------------------------------------------------- /documentation/docs/.vitepress/lib/Music.ts: -------------------------------------------------------------------------------- 1 | const MUSICS = [ 2 | { 3 | url: 'https://github.com/riyaddecoder/audio-files/raw/master/Aurthohin-Anmone.mp3', 4 | title: 'Anmone - Aurthohin', 5 | sourceLink: 'https://youtu.be/OZZ_Qc2t49M?si=mXdMlU2pZVF-b5Mi' 6 | }, 7 | { 8 | url: 'https://github.com/riyaddecoder/audio-files/raw/master/Kotha%20Koiyo%20Na%20-%20Coke%20Studio%20Bangla.mp3', 9 | title: 'Kotha koio na - Coke Studio', 10 | sourceLink: 'https://youtu.be/a7V9bUwc4cU?si=WyOopTUwhjZuLwZs' 11 | }, 12 | { 13 | url: 'https://github.com/riyaddecoder/audio-files/raw/master/Anmone2-Aurthohin.mp3', 14 | title: 'Anmone 2 - Aurthohin', 15 | sourceLink: 'https://youtu.be/qWW4GBgQII8?si=e_ijRpo144ip0LT6' 16 | }, 17 | { 18 | url: 'https://github.com/riyaddecoder/audio-files/raw/master/Poth%20Chola%20-%20Artcell.mp3', 19 | title: 'Poth Chola - Artcell', 20 | sourceLink: 'https://youtu.be/CKfhGvUPXkY?si=NSEmDY1T-gsUigSv' 21 | } 22 | ]; 23 | 24 | export class Music { 25 | private currentMusic = MUSICS[0]; 26 | private musicIndexes = new Set(); 27 | 28 | nextMusic = () => { 29 | if (MUSICS.length <= this.musicIndexes.size) { 30 | this.musicIndexes.clear(); 31 | } 32 | let number; 33 | do { 34 | number = Math.floor(Math.random() * MUSICS.length); 35 | } while (this.musicIndexes.has(number)); 36 | this.currentMusic = MUSICS[number]; 37 | this.musicIndexes.add(number); 38 | return MUSICS[number]; 39 | }; 40 | 41 | getCurrentMusic = () => { 42 | return this.currentMusic; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /documentation/docs/.vitepress/styles/app.css: -------------------------------------------------------------------------------- 1 | .VPNavBar .vpi-social-npm { 2 | color: #cb3837; 3 | } 4 | 5 | .dark .VPNavBar .vpi-social-npm { 6 | color: #ff5150; 7 | } 8 | 9 | .VPNavBar .vpi-social-linkedin { 10 | color: #0a66c2; 11 | } 12 | 13 | .dark .VPNavBar .vpi-social-linkedin { 14 | color: #1e8eff; 15 | } 16 | 17 | .VPNavBar .vpi-social-discord { 18 | color: #5865f2; 19 | } 20 | 21 | .dark .VPNavBar .vpi-social-discord { 22 | color: #6c78ff; 23 | } 24 | 25 | .dark .VPNavBar .vpi-social-github { 26 | color: #fff; 27 | } 28 | 29 | .VPNavBar .vpi-social-github { 30 | color: #000; 31 | } 32 | -------------------------------------------------------------------------------- /documentation/docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // docs/.vitepress/theme/index.js 2 | import DefaultTheme from 'vitepress/theme' 3 | import '../styles/app.css' // Import your custom CSS here 4 | 5 | export default { 6 | ...DefaultTheme, 7 | enhanceApp({ app }) { 8 | // You can add more custom logic here if needed 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /documentation/docs/examples.md: -------------------------------------------------------------------------------- 1 | # Example Usage 2 | 3 | Below are examples of how you can use the `AudioPlayer` component. Replace `path/to/audio.mp3` with the actual URL or path of your audio file. 4 | 5 | ## Example 1: Basic usage with default props 6 | 7 | ```js 8 | import { AudioPlayer } from "react-audio-play"; 9 | 10 | export default function App() { 11 | return ; 12 | } 13 | ``` 14 | 15 | Preview 16 | 17 | 18 | 19 | Music Source: {{music.getCurrentMusic().title}} 20 | 21 | ## Example 2: Looping audio, set the volume to 50% and volume control placement bottom 22 | 23 | ```js 24 | import { AudioPlayer } from "react-audio-play"; 25 | 26 | export default function App() { 27 | return ( 28 | 34 | ); 35 | } 36 | ``` 37 | 38 | Preview 39 | 40 | 41 | 42 | Music Source: {{music.getCurrentMusic().title}} 43 | 44 | ## Example 3: Using audio callbacks 45 | 46 | ```js 47 | import { AudioPlayer } from "react-audio-play"; 48 | 49 | export default function App() { 50 | const handlePlay = () => { 51 | console.log("Audio started playing"); 52 | }; 53 | 54 | const handlePause = () => { 55 | console.log("Audio paused"); 56 | }; 57 | 58 | const handleEnd = () => { 59 | console.log("Audio ended"); 60 | }; 61 | 62 | const handleError = (event, errorMessage) => { 63 | console.log(errorMessage); 64 | }; 65 | 66 | return ( 67 | 74 | ); 75 | } 76 | ``` 77 | 78 | Preview 79 | 80 | 81 | 82 | Music Source: {{music.getCurrentMusic().title}} 83 | 84 | ## Example 4: Usage with ref (available from v1.0.4) 85 | 86 | ```tsx 87 | import { useRef } from 'react'; 88 | import { AudioPlayer, AudioPlayerRef } from 'react-audio-play'; 89 | 90 | function App() { 91 | const playerRef = useRef(null); 92 | 93 | const handlePlay = () => { 94 | playerRef.current?.play(); 95 | }; 96 | 97 | const handlePause = () => { 98 | playerRef.current?.pause(); 99 | }; 100 | 101 | const handleStop = () => { 102 | playerRef.current?.stop(); 103 | }; 104 | 105 | const handleFocus = () => { 106 | playerRef.current?.focus(); 107 | }; 108 | 109 | return ( 110 |
111 | 112 | 113 | 114 | 115 | 116 |
117 | ); 118 | } 119 | 120 | ``` 121 | 122 | Preview 123 | 124 | 125 | 126 | Music Source: {{music.getCurrentMusic().title}} 127 | 128 | ## Example 5: Darkmode using basic style props 129 | 130 | ```js 131 | import { AudioPlayer } from "react-audio-play"; 132 | 133 | export default function App() { 134 | return ( 135 | 141 | ); 142 | } 143 | ``` 144 | 145 | Preview 146 | 147 | 148 | 149 | Music Source: {{music.getCurrentMusic().title}} 150 | 151 | ## Example 6: Using Style Object 152 | 153 | ```js 154 | import { AudioPlayer } from "react-audio-play"; 155 | 156 | export default function App() { 157 | return ( 158 | 164 | ); 165 | } 166 | ``` 167 | 168 | Preview 169 | 170 | 171 | 172 | Music Source: {{music.getCurrentMusic().title}} 173 | 174 | ## Example 7: Using Custom CSS 175 | 176 | ::: code-group 177 | 178 | ```css [CSS] 179 | .custom-style.rap-container { 180 | background-color: #000000; 181 | background-image: linear-gradient(147deg, #000000 0%, #04619f 74%); 182 | color: aliceblue; 183 | } 184 | 185 | .custom-style.rap-container .rap-pp-icon path, 186 | .custom-style.rap-container .rap-volume-btn path { 187 | fill: white; 188 | } 189 | 190 | .custom-style.rap-container .rap-slider .rap-progress { 191 | background-color: #daecff; 192 | } 193 | 194 | .custom-style.rap-container .rap-volume .rap-volume-controls { 195 | background-color: #000000; 196 | background-image: linear-gradient(147deg, #000000 0%, #04619f 74%); 197 | } 198 | 199 | .custom-style.rap-container .rap-slider .rap-progress .rap-pin { 200 | background-color: #c3d5ff; 201 | box-shadow: 0 0 9px 7px #269eff52; 202 | } 203 | 204 | .custom-style.rap-container svg.rap-pp-icon:hover, 205 | .custom-style.rap-container .rap-volume-btn svg:hover { 206 | filter: drop-shadow(0px 0px 6px rgba(255, 255, 255, 0.9)); 207 | } 208 | ``` 209 | 210 | ```js [TSX] 211 | import { AudioPlayer } from "react-audio-play"; 212 | 213 | export default function App() { 214 | return ; 215 | } 216 | ``` 217 | 218 | ::: 219 | 220 | ::: warning 221 | Use a wrapper class to avoid CSS override issues. Ex: `.custom-style` 222 | ::: 223 | 224 | Preview 225 | 226 | 227 | 228 | Music Source: {{music.getCurrentMusic().title}} 229 | 230 | ## Example 8: More Playing With CSS 231 | 232 | ::: code-group 233 | 234 | ```css [CSS] 235 | .custom-style.rap-container { 236 | background-color: #e4e4e4; 237 | color: #566574; 238 | border-radius: 20px; 239 | } 240 | 241 | .custom-style.rap-container .rap-slider .rap-progress { 242 | background-color: #959595; 243 | } 244 | 245 | .custom-style.rap-container .rap-slider .rap-progress .rap-pin { 246 | background-color: #566574; 247 | height: 18px; 248 | width: 18px; 249 | border-radius: 10px; 250 | } 251 | 252 | .custom-style.rap-container .rap-controls .rap-slider .rap-progress .rap-pin { 253 | top: -5px; 254 | } 255 | 256 | .custom-style.rap-container .rap-controls .rap-slider { 257 | height: 8px; 258 | border-radius: 4px; 259 | } 260 | 261 | .custom-style.rap-container .rap-volume .rap-volume-btn.rap-volume-open path { 262 | fill: #000; 263 | } 264 | 265 | .custom-style.rap-container .rap-volume .rap-volume-controls { 266 | background-color: #e4e4e4; 267 | } 268 | 269 | .custom-style.rap-container .rap-volume .rap-volume-controls .rap-slider, 270 | .custom-style.rap-container 271 | .rap-volume 272 | .rap-volume-controls 273 | .rap-slider 274 | .rap-progress { 275 | width: 8px; 276 | } 277 | ``` 278 | 279 | ```js [TSX] 280 | import { AudioPlayer } from "react-audio-play"; 281 | 282 | export default function App() { 283 | return ; 284 | } 285 | ``` 286 | 287 | ::: 288 | 289 | ::: warning 290 | Use a wrapper class to avoid CSS override issues. Ex: `.custom-style` 291 | ::: 292 | 293 | Preview 294 | 295 | 296 | 297 | Music Source: {{music.getCurrentMusic().title}} 298 | 299 | 306 | -------------------------------------------------------------------------------- /documentation/docs/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Installation 4 | 5 | Welcome to the Audio Player documentation. To get started, follow the steps below: 6 | 7 | 1. **Install the package using npm:** 8 | 9 | ```bash 10 | npm install react-audio-play 11 | ``` 12 | 13 | 2. **Alternatively, install with yarn:** 14 | 15 | ```bash 16 | yarn add react-audio-play 17 | ``` 18 | 19 | 3. **Import and use the `AudioPlayer` component in your project:** 20 | 21 | ```js 22 | import { AudioPlayer } from 'react-audio-play'; 23 | 24 | export default function App() { 25 | return ; 26 | } 27 | ``` 28 | 29 | 4. **Customize the appearance and functionality as needed.** 30 | 31 | ## Props 32 | 33 | The **AudioPlayer** component accepts the following props: 34 | 35 | | Prop | Type | Description | 36 | | --------------- | -------------------- | --------------------------------------------------------------------------------------------- | 37 | | `className` | string, optional | A CSS class name for styling the component. | 38 | | `src` | string, required | The URL or file path of the audio file to be played. | 39 | | `autoPlay` | boolean, optional | Set to `true` to autoplay the audio (default: `false`). | 40 | | `preload` | string, optional |
  • `auto` - The audio data is loaded as soon as possible.
  • `metadata` - Only metadata (e.g., duration) is loaded.
  • `none` - No preloading. Audio data is only loaded when requested.
| 41 | | `loop` | boolean, optional | Set to `true` to loop the audio playback (default: `false`). | 42 | | `volume` | number, optional | The initial volume level (0 to 100) of the audio (default: `100`). | 43 | | `hasKeyBindings`| boolean, optional | Specifies whether the `AudioPlayer` component should enable keyboard shortcuts for volume control and seeking (default: `true`). | 44 | | `onPlay` | function, optional | Callback function to execute when the audio starts playing. | 45 | | `onPause` | function, optional | Callback function to execute when the audio is paused. | 46 | | `onEnd` | function, optional | Callback function to execute when the audio playback ends. | 47 | | `onError` | function, optional | Callback function to execute if there’s an error loading or playing the audio. | 48 | | `backgroundColor`| string, optional | Set the background color of the audio player (default: `#fff`). | 49 | | `color` | string, optional | The text and icon color of the audio player (default: `#566574`). | 50 | | `sliderColor` | string, optional | The color of the progress slider (default: `#007FFF`). | 51 | | `volumePlacement`| string, optional | Control where the volume controls are located (`top | bottom`) (default: `top`). | 52 | | `width` | string, optional | The width of the audio player. For example, `"100%"`, `"300px"`, etc. | 53 | | `style` | object, optional | An object containing additional inline styles for the component. | 54 | 55 | 56 | ## Advanced Usage 57 | 58 | Starting with version `v1.0.4`, you can access certain actions of the `AudioPlayer` component programmatically using a `ref` with the following interface: 59 | 60 | - `play`: Function to start audio playback. 61 | - `pause`: Function to pause audio playback. 62 | - `stop`: Function to stop the audio playback. 63 | - `focus`: Function to focus on the audio player component. 64 | 65 | ::: info 66 | [Example Here](/examples.html#example-4-usage-with-ref-available-from-v1-0-4) 67 | ::: 68 | 69 | ## Keyboard Shortcuts 70 | 71 | Below are the keyboard shortcuts available when the audio player is focused. They can be turned off by setting the `hasKeyBindings` prop to `false`. 72 | 73 | | Key | Action | 74 | | ----- | ------------- | 75 | | Space | Play/Pause | 76 | | ← | Rewind | 77 | | → | Forward | 78 | | ↑ | Volume up | 79 | | ↓ | Volume down | 80 | 81 | 82 | -------------------------------------------------------------------------------- /documentation/docs/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riyaddecoder/react-audio-play/8353163064a1d6bcce4f44c791913a81462620c6/documentation/docs/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /documentation/docs/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riyaddecoder/react-audio-play/8353163064a1d6bcce4f44c791913a81462620c6/documentation/docs/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /documentation/docs/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riyaddecoder/react-audio-play/8353163064a1d6bcce4f44c791913a81462620c6/documentation/docs/public/apple-touch-icon.png -------------------------------------------------------------------------------- /documentation/docs/public/clarity.js: -------------------------------------------------------------------------------- 1 | (function (c, l, a, r, i, t, y) { 2 | c[a] = 3 | c[a] || 4 | function () { 5 | (c[a].q = c[a].q || []).push(arguments); 6 | }; 7 | t = l.createElement(r); 8 | t.async = 1; 9 | t.src = 'https://www.clarity.ms/tag/' + i; 10 | y = l.getElementsByTagName(r)[0]; 11 | y.parentNode.insertBefore(t, y); 12 | })(window, document, 'clarity', 'script', 'itd5tbp46u'); 13 | -------------------------------------------------------------------------------- /documentation/docs/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riyaddecoder/react-audio-play/8353163064a1d6bcce4f44c791913a81462620c6/documentation/docs/public/favicon-16x16.png -------------------------------------------------------------------------------- /documentation/docs/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riyaddecoder/react-audio-play/8353163064a1d6bcce4f44c791913a81462620c6/documentation/docs/public/favicon-32x32.png -------------------------------------------------------------------------------- /documentation/docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riyaddecoder/react-audio-play/8353163064a1d6bcce4f44c791913a81462620c6/documentation/docs/public/favicon.ico -------------------------------------------------------------------------------- /documentation/docs/sponsors.md: -------------------------------------------------------------------------------- 1 | # Sponsors 2 | 3 | Currently, there are no sponsors. 4 | 5 | However, if you'd like to support the project, you can contribute by buying me a cup of tea! Your support helps keep the project running and allows for continuous improvement. 6 | 7 | [Buy me a cup of tea](https://buymeacoffee.com/riyaddecoder) 8 | 9 | Thank you for your generosity and support! 10 | -------------------------------------------------------------------------------- /documentation/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentation", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "react": "^18.3.1", 9 | "react-audio-play": "^1.0.4", 10 | "react-dom": "^18.3.1" 11 | }, 12 | "devDependencies": { 13 | "@types/express": "^4.17.21", 14 | "vitepress": "^1.3.4" 15 | } 16 | }, 17 | "node_modules/@algolia/autocomplete-core": { 18 | "version": "1.9.3", 19 | "dev": true, 20 | "license": "MIT", 21 | "dependencies": { 22 | "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", 23 | "@algolia/autocomplete-shared": "1.9.3" 24 | } 25 | }, 26 | "node_modules/@algolia/autocomplete-plugin-algolia-insights": { 27 | "version": "1.9.3", 28 | "dev": true, 29 | "license": "MIT", 30 | "dependencies": { 31 | "@algolia/autocomplete-shared": "1.9.3" 32 | }, 33 | "peerDependencies": { 34 | "search-insights": ">= 1 < 3" 35 | } 36 | }, 37 | "node_modules/@algolia/autocomplete-preset-algolia": { 38 | "version": "1.9.3", 39 | "dev": true, 40 | "license": "MIT", 41 | "dependencies": { 42 | "@algolia/autocomplete-shared": "1.9.3" 43 | }, 44 | "peerDependencies": { 45 | "@algolia/client-search": ">= 4.9.1 < 6", 46 | "algoliasearch": ">= 4.9.1 < 6" 47 | } 48 | }, 49 | "node_modules/@algolia/autocomplete-shared": { 50 | "version": "1.9.3", 51 | "dev": true, 52 | "license": "MIT", 53 | "peerDependencies": { 54 | "@algolia/client-search": ">= 4.9.1 < 6", 55 | "algoliasearch": ">= 4.9.1 < 6" 56 | } 57 | }, 58 | "node_modules/@algolia/cache-browser-local-storage": { 59 | "version": "4.24.0", 60 | "dev": true, 61 | "license": "MIT", 62 | "dependencies": { 63 | "@algolia/cache-common": "4.24.0" 64 | } 65 | }, 66 | "node_modules/@algolia/cache-common": { 67 | "version": "4.24.0", 68 | "dev": true, 69 | "license": "MIT" 70 | }, 71 | "node_modules/@algolia/cache-in-memory": { 72 | "version": "4.24.0", 73 | "dev": true, 74 | "license": "MIT", 75 | "dependencies": { 76 | "@algolia/cache-common": "4.24.0" 77 | } 78 | }, 79 | "node_modules/@algolia/client-account": { 80 | "version": "4.24.0", 81 | "dev": true, 82 | "license": "MIT", 83 | "dependencies": { 84 | "@algolia/client-common": "4.24.0", 85 | "@algolia/client-search": "4.24.0", 86 | "@algolia/transporter": "4.24.0" 87 | } 88 | }, 89 | "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { 90 | "version": "4.24.0", 91 | "dev": true, 92 | "license": "MIT", 93 | "dependencies": { 94 | "@algolia/requester-common": "4.24.0", 95 | "@algolia/transporter": "4.24.0" 96 | } 97 | }, 98 | "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { 99 | "version": "4.24.0", 100 | "dev": true, 101 | "license": "MIT", 102 | "dependencies": { 103 | "@algolia/client-common": "4.24.0", 104 | "@algolia/requester-common": "4.24.0", 105 | "@algolia/transporter": "4.24.0" 106 | } 107 | }, 108 | "node_modules/@algolia/client-analytics": { 109 | "version": "4.24.0", 110 | "dev": true, 111 | "license": "MIT", 112 | "dependencies": { 113 | "@algolia/client-common": "4.24.0", 114 | "@algolia/client-search": "4.24.0", 115 | "@algolia/requester-common": "4.24.0", 116 | "@algolia/transporter": "4.24.0" 117 | } 118 | }, 119 | "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { 120 | "version": "4.24.0", 121 | "dev": true, 122 | "license": "MIT", 123 | "dependencies": { 124 | "@algolia/requester-common": "4.24.0", 125 | "@algolia/transporter": "4.24.0" 126 | } 127 | }, 128 | "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { 129 | "version": "4.24.0", 130 | "dev": true, 131 | "license": "MIT", 132 | "dependencies": { 133 | "@algolia/client-common": "4.24.0", 134 | "@algolia/requester-common": "4.24.0", 135 | "@algolia/transporter": "4.24.0" 136 | } 137 | }, 138 | "node_modules/@algolia/client-common": { 139 | "version": "5.8.1", 140 | "dev": true, 141 | "license": "MIT", 142 | "peer": true, 143 | "engines": { 144 | "node": ">= 14.0.0" 145 | } 146 | }, 147 | "node_modules/@algolia/client-personalization": { 148 | "version": "4.24.0", 149 | "dev": true, 150 | "license": "MIT", 151 | "dependencies": { 152 | "@algolia/client-common": "4.24.0", 153 | "@algolia/requester-common": "4.24.0", 154 | "@algolia/transporter": "4.24.0" 155 | } 156 | }, 157 | "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { 158 | "version": "4.24.0", 159 | "dev": true, 160 | "license": "MIT", 161 | "dependencies": { 162 | "@algolia/requester-common": "4.24.0", 163 | "@algolia/transporter": "4.24.0" 164 | } 165 | }, 166 | "node_modules/@algolia/client-search": { 167 | "version": "5.8.1", 168 | "dev": true, 169 | "license": "MIT", 170 | "peer": true, 171 | "dependencies": { 172 | "@algolia/client-common": "5.8.1", 173 | "@algolia/requester-browser-xhr": "5.8.1", 174 | "@algolia/requester-fetch": "5.8.1", 175 | "@algolia/requester-node-http": "5.8.1" 176 | }, 177 | "engines": { 178 | "node": ">= 14.0.0" 179 | } 180 | }, 181 | "node_modules/@algolia/logger-common": { 182 | "version": "4.24.0", 183 | "dev": true, 184 | "license": "MIT" 185 | }, 186 | "node_modules/@algolia/logger-console": { 187 | "version": "4.24.0", 188 | "dev": true, 189 | "license": "MIT", 190 | "dependencies": { 191 | "@algolia/logger-common": "4.24.0" 192 | } 193 | }, 194 | "node_modules/@algolia/recommend": { 195 | "version": "4.24.0", 196 | "dev": true, 197 | "license": "MIT", 198 | "dependencies": { 199 | "@algolia/cache-browser-local-storage": "4.24.0", 200 | "@algolia/cache-common": "4.24.0", 201 | "@algolia/cache-in-memory": "4.24.0", 202 | "@algolia/client-common": "4.24.0", 203 | "@algolia/client-search": "4.24.0", 204 | "@algolia/logger-common": "4.24.0", 205 | "@algolia/logger-console": "4.24.0", 206 | "@algolia/requester-browser-xhr": "4.24.0", 207 | "@algolia/requester-common": "4.24.0", 208 | "@algolia/requester-node-http": "4.24.0", 209 | "@algolia/transporter": "4.24.0" 210 | } 211 | }, 212 | "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { 213 | "version": "4.24.0", 214 | "dev": true, 215 | "license": "MIT", 216 | "dependencies": { 217 | "@algolia/requester-common": "4.24.0", 218 | "@algolia/transporter": "4.24.0" 219 | } 220 | }, 221 | "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { 222 | "version": "4.24.0", 223 | "dev": true, 224 | "license": "MIT", 225 | "dependencies": { 226 | "@algolia/client-common": "4.24.0", 227 | "@algolia/requester-common": "4.24.0", 228 | "@algolia/transporter": "4.24.0" 229 | } 230 | }, 231 | "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { 232 | "version": "4.24.0", 233 | "dev": true, 234 | "license": "MIT", 235 | "dependencies": { 236 | "@algolia/requester-common": "4.24.0" 237 | } 238 | }, 239 | "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { 240 | "version": "4.24.0", 241 | "dev": true, 242 | "license": "MIT", 243 | "dependencies": { 244 | "@algolia/requester-common": "4.24.0" 245 | } 246 | }, 247 | "node_modules/@algolia/requester-browser-xhr": { 248 | "version": "5.8.1", 249 | "dev": true, 250 | "license": "MIT", 251 | "peer": true, 252 | "dependencies": { 253 | "@algolia/client-common": "5.8.1" 254 | }, 255 | "engines": { 256 | "node": ">= 14.0.0" 257 | } 258 | }, 259 | "node_modules/@algolia/requester-common": { 260 | "version": "4.24.0", 261 | "dev": true, 262 | "license": "MIT" 263 | }, 264 | "node_modules/@algolia/requester-fetch": { 265 | "version": "5.8.1", 266 | "dev": true, 267 | "license": "MIT", 268 | "peer": true, 269 | "dependencies": { 270 | "@algolia/client-common": "5.8.1" 271 | }, 272 | "engines": { 273 | "node": ">= 14.0.0" 274 | } 275 | }, 276 | "node_modules/@algolia/requester-node-http": { 277 | "version": "5.8.1", 278 | "dev": true, 279 | "license": "MIT", 280 | "peer": true, 281 | "dependencies": { 282 | "@algolia/client-common": "5.8.1" 283 | }, 284 | "engines": { 285 | "node": ">= 14.0.0" 286 | } 287 | }, 288 | "node_modules/@algolia/transporter": { 289 | "version": "4.24.0", 290 | "dev": true, 291 | "license": "MIT", 292 | "dependencies": { 293 | "@algolia/cache-common": "4.24.0", 294 | "@algolia/logger-common": "4.24.0", 295 | "@algolia/requester-common": "4.24.0" 296 | } 297 | }, 298 | "node_modules/@babel/helper-string-parser": { 299 | "version": "7.25.7", 300 | "dev": true, 301 | "license": "MIT", 302 | "engines": { 303 | "node": ">=6.9.0" 304 | } 305 | }, 306 | "node_modules/@babel/helper-validator-identifier": { 307 | "version": "7.25.7", 308 | "dev": true, 309 | "license": "MIT", 310 | "engines": { 311 | "node": ">=6.9.0" 312 | } 313 | }, 314 | "node_modules/@babel/parser": { 315 | "version": "7.25.8", 316 | "dev": true, 317 | "license": "MIT", 318 | "dependencies": { 319 | "@babel/types": "^7.25.8" 320 | }, 321 | "bin": { 322 | "parser": "bin/babel-parser.js" 323 | }, 324 | "engines": { 325 | "node": ">=6.0.0" 326 | } 327 | }, 328 | "node_modules/@babel/types": { 329 | "version": "7.25.8", 330 | "dev": true, 331 | "license": "MIT", 332 | "dependencies": { 333 | "@babel/helper-string-parser": "^7.25.7", 334 | "@babel/helper-validator-identifier": "^7.25.7", 335 | "to-fast-properties": "^2.0.0" 336 | }, 337 | "engines": { 338 | "node": ">=6.9.0" 339 | } 340 | }, 341 | "node_modules/@docsearch/css": { 342 | "version": "3.6.2", 343 | "dev": true, 344 | "license": "MIT" 345 | }, 346 | "node_modules/@docsearch/js": { 347 | "version": "3.6.2", 348 | "dev": true, 349 | "license": "MIT", 350 | "dependencies": { 351 | "@docsearch/react": "3.6.2", 352 | "preact": "^10.0.0" 353 | } 354 | }, 355 | "node_modules/@docsearch/react": { 356 | "version": "3.6.2", 357 | "dev": true, 358 | "license": "MIT", 359 | "dependencies": { 360 | "@algolia/autocomplete-core": "1.9.3", 361 | "@algolia/autocomplete-preset-algolia": "1.9.3", 362 | "@docsearch/css": "3.6.2", 363 | "algoliasearch": "^4.19.1" 364 | }, 365 | "peerDependencies": { 366 | "@types/react": ">= 16.8.0 < 19.0.0", 367 | "react": ">= 16.8.0 < 19.0.0", 368 | "react-dom": ">= 16.8.0 < 19.0.0", 369 | "search-insights": ">= 1 < 3" 370 | }, 371 | "peerDependenciesMeta": { 372 | "@types/react": { 373 | "optional": true 374 | }, 375 | "react": { 376 | "optional": true 377 | }, 378 | "react-dom": { 379 | "optional": true 380 | }, 381 | "search-insights": { 382 | "optional": true 383 | } 384 | } 385 | }, 386 | "node_modules/@esbuild/linux-x64": { 387 | "version": "0.21.5", 388 | "cpu": [ 389 | "x64" 390 | ], 391 | "dev": true, 392 | "license": "MIT", 393 | "optional": true, 394 | "os": [ 395 | "linux" 396 | ], 397 | "engines": { 398 | "node": ">=12" 399 | } 400 | }, 401 | "node_modules/@jridgewell/sourcemap-codec": { 402 | "version": "1.5.0", 403 | "dev": true, 404 | "license": "MIT" 405 | }, 406 | "node_modules/@rollup/rollup-linux-x64-gnu": { 407 | "version": "4.24.0", 408 | "cpu": [ 409 | "x64" 410 | ], 411 | "dev": true, 412 | "license": "MIT", 413 | "optional": true, 414 | "os": [ 415 | "linux" 416 | ] 417 | }, 418 | "node_modules/@rollup/rollup-linux-x64-musl": { 419 | "version": "4.24.0", 420 | "cpu": [ 421 | "x64" 422 | ], 423 | "dev": true, 424 | "license": "MIT", 425 | "optional": true, 426 | "os": [ 427 | "linux" 428 | ] 429 | }, 430 | "node_modules/@shikijs/core": { 431 | "version": "1.22.0", 432 | "dev": true, 433 | "license": "MIT", 434 | "dependencies": { 435 | "@shikijs/engine-javascript": "1.22.0", 436 | "@shikijs/engine-oniguruma": "1.22.0", 437 | "@shikijs/types": "1.22.0", 438 | "@shikijs/vscode-textmate": "^9.3.0", 439 | "@types/hast": "^3.0.4", 440 | "hast-util-to-html": "^9.0.3" 441 | } 442 | }, 443 | "node_modules/@shikijs/engine-javascript": { 444 | "version": "1.22.0", 445 | "dev": true, 446 | "license": "MIT", 447 | "dependencies": { 448 | "@shikijs/types": "1.22.0", 449 | "@shikijs/vscode-textmate": "^9.3.0", 450 | "oniguruma-to-js": "0.4.3" 451 | } 452 | }, 453 | "node_modules/@shikijs/engine-oniguruma": { 454 | "version": "1.22.0", 455 | "dev": true, 456 | "license": "MIT", 457 | "dependencies": { 458 | "@shikijs/types": "1.22.0", 459 | "@shikijs/vscode-textmate": "^9.3.0" 460 | } 461 | }, 462 | "node_modules/@shikijs/transformers": { 463 | "version": "1.22.0", 464 | "dev": true, 465 | "license": "MIT", 466 | "dependencies": { 467 | "shiki": "1.22.0" 468 | } 469 | }, 470 | "node_modules/@shikijs/types": { 471 | "version": "1.22.0", 472 | "dev": true, 473 | "license": "MIT", 474 | "dependencies": { 475 | "@shikijs/vscode-textmate": "^9.3.0", 476 | "@types/hast": "^3.0.4" 477 | } 478 | }, 479 | "node_modules/@shikijs/vscode-textmate": { 480 | "version": "9.3.0", 481 | "dev": true, 482 | "license": "MIT" 483 | }, 484 | "node_modules/@types/body-parser": { 485 | "version": "1.19.5", 486 | "dev": true, 487 | "license": "MIT", 488 | "dependencies": { 489 | "@types/connect": "*", 490 | "@types/node": "*" 491 | } 492 | }, 493 | "node_modules/@types/connect": { 494 | "version": "3.4.38", 495 | "dev": true, 496 | "license": "MIT", 497 | "dependencies": { 498 | "@types/node": "*" 499 | } 500 | }, 501 | "node_modules/@types/estree": { 502 | "version": "1.0.6", 503 | "dev": true, 504 | "license": "MIT" 505 | }, 506 | "node_modules/@types/express": { 507 | "version": "4.17.21", 508 | "dev": true, 509 | "license": "MIT", 510 | "dependencies": { 511 | "@types/body-parser": "*", 512 | "@types/express-serve-static-core": "^4.17.33", 513 | "@types/qs": "*", 514 | "@types/serve-static": "*" 515 | } 516 | }, 517 | "node_modules/@types/express-serve-static-core": { 518 | "version": "4.19.6", 519 | "dev": true, 520 | "license": "MIT", 521 | "dependencies": { 522 | "@types/node": "*", 523 | "@types/qs": "*", 524 | "@types/range-parser": "*", 525 | "@types/send": "*" 526 | } 527 | }, 528 | "node_modules/@types/hast": { 529 | "version": "3.0.4", 530 | "dev": true, 531 | "license": "MIT", 532 | "dependencies": { 533 | "@types/unist": "*" 534 | } 535 | }, 536 | "node_modules/@types/http-errors": { 537 | "version": "2.0.4", 538 | "dev": true, 539 | "license": "MIT" 540 | }, 541 | "node_modules/@types/linkify-it": { 542 | "version": "5.0.0", 543 | "dev": true, 544 | "license": "MIT" 545 | }, 546 | "node_modules/@types/markdown-it": { 547 | "version": "14.1.2", 548 | "dev": true, 549 | "license": "MIT", 550 | "dependencies": { 551 | "@types/linkify-it": "^5", 552 | "@types/mdurl": "^2" 553 | } 554 | }, 555 | "node_modules/@types/mdast": { 556 | "version": "4.0.4", 557 | "dev": true, 558 | "license": "MIT", 559 | "dependencies": { 560 | "@types/unist": "*" 561 | } 562 | }, 563 | "node_modules/@types/mdurl": { 564 | "version": "2.0.0", 565 | "dev": true, 566 | "license": "MIT" 567 | }, 568 | "node_modules/@types/mime": { 569 | "version": "1.3.5", 570 | "dev": true, 571 | "license": "MIT" 572 | }, 573 | "node_modules/@types/node": { 574 | "version": "22.7.5", 575 | "dev": true, 576 | "license": "MIT", 577 | "dependencies": { 578 | "undici-types": "~6.19.2" 579 | } 580 | }, 581 | "node_modules/@types/qs": { 582 | "version": "6.9.16", 583 | "dev": true, 584 | "license": "MIT" 585 | }, 586 | "node_modules/@types/range-parser": { 587 | "version": "1.2.7", 588 | "dev": true, 589 | "license": "MIT" 590 | }, 591 | "node_modules/@types/send": { 592 | "version": "0.17.4", 593 | "dev": true, 594 | "license": "MIT", 595 | "dependencies": { 596 | "@types/mime": "^1", 597 | "@types/node": "*" 598 | } 599 | }, 600 | "node_modules/@types/serve-static": { 601 | "version": "1.15.7", 602 | "dev": true, 603 | "license": "MIT", 604 | "dependencies": { 605 | "@types/http-errors": "*", 606 | "@types/node": "*", 607 | "@types/send": "*" 608 | } 609 | }, 610 | "node_modules/@types/unist": { 611 | "version": "3.0.3", 612 | "dev": true, 613 | "license": "MIT" 614 | }, 615 | "node_modules/@types/web-bluetooth": { 616 | "version": "0.0.20", 617 | "dev": true, 618 | "license": "MIT" 619 | }, 620 | "node_modules/@ungap/structured-clone": { 621 | "version": "1.2.0", 622 | "dev": true, 623 | "license": "ISC" 624 | }, 625 | "node_modules/@vitejs/plugin-vue": { 626 | "version": "5.1.4", 627 | "dev": true, 628 | "license": "MIT", 629 | "engines": { 630 | "node": "^18.0.0 || >=20.0.0" 631 | }, 632 | "peerDependencies": { 633 | "vite": "^5.0.0", 634 | "vue": "^3.2.25" 635 | } 636 | }, 637 | "node_modules/@vue/compiler-core": { 638 | "version": "3.5.12", 639 | "dev": true, 640 | "license": "MIT", 641 | "dependencies": { 642 | "@babel/parser": "^7.25.3", 643 | "@vue/shared": "3.5.12", 644 | "entities": "^4.5.0", 645 | "estree-walker": "^2.0.2", 646 | "source-map-js": "^1.2.0" 647 | } 648 | }, 649 | "node_modules/@vue/compiler-dom": { 650 | "version": "3.5.12", 651 | "dev": true, 652 | "license": "MIT", 653 | "dependencies": { 654 | "@vue/compiler-core": "3.5.12", 655 | "@vue/shared": "3.5.12" 656 | } 657 | }, 658 | "node_modules/@vue/compiler-sfc": { 659 | "version": "3.5.12", 660 | "dev": true, 661 | "license": "MIT", 662 | "dependencies": { 663 | "@babel/parser": "^7.25.3", 664 | "@vue/compiler-core": "3.5.12", 665 | "@vue/compiler-dom": "3.5.12", 666 | "@vue/compiler-ssr": "3.5.12", 667 | "@vue/shared": "3.5.12", 668 | "estree-walker": "^2.0.2", 669 | "magic-string": "^0.30.11", 670 | "postcss": "^8.4.47", 671 | "source-map-js": "^1.2.0" 672 | } 673 | }, 674 | "node_modules/@vue/compiler-ssr": { 675 | "version": "3.5.12", 676 | "dev": true, 677 | "license": "MIT", 678 | "dependencies": { 679 | "@vue/compiler-dom": "3.5.12", 680 | "@vue/shared": "3.5.12" 681 | } 682 | }, 683 | "node_modules/@vue/devtools-api": { 684 | "version": "7.4.6", 685 | "dev": true, 686 | "license": "MIT", 687 | "dependencies": { 688 | "@vue/devtools-kit": "^7.4.6" 689 | } 690 | }, 691 | "node_modules/@vue/devtools-kit": { 692 | "version": "7.4.6", 693 | "dev": true, 694 | "license": "MIT", 695 | "dependencies": { 696 | "@vue/devtools-shared": "^7.4.6", 697 | "birpc": "^0.2.17", 698 | "hookable": "^5.5.3", 699 | "mitt": "^3.0.1", 700 | "perfect-debounce": "^1.0.0", 701 | "speakingurl": "^14.0.1", 702 | "superjson": "^2.2.1" 703 | } 704 | }, 705 | "node_modules/@vue/devtools-shared": { 706 | "version": "7.4.6", 707 | "dev": true, 708 | "license": "MIT", 709 | "dependencies": { 710 | "rfdc": "^1.4.1" 711 | } 712 | }, 713 | "node_modules/@vue/reactivity": { 714 | "version": "3.5.12", 715 | "dev": true, 716 | "license": "MIT", 717 | "dependencies": { 718 | "@vue/shared": "3.5.12" 719 | } 720 | }, 721 | "node_modules/@vue/runtime-core": { 722 | "version": "3.5.12", 723 | "dev": true, 724 | "license": "MIT", 725 | "dependencies": { 726 | "@vue/reactivity": "3.5.12", 727 | "@vue/shared": "3.5.12" 728 | } 729 | }, 730 | "node_modules/@vue/runtime-dom": { 731 | "version": "3.5.12", 732 | "dev": true, 733 | "license": "MIT", 734 | "dependencies": { 735 | "@vue/reactivity": "3.5.12", 736 | "@vue/runtime-core": "3.5.12", 737 | "@vue/shared": "3.5.12", 738 | "csstype": "^3.1.3" 739 | } 740 | }, 741 | "node_modules/@vue/server-renderer": { 742 | "version": "3.5.12", 743 | "dev": true, 744 | "license": "MIT", 745 | "dependencies": { 746 | "@vue/compiler-ssr": "3.5.12", 747 | "@vue/shared": "3.5.12" 748 | }, 749 | "peerDependencies": { 750 | "vue": "3.5.12" 751 | } 752 | }, 753 | "node_modules/@vue/shared": { 754 | "version": "3.5.12", 755 | "dev": true, 756 | "license": "MIT" 757 | }, 758 | "node_modules/@vueuse/core": { 759 | "version": "11.1.0", 760 | "dev": true, 761 | "license": "MIT", 762 | "dependencies": { 763 | "@types/web-bluetooth": "^0.0.20", 764 | "@vueuse/metadata": "11.1.0", 765 | "@vueuse/shared": "11.1.0", 766 | "vue-demi": ">=0.14.10" 767 | }, 768 | "funding": { 769 | "url": "https://github.com/sponsors/antfu" 770 | } 771 | }, 772 | "node_modules/@vueuse/core/node_modules/vue-demi": { 773 | "version": "0.14.10", 774 | "dev": true, 775 | "hasInstallScript": true, 776 | "license": "MIT", 777 | "bin": { 778 | "vue-demi-fix": "bin/vue-demi-fix.js", 779 | "vue-demi-switch": "bin/vue-demi-switch.js" 780 | }, 781 | "engines": { 782 | "node": ">=12" 783 | }, 784 | "funding": { 785 | "url": "https://github.com/sponsors/antfu" 786 | }, 787 | "peerDependencies": { 788 | "@vue/composition-api": "^1.0.0-rc.1", 789 | "vue": "^3.0.0-0 || ^2.6.0" 790 | }, 791 | "peerDependenciesMeta": { 792 | "@vue/composition-api": { 793 | "optional": true 794 | } 795 | } 796 | }, 797 | "node_modules/@vueuse/integrations": { 798 | "version": "11.1.0", 799 | "dev": true, 800 | "license": "MIT", 801 | "dependencies": { 802 | "@vueuse/core": "11.1.0", 803 | "@vueuse/shared": "11.1.0", 804 | "vue-demi": ">=0.14.10" 805 | }, 806 | "funding": { 807 | "url": "https://github.com/sponsors/antfu" 808 | }, 809 | "peerDependencies": { 810 | "async-validator": "^4", 811 | "axios": "^1", 812 | "change-case": "^5", 813 | "drauu": "^0.4", 814 | "focus-trap": "^7", 815 | "fuse.js": "^7", 816 | "idb-keyval": "^6", 817 | "jwt-decode": "^4", 818 | "nprogress": "^0.2", 819 | "qrcode": "^1.5", 820 | "sortablejs": "^1", 821 | "universal-cookie": "^7" 822 | }, 823 | "peerDependenciesMeta": { 824 | "async-validator": { 825 | "optional": true 826 | }, 827 | "axios": { 828 | "optional": true 829 | }, 830 | "change-case": { 831 | "optional": true 832 | }, 833 | "drauu": { 834 | "optional": true 835 | }, 836 | "focus-trap": { 837 | "optional": true 838 | }, 839 | "fuse.js": { 840 | "optional": true 841 | }, 842 | "idb-keyval": { 843 | "optional": true 844 | }, 845 | "jwt-decode": { 846 | "optional": true 847 | }, 848 | "nprogress": { 849 | "optional": true 850 | }, 851 | "qrcode": { 852 | "optional": true 853 | }, 854 | "sortablejs": { 855 | "optional": true 856 | }, 857 | "universal-cookie": { 858 | "optional": true 859 | } 860 | } 861 | }, 862 | "node_modules/@vueuse/integrations/node_modules/vue-demi": { 863 | "version": "0.14.10", 864 | "dev": true, 865 | "hasInstallScript": true, 866 | "license": "MIT", 867 | "bin": { 868 | "vue-demi-fix": "bin/vue-demi-fix.js", 869 | "vue-demi-switch": "bin/vue-demi-switch.js" 870 | }, 871 | "engines": { 872 | "node": ">=12" 873 | }, 874 | "funding": { 875 | "url": "https://github.com/sponsors/antfu" 876 | }, 877 | "peerDependencies": { 878 | "@vue/composition-api": "^1.0.0-rc.1", 879 | "vue": "^3.0.0-0 || ^2.6.0" 880 | }, 881 | "peerDependenciesMeta": { 882 | "@vue/composition-api": { 883 | "optional": true 884 | } 885 | } 886 | }, 887 | "node_modules/@vueuse/metadata": { 888 | "version": "11.1.0", 889 | "dev": true, 890 | "license": "MIT", 891 | "funding": { 892 | "url": "https://github.com/sponsors/antfu" 893 | } 894 | }, 895 | "node_modules/@vueuse/shared": { 896 | "version": "11.1.0", 897 | "dev": true, 898 | "license": "MIT", 899 | "dependencies": { 900 | "vue-demi": ">=0.14.10" 901 | }, 902 | "funding": { 903 | "url": "https://github.com/sponsors/antfu" 904 | } 905 | }, 906 | "node_modules/@vueuse/shared/node_modules/vue-demi": { 907 | "version": "0.14.10", 908 | "dev": true, 909 | "hasInstallScript": true, 910 | "license": "MIT", 911 | "bin": { 912 | "vue-demi-fix": "bin/vue-demi-fix.js", 913 | "vue-demi-switch": "bin/vue-demi-switch.js" 914 | }, 915 | "engines": { 916 | "node": ">=12" 917 | }, 918 | "funding": { 919 | "url": "https://github.com/sponsors/antfu" 920 | }, 921 | "peerDependencies": { 922 | "@vue/composition-api": "^1.0.0-rc.1", 923 | "vue": "^3.0.0-0 || ^2.6.0" 924 | }, 925 | "peerDependenciesMeta": { 926 | "@vue/composition-api": { 927 | "optional": true 928 | } 929 | } 930 | }, 931 | "node_modules/algoliasearch": { 932 | "version": "4.24.0", 933 | "dev": true, 934 | "license": "MIT", 935 | "dependencies": { 936 | "@algolia/cache-browser-local-storage": "4.24.0", 937 | "@algolia/cache-common": "4.24.0", 938 | "@algolia/cache-in-memory": "4.24.0", 939 | "@algolia/client-account": "4.24.0", 940 | "@algolia/client-analytics": "4.24.0", 941 | "@algolia/client-common": "4.24.0", 942 | "@algolia/client-personalization": "4.24.0", 943 | "@algolia/client-search": "4.24.0", 944 | "@algolia/logger-common": "4.24.0", 945 | "@algolia/logger-console": "4.24.0", 946 | "@algolia/recommend": "4.24.0", 947 | "@algolia/requester-browser-xhr": "4.24.0", 948 | "@algolia/requester-common": "4.24.0", 949 | "@algolia/requester-node-http": "4.24.0", 950 | "@algolia/transporter": "4.24.0" 951 | } 952 | }, 953 | "node_modules/algoliasearch/node_modules/@algolia/client-common": { 954 | "version": "4.24.0", 955 | "dev": true, 956 | "license": "MIT", 957 | "dependencies": { 958 | "@algolia/requester-common": "4.24.0", 959 | "@algolia/transporter": "4.24.0" 960 | } 961 | }, 962 | "node_modules/algoliasearch/node_modules/@algolia/client-search": { 963 | "version": "4.24.0", 964 | "dev": true, 965 | "license": "MIT", 966 | "dependencies": { 967 | "@algolia/client-common": "4.24.0", 968 | "@algolia/requester-common": "4.24.0", 969 | "@algolia/transporter": "4.24.0" 970 | } 971 | }, 972 | "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { 973 | "version": "4.24.0", 974 | "dev": true, 975 | "license": "MIT", 976 | "dependencies": { 977 | "@algolia/requester-common": "4.24.0" 978 | } 979 | }, 980 | "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { 981 | "version": "4.24.0", 982 | "dev": true, 983 | "license": "MIT", 984 | "dependencies": { 985 | "@algolia/requester-common": "4.24.0" 986 | } 987 | }, 988 | "node_modules/birpc": { 989 | "version": "0.2.19", 990 | "dev": true, 991 | "license": "MIT", 992 | "funding": { 993 | "url": "https://github.com/sponsors/antfu" 994 | } 995 | }, 996 | "node_modules/ccount": { 997 | "version": "2.0.1", 998 | "dev": true, 999 | "license": "MIT", 1000 | "funding": { 1001 | "type": "github", 1002 | "url": "https://github.com/sponsors/wooorm" 1003 | } 1004 | }, 1005 | "node_modules/character-entities-html4": { 1006 | "version": "2.1.0", 1007 | "dev": true, 1008 | "license": "MIT", 1009 | "funding": { 1010 | "type": "github", 1011 | "url": "https://github.com/sponsors/wooorm" 1012 | } 1013 | }, 1014 | "node_modules/character-entities-legacy": { 1015 | "version": "3.0.0", 1016 | "dev": true, 1017 | "license": "MIT", 1018 | "funding": { 1019 | "type": "github", 1020 | "url": "https://github.com/sponsors/wooorm" 1021 | } 1022 | }, 1023 | "node_modules/comma-separated-tokens": { 1024 | "version": "2.0.3", 1025 | "dev": true, 1026 | "license": "MIT", 1027 | "funding": { 1028 | "type": "github", 1029 | "url": "https://github.com/sponsors/wooorm" 1030 | } 1031 | }, 1032 | "node_modules/copy-anything": { 1033 | "version": "3.0.5", 1034 | "dev": true, 1035 | "license": "MIT", 1036 | "dependencies": { 1037 | "is-what": "^4.1.8" 1038 | }, 1039 | "engines": { 1040 | "node": ">=12.13" 1041 | }, 1042 | "funding": { 1043 | "url": "https://github.com/sponsors/mesqueeb" 1044 | } 1045 | }, 1046 | "node_modules/csstype": { 1047 | "version": "3.1.3", 1048 | "dev": true, 1049 | "license": "MIT" 1050 | }, 1051 | "node_modules/dequal": { 1052 | "version": "2.0.3", 1053 | "dev": true, 1054 | "license": "MIT", 1055 | "engines": { 1056 | "node": ">=6" 1057 | } 1058 | }, 1059 | "node_modules/devlop": { 1060 | "version": "1.1.0", 1061 | "dev": true, 1062 | "license": "MIT", 1063 | "dependencies": { 1064 | "dequal": "^2.0.0" 1065 | }, 1066 | "funding": { 1067 | "type": "github", 1068 | "url": "https://github.com/sponsors/wooorm" 1069 | } 1070 | }, 1071 | "node_modules/entities": { 1072 | "version": "4.5.0", 1073 | "dev": true, 1074 | "license": "BSD-2-Clause", 1075 | "engines": { 1076 | "node": ">=0.12" 1077 | }, 1078 | "funding": { 1079 | "url": "https://github.com/fb55/entities?sponsor=1" 1080 | } 1081 | }, 1082 | "node_modules/esbuild": { 1083 | "version": "0.21.5", 1084 | "dev": true, 1085 | "hasInstallScript": true, 1086 | "license": "MIT", 1087 | "bin": { 1088 | "esbuild": "bin/esbuild" 1089 | }, 1090 | "engines": { 1091 | "node": ">=12" 1092 | }, 1093 | "optionalDependencies": { 1094 | "@esbuild/aix-ppc64": "0.21.5", 1095 | "@esbuild/android-arm": "0.21.5", 1096 | "@esbuild/android-arm64": "0.21.5", 1097 | "@esbuild/android-x64": "0.21.5", 1098 | "@esbuild/darwin-arm64": "0.21.5", 1099 | "@esbuild/darwin-x64": "0.21.5", 1100 | "@esbuild/freebsd-arm64": "0.21.5", 1101 | "@esbuild/freebsd-x64": "0.21.5", 1102 | "@esbuild/linux-arm": "0.21.5", 1103 | "@esbuild/linux-arm64": "0.21.5", 1104 | "@esbuild/linux-ia32": "0.21.5", 1105 | "@esbuild/linux-loong64": "0.21.5", 1106 | "@esbuild/linux-mips64el": "0.21.5", 1107 | "@esbuild/linux-ppc64": "0.21.5", 1108 | "@esbuild/linux-riscv64": "0.21.5", 1109 | "@esbuild/linux-s390x": "0.21.5", 1110 | "@esbuild/linux-x64": "0.21.5", 1111 | "@esbuild/netbsd-x64": "0.21.5", 1112 | "@esbuild/openbsd-x64": "0.21.5", 1113 | "@esbuild/sunos-x64": "0.21.5", 1114 | "@esbuild/win32-arm64": "0.21.5", 1115 | "@esbuild/win32-ia32": "0.21.5", 1116 | "@esbuild/win32-x64": "0.21.5" 1117 | } 1118 | }, 1119 | "node_modules/estree-walker": { 1120 | "version": "2.0.2", 1121 | "dev": true, 1122 | "license": "MIT" 1123 | }, 1124 | "node_modules/focus-trap": { 1125 | "version": "7.6.0", 1126 | "dev": true, 1127 | "license": "MIT", 1128 | "dependencies": { 1129 | "tabbable": "^6.2.0" 1130 | } 1131 | }, 1132 | "node_modules/hast-util-to-html": { 1133 | "version": "9.0.3", 1134 | "dev": true, 1135 | "license": "MIT", 1136 | "dependencies": { 1137 | "@types/hast": "^3.0.0", 1138 | "@types/unist": "^3.0.0", 1139 | "ccount": "^2.0.0", 1140 | "comma-separated-tokens": "^2.0.0", 1141 | "hast-util-whitespace": "^3.0.0", 1142 | "html-void-elements": "^3.0.0", 1143 | "mdast-util-to-hast": "^13.0.0", 1144 | "property-information": "^6.0.0", 1145 | "space-separated-tokens": "^2.0.0", 1146 | "stringify-entities": "^4.0.0", 1147 | "zwitch": "^2.0.4" 1148 | }, 1149 | "funding": { 1150 | "type": "opencollective", 1151 | "url": "https://opencollective.com/unified" 1152 | } 1153 | }, 1154 | "node_modules/hast-util-whitespace": { 1155 | "version": "3.0.0", 1156 | "dev": true, 1157 | "license": "MIT", 1158 | "dependencies": { 1159 | "@types/hast": "^3.0.0" 1160 | }, 1161 | "funding": { 1162 | "type": "opencollective", 1163 | "url": "https://opencollective.com/unified" 1164 | } 1165 | }, 1166 | "node_modules/hookable": { 1167 | "version": "5.5.3", 1168 | "dev": true, 1169 | "license": "MIT" 1170 | }, 1171 | "node_modules/html-void-elements": { 1172 | "version": "3.0.0", 1173 | "dev": true, 1174 | "license": "MIT", 1175 | "funding": { 1176 | "type": "github", 1177 | "url": "https://github.com/sponsors/wooorm" 1178 | } 1179 | }, 1180 | "node_modules/is-what": { 1181 | "version": "4.1.16", 1182 | "dev": true, 1183 | "license": "MIT", 1184 | "engines": { 1185 | "node": ">=12.13" 1186 | }, 1187 | "funding": { 1188 | "url": "https://github.com/sponsors/mesqueeb" 1189 | } 1190 | }, 1191 | "node_modules/js-tokens": { 1192 | "version": "4.0.0", 1193 | "license": "MIT" 1194 | }, 1195 | "node_modules/loose-envify": { 1196 | "version": "1.4.0", 1197 | "license": "MIT", 1198 | "dependencies": { 1199 | "js-tokens": "^3.0.0 || ^4.0.0" 1200 | }, 1201 | "bin": { 1202 | "loose-envify": "cli.js" 1203 | } 1204 | }, 1205 | "node_modules/magic-string": { 1206 | "version": "0.30.12", 1207 | "dev": true, 1208 | "license": "MIT", 1209 | "dependencies": { 1210 | "@jridgewell/sourcemap-codec": "^1.5.0" 1211 | } 1212 | }, 1213 | "node_modules/mark.js": { 1214 | "version": "8.11.1", 1215 | "dev": true, 1216 | "license": "MIT" 1217 | }, 1218 | "node_modules/mdast-util-to-hast": { 1219 | "version": "13.2.0", 1220 | "dev": true, 1221 | "license": "MIT", 1222 | "dependencies": { 1223 | "@types/hast": "^3.0.0", 1224 | "@types/mdast": "^4.0.0", 1225 | "@ungap/structured-clone": "^1.0.0", 1226 | "devlop": "^1.0.0", 1227 | "micromark-util-sanitize-uri": "^2.0.0", 1228 | "trim-lines": "^3.0.0", 1229 | "unist-util-position": "^5.0.0", 1230 | "unist-util-visit": "^5.0.0", 1231 | "vfile": "^6.0.0" 1232 | }, 1233 | "funding": { 1234 | "type": "opencollective", 1235 | "url": "https://opencollective.com/unified" 1236 | } 1237 | }, 1238 | "node_modules/micromark-util-character": { 1239 | "version": "2.1.0", 1240 | "dev": true, 1241 | "funding": [ 1242 | { 1243 | "type": "GitHub Sponsors", 1244 | "url": "https://github.com/sponsors/unifiedjs" 1245 | }, 1246 | { 1247 | "type": "OpenCollective", 1248 | "url": "https://opencollective.com/unified" 1249 | } 1250 | ], 1251 | "license": "MIT", 1252 | "dependencies": { 1253 | "micromark-util-symbol": "^2.0.0", 1254 | "micromark-util-types": "^2.0.0" 1255 | } 1256 | }, 1257 | "node_modules/micromark-util-encode": { 1258 | "version": "2.0.0", 1259 | "dev": true, 1260 | "funding": [ 1261 | { 1262 | "type": "GitHub Sponsors", 1263 | "url": "https://github.com/sponsors/unifiedjs" 1264 | }, 1265 | { 1266 | "type": "OpenCollective", 1267 | "url": "https://opencollective.com/unified" 1268 | } 1269 | ], 1270 | "license": "MIT" 1271 | }, 1272 | "node_modules/micromark-util-sanitize-uri": { 1273 | "version": "2.0.0", 1274 | "dev": true, 1275 | "funding": [ 1276 | { 1277 | "type": "GitHub Sponsors", 1278 | "url": "https://github.com/sponsors/unifiedjs" 1279 | }, 1280 | { 1281 | "type": "OpenCollective", 1282 | "url": "https://opencollective.com/unified" 1283 | } 1284 | ], 1285 | "license": "MIT", 1286 | "dependencies": { 1287 | "micromark-util-character": "^2.0.0", 1288 | "micromark-util-encode": "^2.0.0", 1289 | "micromark-util-symbol": "^2.0.0" 1290 | } 1291 | }, 1292 | "node_modules/micromark-util-symbol": { 1293 | "version": "2.0.0", 1294 | "dev": true, 1295 | "funding": [ 1296 | { 1297 | "type": "GitHub Sponsors", 1298 | "url": "https://github.com/sponsors/unifiedjs" 1299 | }, 1300 | { 1301 | "type": "OpenCollective", 1302 | "url": "https://opencollective.com/unified" 1303 | } 1304 | ], 1305 | "license": "MIT" 1306 | }, 1307 | "node_modules/micromark-util-types": { 1308 | "version": "2.0.0", 1309 | "dev": true, 1310 | "funding": [ 1311 | { 1312 | "type": "GitHub Sponsors", 1313 | "url": "https://github.com/sponsors/unifiedjs" 1314 | }, 1315 | { 1316 | "type": "OpenCollective", 1317 | "url": "https://opencollective.com/unified" 1318 | } 1319 | ], 1320 | "license": "MIT" 1321 | }, 1322 | "node_modules/minisearch": { 1323 | "version": "7.1.0", 1324 | "dev": true, 1325 | "license": "MIT" 1326 | }, 1327 | "node_modules/mitt": { 1328 | "version": "3.0.1", 1329 | "dev": true, 1330 | "license": "MIT" 1331 | }, 1332 | "node_modules/nanoid": { 1333 | "version": "3.3.7", 1334 | "dev": true, 1335 | "funding": [ 1336 | { 1337 | "type": "github", 1338 | "url": "https://github.com/sponsors/ai" 1339 | } 1340 | ], 1341 | "license": "MIT", 1342 | "bin": { 1343 | "nanoid": "bin/nanoid.cjs" 1344 | }, 1345 | "engines": { 1346 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1347 | } 1348 | }, 1349 | "node_modules/oniguruma-to-js": { 1350 | "version": "0.4.3", 1351 | "dev": true, 1352 | "license": "MIT", 1353 | "dependencies": { 1354 | "regex": "^4.3.2" 1355 | }, 1356 | "funding": { 1357 | "url": "https://github.com/sponsors/antfu" 1358 | } 1359 | }, 1360 | "node_modules/perfect-debounce": { 1361 | "version": "1.0.0", 1362 | "dev": true, 1363 | "license": "MIT" 1364 | }, 1365 | "node_modules/picocolors": { 1366 | "version": "1.1.0", 1367 | "dev": true, 1368 | "license": "ISC" 1369 | }, 1370 | "node_modules/postcss": { 1371 | "version": "8.4.47", 1372 | "dev": true, 1373 | "funding": [ 1374 | { 1375 | "type": "opencollective", 1376 | "url": "https://opencollective.com/postcss/" 1377 | }, 1378 | { 1379 | "type": "tidelift", 1380 | "url": "https://tidelift.com/funding/github/npm/postcss" 1381 | }, 1382 | { 1383 | "type": "github", 1384 | "url": "https://github.com/sponsors/ai" 1385 | } 1386 | ], 1387 | "license": "MIT", 1388 | "dependencies": { 1389 | "nanoid": "^3.3.7", 1390 | "picocolors": "^1.1.0", 1391 | "source-map-js": "^1.2.1" 1392 | }, 1393 | "engines": { 1394 | "node": "^10 || ^12 || >=14" 1395 | } 1396 | }, 1397 | "node_modules/preact": { 1398 | "version": "10.24.3", 1399 | "dev": true, 1400 | "license": "MIT", 1401 | "funding": { 1402 | "type": "opencollective", 1403 | "url": "https://opencollective.com/preact" 1404 | } 1405 | }, 1406 | "node_modules/property-information": { 1407 | "version": "6.5.0", 1408 | "dev": true, 1409 | "license": "MIT", 1410 | "funding": { 1411 | "type": "github", 1412 | "url": "https://github.com/sponsors/wooorm" 1413 | } 1414 | }, 1415 | "node_modules/react": { 1416 | "version": "18.3.1", 1417 | "license": "MIT", 1418 | "dependencies": { 1419 | "loose-envify": "^1.1.0" 1420 | }, 1421 | "engines": { 1422 | "node": ">=0.10.0" 1423 | } 1424 | }, 1425 | "node_modules/react-audio-play": { 1426 | "version": "1.0.4", 1427 | "resolved": "https://registry.npmjs.org/react-audio-play/-/react-audio-play-1.0.4.tgz", 1428 | "integrity": "sha512-2xOOQb0VDEqac6fvjdNrbYH4LjJoCxfzDh6cryCTdJusDJ1Th92Wg4oF6QdO0ZR/YbSlmns3T4PnM94KU2QiFw==", 1429 | "peerDependencies": { 1430 | "react": ">=17" 1431 | } 1432 | }, 1433 | "node_modules/react-dom": { 1434 | "version": "18.3.1", 1435 | "license": "MIT", 1436 | "dependencies": { 1437 | "loose-envify": "^1.1.0", 1438 | "scheduler": "^0.23.2" 1439 | }, 1440 | "peerDependencies": { 1441 | "react": "^18.3.1" 1442 | } 1443 | }, 1444 | "node_modules/regex": { 1445 | "version": "4.3.3", 1446 | "dev": true, 1447 | "license": "MIT" 1448 | }, 1449 | "node_modules/rfdc": { 1450 | "version": "1.4.1", 1451 | "dev": true, 1452 | "license": "MIT" 1453 | }, 1454 | "node_modules/rollup": { 1455 | "version": "4.24.0", 1456 | "dev": true, 1457 | "license": "MIT", 1458 | "dependencies": { 1459 | "@types/estree": "1.0.6" 1460 | }, 1461 | "bin": { 1462 | "rollup": "dist/bin/rollup" 1463 | }, 1464 | "engines": { 1465 | "node": ">=18.0.0", 1466 | "npm": ">=8.0.0" 1467 | }, 1468 | "optionalDependencies": { 1469 | "@rollup/rollup-android-arm-eabi": "4.24.0", 1470 | "@rollup/rollup-android-arm64": "4.24.0", 1471 | "@rollup/rollup-darwin-arm64": "4.24.0", 1472 | "@rollup/rollup-darwin-x64": "4.24.0", 1473 | "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", 1474 | "@rollup/rollup-linux-arm-musleabihf": "4.24.0", 1475 | "@rollup/rollup-linux-arm64-gnu": "4.24.0", 1476 | "@rollup/rollup-linux-arm64-musl": "4.24.0", 1477 | "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", 1478 | "@rollup/rollup-linux-riscv64-gnu": "4.24.0", 1479 | "@rollup/rollup-linux-s390x-gnu": "4.24.0", 1480 | "@rollup/rollup-linux-x64-gnu": "4.24.0", 1481 | "@rollup/rollup-linux-x64-musl": "4.24.0", 1482 | "@rollup/rollup-win32-arm64-msvc": "4.24.0", 1483 | "@rollup/rollup-win32-ia32-msvc": "4.24.0", 1484 | "@rollup/rollup-win32-x64-msvc": "4.24.0", 1485 | "fsevents": "~2.3.2" 1486 | } 1487 | }, 1488 | "node_modules/scheduler": { 1489 | "version": "0.23.2", 1490 | "license": "MIT", 1491 | "dependencies": { 1492 | "loose-envify": "^1.1.0" 1493 | } 1494 | }, 1495 | "node_modules/search-insights": { 1496 | "version": "2.17.2", 1497 | "dev": true, 1498 | "license": "MIT", 1499 | "peer": true 1500 | }, 1501 | "node_modules/shiki": { 1502 | "version": "1.22.0", 1503 | "dev": true, 1504 | "license": "MIT", 1505 | "dependencies": { 1506 | "@shikijs/core": "1.22.0", 1507 | "@shikijs/engine-javascript": "1.22.0", 1508 | "@shikijs/engine-oniguruma": "1.22.0", 1509 | "@shikijs/types": "1.22.0", 1510 | "@shikijs/vscode-textmate": "^9.3.0", 1511 | "@types/hast": "^3.0.4" 1512 | } 1513 | }, 1514 | "node_modules/source-map-js": { 1515 | "version": "1.2.1", 1516 | "dev": true, 1517 | "license": "BSD-3-Clause", 1518 | "engines": { 1519 | "node": ">=0.10.0" 1520 | } 1521 | }, 1522 | "node_modules/space-separated-tokens": { 1523 | "version": "2.0.2", 1524 | "dev": true, 1525 | "license": "MIT", 1526 | "funding": { 1527 | "type": "github", 1528 | "url": "https://github.com/sponsors/wooorm" 1529 | } 1530 | }, 1531 | "node_modules/speakingurl": { 1532 | "version": "14.0.1", 1533 | "dev": true, 1534 | "license": "BSD-3-Clause", 1535 | "engines": { 1536 | "node": ">=0.10.0" 1537 | } 1538 | }, 1539 | "node_modules/stringify-entities": { 1540 | "version": "4.0.4", 1541 | "dev": true, 1542 | "license": "MIT", 1543 | "dependencies": { 1544 | "character-entities-html4": "^2.0.0", 1545 | "character-entities-legacy": "^3.0.0" 1546 | }, 1547 | "funding": { 1548 | "type": "github", 1549 | "url": "https://github.com/sponsors/wooorm" 1550 | } 1551 | }, 1552 | "node_modules/superjson": { 1553 | "version": "2.2.1", 1554 | "dev": true, 1555 | "license": "MIT", 1556 | "dependencies": { 1557 | "copy-anything": "^3.0.2" 1558 | }, 1559 | "engines": { 1560 | "node": ">=16" 1561 | } 1562 | }, 1563 | "node_modules/tabbable": { 1564 | "version": "6.2.0", 1565 | "dev": true, 1566 | "license": "MIT" 1567 | }, 1568 | "node_modules/to-fast-properties": { 1569 | "version": "2.0.0", 1570 | "dev": true, 1571 | "license": "MIT", 1572 | "engines": { 1573 | "node": ">=4" 1574 | } 1575 | }, 1576 | "node_modules/trim-lines": { 1577 | "version": "3.0.1", 1578 | "dev": true, 1579 | "license": "MIT", 1580 | "funding": { 1581 | "type": "github", 1582 | "url": "https://github.com/sponsors/wooorm" 1583 | } 1584 | }, 1585 | "node_modules/undici-types": { 1586 | "version": "6.19.8", 1587 | "dev": true, 1588 | "license": "MIT" 1589 | }, 1590 | "node_modules/unist-util-is": { 1591 | "version": "6.0.0", 1592 | "dev": true, 1593 | "license": "MIT", 1594 | "dependencies": { 1595 | "@types/unist": "^3.0.0" 1596 | }, 1597 | "funding": { 1598 | "type": "opencollective", 1599 | "url": "https://opencollective.com/unified" 1600 | } 1601 | }, 1602 | "node_modules/unist-util-position": { 1603 | "version": "5.0.0", 1604 | "dev": true, 1605 | "license": "MIT", 1606 | "dependencies": { 1607 | "@types/unist": "^3.0.0" 1608 | }, 1609 | "funding": { 1610 | "type": "opencollective", 1611 | "url": "https://opencollective.com/unified" 1612 | } 1613 | }, 1614 | "node_modules/unist-util-stringify-position": { 1615 | "version": "4.0.0", 1616 | "dev": true, 1617 | "license": "MIT", 1618 | "dependencies": { 1619 | "@types/unist": "^3.0.0" 1620 | }, 1621 | "funding": { 1622 | "type": "opencollective", 1623 | "url": "https://opencollective.com/unified" 1624 | } 1625 | }, 1626 | "node_modules/unist-util-visit": { 1627 | "version": "5.0.0", 1628 | "dev": true, 1629 | "license": "MIT", 1630 | "dependencies": { 1631 | "@types/unist": "^3.0.0", 1632 | "unist-util-is": "^6.0.0", 1633 | "unist-util-visit-parents": "^6.0.0" 1634 | }, 1635 | "funding": { 1636 | "type": "opencollective", 1637 | "url": "https://opencollective.com/unified" 1638 | } 1639 | }, 1640 | "node_modules/unist-util-visit-parents": { 1641 | "version": "6.0.1", 1642 | "dev": true, 1643 | "license": "MIT", 1644 | "dependencies": { 1645 | "@types/unist": "^3.0.0", 1646 | "unist-util-is": "^6.0.0" 1647 | }, 1648 | "funding": { 1649 | "type": "opencollective", 1650 | "url": "https://opencollective.com/unified" 1651 | } 1652 | }, 1653 | "node_modules/vfile": { 1654 | "version": "6.0.3", 1655 | "dev": true, 1656 | "license": "MIT", 1657 | "dependencies": { 1658 | "@types/unist": "^3.0.0", 1659 | "vfile-message": "^4.0.0" 1660 | }, 1661 | "funding": { 1662 | "type": "opencollective", 1663 | "url": "https://opencollective.com/unified" 1664 | } 1665 | }, 1666 | "node_modules/vfile-message": { 1667 | "version": "4.0.2", 1668 | "dev": true, 1669 | "license": "MIT", 1670 | "dependencies": { 1671 | "@types/unist": "^3.0.0", 1672 | "unist-util-stringify-position": "^4.0.0" 1673 | }, 1674 | "funding": { 1675 | "type": "opencollective", 1676 | "url": "https://opencollective.com/unified" 1677 | } 1678 | }, 1679 | "node_modules/vite": { 1680 | "version": "5.4.9", 1681 | "dev": true, 1682 | "license": "MIT", 1683 | "dependencies": { 1684 | "esbuild": "^0.21.3", 1685 | "postcss": "^8.4.43", 1686 | "rollup": "^4.20.0" 1687 | }, 1688 | "bin": { 1689 | "vite": "bin/vite.js" 1690 | }, 1691 | "engines": { 1692 | "node": "^18.0.0 || >=20.0.0" 1693 | }, 1694 | "funding": { 1695 | "url": "https://github.com/vitejs/vite?sponsor=1" 1696 | }, 1697 | "optionalDependencies": { 1698 | "fsevents": "~2.3.3" 1699 | }, 1700 | "peerDependencies": { 1701 | "@types/node": "^18.0.0 || >=20.0.0", 1702 | "less": "*", 1703 | "lightningcss": "^1.21.0", 1704 | "sass": "*", 1705 | "sass-embedded": "*", 1706 | "stylus": "*", 1707 | "sugarss": "*", 1708 | "terser": "^5.4.0" 1709 | }, 1710 | "peerDependenciesMeta": { 1711 | "@types/node": { 1712 | "optional": true 1713 | }, 1714 | "less": { 1715 | "optional": true 1716 | }, 1717 | "lightningcss": { 1718 | "optional": true 1719 | }, 1720 | "sass": { 1721 | "optional": true 1722 | }, 1723 | "sass-embedded": { 1724 | "optional": true 1725 | }, 1726 | "stylus": { 1727 | "optional": true 1728 | }, 1729 | "sugarss": { 1730 | "optional": true 1731 | }, 1732 | "terser": { 1733 | "optional": true 1734 | } 1735 | } 1736 | }, 1737 | "node_modules/vitepress": { 1738 | "version": "1.4.1", 1739 | "dev": true, 1740 | "license": "MIT", 1741 | "dependencies": { 1742 | "@docsearch/css": "^3.6.2", 1743 | "@docsearch/js": "^3.6.2", 1744 | "@shikijs/core": "^1.22.0", 1745 | "@shikijs/transformers": "^1.22.0", 1746 | "@shikijs/types": "^1.22.0", 1747 | "@types/markdown-it": "^14.1.2", 1748 | "@vitejs/plugin-vue": "^5.1.4", 1749 | "@vue/devtools-api": "^7.4.6", 1750 | "@vue/shared": "^3.5.12", 1751 | "@vueuse/core": "^11.1.0", 1752 | "@vueuse/integrations": "^11.1.0", 1753 | "focus-trap": "^7.6.0", 1754 | "mark.js": "8.11.1", 1755 | "minisearch": "^7.1.0", 1756 | "shiki": "^1.22.0", 1757 | "vite": "^5.4.8", 1758 | "vue": "^3.5.12" 1759 | }, 1760 | "bin": { 1761 | "vitepress": "bin/vitepress.js" 1762 | }, 1763 | "peerDependencies": { 1764 | "markdown-it-mathjax3": "^4", 1765 | "postcss": "^8" 1766 | }, 1767 | "peerDependenciesMeta": { 1768 | "markdown-it-mathjax3": { 1769 | "optional": true 1770 | }, 1771 | "postcss": { 1772 | "optional": true 1773 | } 1774 | } 1775 | }, 1776 | "node_modules/vue": { 1777 | "version": "3.5.12", 1778 | "dev": true, 1779 | "license": "MIT", 1780 | "dependencies": { 1781 | "@vue/compiler-dom": "3.5.12", 1782 | "@vue/compiler-sfc": "3.5.12", 1783 | "@vue/runtime-dom": "3.5.12", 1784 | "@vue/server-renderer": "3.5.12", 1785 | "@vue/shared": "3.5.12" 1786 | }, 1787 | "peerDependencies": { 1788 | "typescript": "*" 1789 | }, 1790 | "peerDependenciesMeta": { 1791 | "typescript": { 1792 | "optional": true 1793 | } 1794 | } 1795 | }, 1796 | "node_modules/zwitch": { 1797 | "version": "2.0.4", 1798 | "dev": true, 1799 | "license": "MIT", 1800 | "funding": { 1801 | "type": "github", 1802 | "url": "https://github.com/sponsors/wooorm" 1803 | } 1804 | } 1805 | } 1806 | } 1807 | -------------------------------------------------------------------------------- /documentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:dev": "vitepress dev docs", 4 | "docs:build": "vitepress build docs", 5 | "docs:serve": "vitepress serve docs" 6 | }, 7 | "type": "module", 8 | "devDependencies": { 9 | "@types/express": "^4.17.21", 10 | "vitepress": "^1.3.4" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-audio-play": "^1.0.4", 15 | "react-dom": "^18.3.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.(t|j)sx?$": "ts-jest" 4 | }, 5 | "testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 6 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 7 | "testEnvironment": "jsdom", 8 | "moduleNameMapper": { 9 | "^.+\\.(css|less)$": "/config/CSSStub.ts" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-audio-play", 3 | "version": "1.0.4", 4 | "description": "React audio player component", 5 | "homepage": "https://riyaddecoder.github.io/react-audio-play/", 6 | "main": "dist/cjs/index.js", 7 | "module": "dist/esm/index.js", 8 | "types": "dist/types.d.ts", 9 | "files": [ 10 | "dist", 11 | "LICENSE", 12 | "README.md" 13 | ], 14 | "scripts": { 15 | "prepare": "npm run build", 16 | "prepublishOnly": "npm test && npm run prettier && npm run lint", 17 | "build": "rollup -c --bundleConfigAsCjs", 18 | "lint": "eslint \"{**/*,*}.{js,ts,jsx,tsx}\"", 19 | "prettier": "prettier --write \"{src,tests,example/src}/**/*.{js,ts,jsx,tsx}\"", 20 | "test": "jest --config jestconfig.json" 21 | }, 22 | "keywords": [ 23 | "best audio player for react", 24 | "simple audio player", 25 | "audio player for react", 26 | "react audio player", 27 | "react-audio", 28 | "react-audio-player", 29 | "audio-player", 30 | "typescripts" 31 | ], 32 | "author": "Shahidul Alam Riyad (riyaddecoder)", 33 | "license": "MIT", 34 | "devDependencies": { 35 | "@rollup/plugin-commonjs": "^25.0.3", 36 | "@rollup/plugin-image": "^3.0.2", 37 | "@rollup/plugin-node-resolve": "^15.1.0", 38 | "@rollup/plugin-terser": "^0.4.3", 39 | "@rollup/plugin-typescript": "^11.1.2", 40 | "@testing-library/react": "^14.0.0", 41 | "@types/jest": "^29.5.3", 42 | "@types/react": "^18.0.14", 43 | "@typescript-eslint/eslint-plugin": "^6.1.0", 44 | "@typescript-eslint/parser": "^6.1.0", 45 | "eslint": "^8.45.0", 46 | "eslint-config-prettier": "^8.8.0", 47 | "eslint-plugin-prettier": "^5.0.0", 48 | "eslint-plugin-react": "^7.33.0", 49 | "eslint-plugin-react-hooks": "^4.6.0", 50 | "jest": "^29.6.1", 51 | "jest-canvas-mock": "^2.5.2", 52 | "jest-environment-jsdom": "^29.6.1", 53 | "prettier": "^3.0.0", 54 | "react": "^18.2.0", 55 | "react-dom": "^18.2.0", 56 | "rollup": "^3.27.2", 57 | "rollup-plugin-dts": "^5.3.0", 58 | "rollup-plugin-peer-deps-external": "^2.2.4", 59 | "rollup-plugin-styles": "^4.0.0", 60 | "ts-jest": "^29.1.1", 61 | "tslib": "^2.6.0", 62 | "typescript": "^5.1.6" 63 | }, 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/riyaddecoder/react-audio-play" 67 | }, 68 | "peerDependencies": { 69 | "react": ">=17" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import dts from 'rollup-plugin-dts'; 2 | import terser from '@rollup/plugin-terser'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import resolve from '@rollup/plugin-node-resolve'; 5 | import typescript from '@rollup/plugin-typescript'; 6 | import peerDepsExternal from 'rollup-plugin-peer-deps-external'; 7 | import styles from 'rollup-plugin-styles'; 8 | import image from '@rollup/plugin-image'; 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-var-requires 11 | const packageJson = require('./package.json'); 12 | 13 | export default [ 14 | { 15 | input: 'src/index.ts', 16 | output: [ 17 | { 18 | file: packageJson.main, 19 | format: 'cjs', 20 | sourcemap: true 21 | }, 22 | { 23 | file: packageJson.module, 24 | format: 'esm', 25 | sourcemap: true 26 | } 27 | ], 28 | plugins: [peerDepsExternal(), resolve(), styles(), commonjs(), typescript({ tsconfig: './tsconfig.json' }), terser(), image()], 29 | external: ['react', 'react-dom', 'styled-components'] 30 | }, 31 | { 32 | input: 'src/index.ts', 33 | output: [{ file: 'dist/types.d.ts', format: 'es' }], 34 | plugins: [dts.default(), styles()] 35 | } 36 | ]; 37 | -------------------------------------------------------------------------------- /src/assets/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riyaddecoder/react-audio-play/8353163064a1d6bcce4f44c791913a81462620c6/src/assets/loading.png -------------------------------------------------------------------------------- /src/components/AudioPlayer.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; 2 | import { AudioInterface, AudioPlayerRef } from './core.interface'; 3 | import { iconPaths } from '../helpers/icons/icons'; 4 | import { formatTime } from '../helpers/utils/formatTime'; 5 | import { getRangeBox } from '../helpers/utils/getRangeBox'; 6 | import getDeviceEventNames from '../helpers/utils/getDeviceEventNames'; 7 | import './audioPlay.css'; 8 | 9 | export const AudioPlayer = forwardRef( 10 | ( 11 | { 12 | autoPlay = false, 13 | className = '', 14 | src, 15 | loop = false, 16 | preload = 'auto', 17 | backgroundColor, 18 | color, 19 | width, 20 | style, 21 | sliderColor, 22 | volume = 100, 23 | volumePlacement = 'top', 24 | hasKeyBindings = true, 25 | onPlay, 26 | onPause, 27 | onEnd, 28 | onError 29 | }, 30 | ref 31 | ) => { 32 | const wrapperRef = useRef(null); 33 | const audioRef = useRef(null); 34 | const currentlyDragged = useRef(null); 35 | const rewindPin = useRef(null); 36 | const volumePin = useRef(null); 37 | const [canPlay, setCanPlay] = useState(preload === 'none'); 38 | const [isPlaying, setIsPlaying] = useState(false); 39 | const [progressBarPercent, setProgressBarPercent] = useState(0); 40 | const [currentTime, setCurrentTime] = useState('0:00'); 41 | const [totalTime, setTotalTime] = useState('--:--'); 42 | const [volumeOpen, setVolumeOpen] = useState(false); 43 | const [volumeProgress, setVolumeProgress] = useState(100); 44 | const [speakerIcon, setSpeakerIcon] = useState(getVolumePath(volume)); 45 | const [coefficient, setCoefficient] = useState(0); 46 | const [hasError, setHasError] = useState(false); 47 | 48 | useEffect(() => { 49 | handleReload(); 50 | // eslint-disable-next-line react-hooks/exhaustive-deps 51 | }, [src]); 52 | 53 | useEffect(() => { 54 | if (audioRef.current?.duration && audioRef.current.duration !== Infinity) { 55 | setTotalTime(formatTime(audioRef.current.duration)); 56 | } 57 | }, [audioRef.current?.duration]); 58 | 59 | useEffect(() => { 60 | if (!isNaN(volume)) { 61 | const tempVol = volume > 100 ? 100 : volume < 0 ? 0 : volume; 62 | setVolumeProgress(tempVol); 63 | if (audioRef.current) { 64 | audioRef.current.volume = tempVol / 100; 65 | } 66 | } 67 | }, [volume]); 68 | 69 | useImperativeHandle(ref, () => ({ 70 | play: () => { 71 | play(); 72 | }, 73 | pause: () => { 74 | pause(); 75 | }, 76 | stop: () => { 77 | stop(); 78 | }, 79 | focus: () => { 80 | focus(); 81 | } 82 | })); 83 | 84 | const getTotalDuration = () => { 85 | if (!audioRef.current) { 86 | return 0; 87 | } 88 | return audioRef.current.duration !== Infinity ? audioRef.current.duration : audioRef.current.buffered.end(0); 89 | }; 90 | 91 | const handleCanPlay = () => { 92 | setCanPlay(true); 93 | }; 94 | 95 | const handleReload = () => { 96 | if (audioRef.current) { 97 | setIsPlaying(false); 98 | setTotalTime('--:--'); 99 | setCanPlay(false); 100 | setHasError(false); 101 | audioRef.current.src = src; 102 | audioRef.current.load(); 103 | } 104 | }; 105 | 106 | const handleOnError = (event: React.SyntheticEvent) => { 107 | setCanPlay(true); 108 | setHasError(true); 109 | if (onError) { 110 | const mediaError = (event.target as HTMLAudioElement).error; 111 | let errorMessage = 'An unknown error occurred.'; 112 | 113 | if (mediaError?.code) { 114 | switch (mediaError?.code) { 115 | case mediaError.MEDIA_ERR_ABORTED: 116 | errorMessage = 'The media playback was aborted.'; 117 | break; 118 | case mediaError.MEDIA_ERR_NETWORK: 119 | errorMessage = 'A network error caused the media to fail.'; 120 | break; 121 | case mediaError.MEDIA_ERR_DECODE: 122 | errorMessage = 'The media playback was aborted due to a decoding error.'; 123 | break; 124 | case mediaError.MEDIA_ERR_SRC_NOT_SUPPORTED: 125 | errorMessage = 'The media source format is not supported.'; 126 | break; 127 | default: 128 | errorMessage = 'An unknown error occurred.'; 129 | break; 130 | } 131 | } 132 | 133 | onError(event, errorMessage); 134 | } 135 | }; 136 | 137 | const handleEnded = () => { 138 | setIsPlaying(false); 139 | if (audioRef.current) { 140 | audioRef.current.currentTime = 0; 141 | if (totalTime === '--:--') { 142 | handleReload(); 143 | } 144 | if (onEnd) { 145 | onEnd(); 146 | } 147 | } 148 | }; 149 | 150 | const handleUpdateVolume = () => { 151 | if (audioRef.current) { 152 | setVolumeProgress(audioRef.current.volume * 100); 153 | if (audioRef.current.volume >= 0.5) { 154 | setSpeakerIcon(iconPaths.fullVolume); 155 | } else if (audioRef.current.volume < 0.5 && audioRef.current.volume > 0.05) { 156 | setSpeakerIcon(iconPaths.midVolume); 157 | } else if (audioRef.current.volume <= 0.05) { 158 | setSpeakerIcon(iconPaths.lowVolume); 159 | } 160 | } 161 | }; 162 | 163 | const handleUpdateProgress = () => { 164 | if (audioRef.current) { 165 | const current = audioRef.current.currentTime; 166 | const percent = (current / getTotalDuration()) * 100; 167 | setProgressBarPercent(percent); 168 | setCurrentTime(formatTime(current)); 169 | } 170 | }; 171 | 172 | const handleLoadedMetaData = () => { 173 | if (audioRef.current?.duration && audioRef.current?.duration !== Infinity) { 174 | setTotalTime(formatTime(audioRef.current.duration ?? 0)); 175 | const currentTime = audioRef.current.duration * coefficient; 176 | audioRef.current.currentTime = currentTime; 177 | } 178 | }; 179 | 180 | function getVolumePath(volumeLevel: number) { 181 | const MIN_VOLUME = 0; 182 | const MAX_VOLUME = 100; 183 | 184 | volumeLevel = isNaN(volumeLevel) ? 100 : Math.max(MIN_VOLUME, Math.min(volumeLevel, MAX_VOLUME)); 185 | 186 | if (volumeLevel >= 50) { 187 | return iconPaths.fullVolume; 188 | } else if (volumeLevel > 5) { 189 | return iconPaths.midVolume; 190 | } 191 | 192 | return iconPaths.lowVolume; 193 | } 194 | 195 | const togglePlay = () => { 196 | if (audioRef.current) { 197 | if (preload === 'none' && !audioRef.current.duration) { 198 | setCanPlay(false); 199 | } 200 | 201 | if (audioRef.current.paused) { 202 | play(); 203 | } else { 204 | pause(); 205 | } 206 | } 207 | }; 208 | 209 | const play = () => { 210 | if (hasError) { 211 | handleReload(); 212 | } else { 213 | audioRef.current?.play(); 214 | setIsPlaying(true); 215 | if (onPlay) { 216 | onPlay(); 217 | } 218 | } 219 | }; 220 | 221 | const pause = () => { 222 | audioRef.current?.pause(); 223 | setIsPlaying(false); 224 | if (onPause) { 225 | onPause(); 226 | } 227 | }; 228 | 229 | const stop = () => { 230 | if (audioRef.current) { 231 | audioRef.current.pause(); 232 | setIsPlaying(false); 233 | audioRef.current.currentTime = 0; 234 | } 235 | }; 236 | 237 | const focus = () => { 238 | wrapperRef.current?.focus(); 239 | }; 240 | 241 | const inRange = (event: MouseEvent | TouchEvent | React.MouseEvent) => { 242 | const rangeBox = getRangeBox(event, currentlyDragged.current); 243 | const rect = rangeBox.getBoundingClientRect(); 244 | const direction = rangeBox.dataset.direction; 245 | if (direction === 'horizontal') { 246 | const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX; 247 | if (clientX - rect.left < 0 || clientX - rect.right > 0) return false; 248 | } else { 249 | const min = rect.top; 250 | const max = min + rangeBox.offsetHeight; 251 | const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY; 252 | if (clientY < min || clientY > max) return false; 253 | } 254 | return true; 255 | }; 256 | 257 | function getCoefficient(event: MouseEvent | TouchEvent | React.MouseEvent) { 258 | const slider = getRangeBox(event, currentlyDragged.current); 259 | const rect = slider.getBoundingClientRect(); 260 | let K = 0; 261 | 262 | if (slider.dataset.direction === 'horizontal') { 263 | const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX; 264 | const offsetX = clientX - rect.left; 265 | const width = slider.clientWidth; 266 | K = offsetX / width; 267 | } else if (slider.dataset.direction === 'vertical') { 268 | const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY; 269 | const height = slider.clientHeight; 270 | const offsetY = clientY - rect.top; 271 | K = 1 - offsetY / height; 272 | } 273 | 274 | return K; 275 | } 276 | 277 | const rewind = (event: MouseEvent | TouchEvent | React.MouseEvent) => { 278 | if (inRange(event) && audioRef.current) { 279 | if (preload === 'none' && !audioRef.current.duration) { 280 | setCanPlay(false); 281 | audioRef.current.load(); 282 | setCoefficient(getCoefficient(event)); 283 | } else if (audioRef.current.duration) { 284 | audioRef.current.currentTime = getTotalDuration() * getCoefficient(event); 285 | } 286 | } 287 | }; 288 | 289 | const changeVolume = (event: MouseEvent | TouchEvent | React.MouseEvent) => { 290 | if (inRange(event) && audioRef.current) { 291 | audioRef.current.volume = getCoefficient(event); 292 | } 293 | }; 294 | 295 | const handleRewindDragging = () => { 296 | currentlyDragged.current = rewindPin.current; 297 | const events = getDeviceEventNames(); 298 | window.addEventListener(events.move, rewind, false); 299 | 300 | window.addEventListener( 301 | events.up, 302 | () => { 303 | currentlyDragged.current = null; 304 | window.removeEventListener(events.move, rewind, false); 305 | }, 306 | { once: true } 307 | ); 308 | }; 309 | 310 | const handleVolumeDragging = () => { 311 | currentlyDragged.current = volumePin.current; 312 | const events = getDeviceEventNames(); 313 | 314 | window.addEventListener(events.move, changeVolume, false); 315 | 316 | window.addEventListener( 317 | events.up, 318 | () => { 319 | currentlyDragged.current = null; 320 | window.removeEventListener(events.move, changeVolume, false); 321 | }, 322 | false 323 | ); 324 | }; 325 | 326 | const adjustAudioTime = (percentage: number) => { 327 | if (audioRef.current) { 328 | const currentTime = audioRef.current.currentTime + getTotalDuration() * (percentage / 100); 329 | audioRef.current.currentTime = Math.min(currentTime, getTotalDuration()); 330 | } 331 | }; 332 | 333 | const adjustVolume = (delta: number) => { 334 | if (audioRef.current) { 335 | audioRef.current.volume = Math.max(0, Math.min(1, audioRef.current.volume + delta)); 336 | } 337 | }; 338 | 339 | const handleKeyPress = (event: React.KeyboardEvent) => { 340 | if (!hasKeyBindings) { 341 | return; 342 | } 343 | event.preventDefault(); 344 | switch (event.key) { 345 | case 'ArrowLeft': 346 | adjustAudioTime(-5); 347 | break; 348 | case 'ArrowRight': 349 | adjustAudioTime(5); 350 | break; 351 | case 'ArrowUp': 352 | adjustVolume(0.05); 353 | break; 354 | case 'ArrowDown': 355 | adjustVolume(-0.05); 356 | break; 357 | case ' ': 358 | togglePlay(); 359 | break; 360 | default: 361 | //Nothing to do 362 | break; 363 | } 364 | }; 365 | 366 | const handleOnPlay = () => { 367 | setIsPlaying(true); 368 | }; 369 | 370 | return ( 371 |
383 | {hasError && ( 384 | 385 | 386 | 390 | 391 | 392 | )} 393 | {!canPlay && !hasError && ( 394 |
395 |
396 |
397 | )} 398 | {canPlay && !hasError && ( 399 |
togglePlay()}> 400 | 401 | 402 | 403 |
404 | )} 405 | 406 |
407 | {currentTime} 408 |
409 |
416 |
424 |
425 |
426 | {totalTime !== '--:--' && {totalTime}} 427 |
428 | 429 |
430 |
setVolumeOpen((vol) => !vol)}> 431 | 432 | 433 | 434 |
435 |
436 |
{ 439 | e.preventDefault(); 440 | e.stopPropagation(); 441 | }} 442 | > 443 |
444 |
451 |
459 |
460 |
461 |
462 |
setVolumeOpen(false)}>
463 |
464 |
465 | 466 | 481 |
482 | ); 483 | } 484 | ); 485 | 486 | AudioPlayer.displayName = 'AudioPlayer'; 487 | -------------------------------------------------------------------------------- /src/components/audioPlay.css: -------------------------------------------------------------------------------- 1 | .rap-container { 2 | max-width: 400px; 3 | height: 56px; 4 | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.07); 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | padding-left: 24px; 9 | padding-right: 24px; 10 | border-radius: 4px; 11 | user-select: none; 12 | -webkit-user-select: none; 13 | background-color: #fff; 14 | color: #55606e; 15 | } 16 | .rap-container:focus { 17 | outline: none; 18 | } 19 | .rap-container .rap-current-time, 20 | .rap-container .rap-total-time { 21 | min-width: 40px; 22 | } 23 | .rap-container .rap-total-time { 24 | text-align: right; 25 | } 26 | .rap-container .rap-pp-button { 27 | cursor: pointer; 28 | } 29 | .rap-container .rap-spinner { 30 | width: 18px; 31 | height: 18px; 32 | background-image: url('../assets/loading.png'); 33 | background-size: cover; 34 | background-repeat: no-repeat; 35 | animation: rapSpin 0.4s linear infinite; 36 | } 37 | .rap-container .rap-slider { 38 | flex-grow: 1; 39 | background-color: #d8d8d8; 40 | cursor: pointer; 41 | position: relative; 42 | } 43 | .rap-container .rap-slider .rap-progress { 44 | background-color: #007fff; 45 | border-radius: inherit; 46 | position: absolute; 47 | pointer-events: none; 48 | } 49 | .rap-container .rap-slider .rap-progress .rap-pin { 50 | height: 16px; 51 | width: 16px; 52 | border-radius: 8px; 53 | background-color: #007fff; 54 | position: absolute; 55 | pointer-events: all; 56 | box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.32); 57 | } 58 | .rap-container .rap-controls { 59 | font-family: 'Roboto', sans-serif; 60 | font-size: 16px; 61 | line-height: 18px; 62 | display: flex; 63 | flex-grow: 1; 64 | justify-content: space-between; 65 | align-items: center; 66 | margin-left: 24px; 67 | margin-right: 24px; 68 | } 69 | .rap-container .rap-controls .rap-slider { 70 | margin-left: 12px; 71 | margin-right: 12px; 72 | border-radius: 2px; 73 | height: 4px; 74 | } 75 | .rap-container .rap-controls .rap-slider .rap-progress { 76 | width: 0; 77 | height: 100%; 78 | } 79 | .rap-container .rap-controls .rap-slider .rap-progress .rap-pin { 80 | right: -8px; 81 | top: -6px; 82 | } 83 | .rap-container .rap-controls span { 84 | cursor: default; 85 | } 86 | .rap-container .rap-volume { 87 | position: relative; 88 | } 89 | .rap-container .rap-volume .rap-volume-btn { 90 | cursor: pointer; 91 | } 92 | .rap-container .rap-volume .rap-volume-controls { 93 | width: 30px; 94 | height: 135px; 95 | background-color: rgba(0, 0, 0, 0.62); 96 | border-radius: 7px; 97 | position: absolute; 98 | left: -3px; 99 | bottom: 52px; 100 | flex-direction: column; 101 | align-items: center; 102 | display: flex; 103 | z-index: 1; 104 | } 105 | .rap-container .rap-volume .rap-hidden { 106 | display: none; 107 | } 108 | .rap-container .rap-volume .rap-vol-placement-top { 109 | position: absolute; 110 | left: 0; 111 | top: 32px; 112 | } 113 | 114 | .rap-container .rap-volume .rap-vol-placement-bottom { 115 | position: absolute; 116 | left: 0; 117 | top: 230px; 118 | } 119 | 120 | .rap-container .rap-volume .rap-volume-controls .rap-slider { 121 | margin-top: 12px; 122 | margin-bottom: 12px; 123 | width: 6px; 124 | border-radius: 3px; 125 | } 126 | .rap-container .rap-volume .rap-volume-controls .rap-slider .rap-progress { 127 | bottom: 0; 128 | height: 100%; 129 | width: 6px; 130 | } 131 | .rap-container .rap-volume .rap-volume-controls .rap-slider .rap-progress .rap-pin { 132 | left: -5px; 133 | top: -8px; 134 | } 135 | 136 | .rap-container svg { 137 | display: block; 138 | } 139 | 140 | .rap-backdrop { 141 | position: fixed; 142 | width: 100vw; 143 | height: 100vh; 144 | top: 0; 145 | left: 0; 146 | } 147 | 148 | @keyframes rapSpin { 149 | from { 150 | transform: rotateZ(0); 151 | } 152 | to { 153 | transform: rotateZ(1turn); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/components/core.interface.ts: -------------------------------------------------------------------------------- 1 | export interface AudioInterface { 2 | autoPlay?: boolean; 3 | className?: string; 4 | src: string; 5 | loop?: boolean; 6 | preload?: 'auto' | 'metadata' | 'none'; 7 | backgroundColor?: string; 8 | color?: string; 9 | width?: number | string; 10 | style?: React.CSSProperties; 11 | sliderColor?: string; 12 | volume?: number; 13 | volumePlacement?: 'top' | 'bottom'; 14 | hasKeyBindings?: boolean; 15 | onPlay?: () => void; 16 | onPause?: () => void; 17 | onEnd?: () => void; 18 | onError?: (event: React.SyntheticEvent, errorMessage: string) => void; 19 | } 20 | 21 | export interface AudioPlayerRef { 22 | play: () => void; 23 | pause: () => void; 24 | stop: () => void; 25 | focus: () => void; 26 | } 27 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AudioPlayer'; 2 | export * from './core.interface'; 3 | -------------------------------------------------------------------------------- /src/helpers/icons/icons.ts: -------------------------------------------------------------------------------- 1 | export const iconPaths = { 2 | fullVolume: 3 | 'M14.667 0v2.747c3.853 1.146 6.666 4.72 6.666 8.946 0 4.227-2.813 7.787-6.666 8.934v2.76C20 22.173 24 17.4 24 11.693 24 5.987 20 1.213 14.667 0zM18 11.693c0-2.36-1.333-4.386-3.333-5.373v10.707c2-.947 3.333-2.987 3.333-5.334zm-18-4v8h5.333L12 22.36V1.027L5.333 7.693H0z', 4 | midVolume: 'M0 7.667v8h5.333L12 22.333V1L5.333 7.667M17.333 11.373C17.333 9.013 16 6.987 14 6v10.707c2-.947 3.333-2.987 3.333-5.334z', 5 | lowVolume: 'M0 7.667v8h5.333L12 22.333V1L5.333 7.667', 6 | pause: 'M0 0h6v24H0zM12 0h6v24h-6z', 7 | play: 'M18 12L0 24V0' 8 | }; 9 | -------------------------------------------------------------------------------- /src/helpers/utils/formatTime.ts: -------------------------------------------------------------------------------- 1 | export const formatTime = (time: number) => { 2 | const min = Math.floor(time / 60); 3 | const sec = Math.floor(time % 60); 4 | return min + ':' + (sec < 10 ? '0' + sec : sec); 5 | }; 6 | -------------------------------------------------------------------------------- /src/helpers/utils/getDeviceEventNames.ts: -------------------------------------------------------------------------------- 1 | type moveEvents = 'touchmove' | 'mousemove'; 2 | type upEvents = 'touchend' | 'mouseup'; 3 | 4 | interface returnProp { 5 | move: moveEvents; 6 | up: upEvents; 7 | } 8 | 9 | const getDeviceEventNames = (): returnProp => { 10 | if (isTouchDevice()) { 11 | return { 12 | move: 'touchmove', 13 | up: 'touchend' 14 | }; 15 | } 16 | return { 17 | move: 'mousemove', 18 | up: 'mouseup' 19 | }; 20 | }; 21 | 22 | const isTouchDevice = () => { 23 | return 'ontouchstart' in window || navigator.maxTouchPoints; 24 | }; 25 | 26 | export default getDeviceEventNames; 27 | -------------------------------------------------------------------------------- /src/helpers/utils/getRangeBox.ts: -------------------------------------------------------------------------------- 1 | function isDraggable(el: HTMLElement) { 2 | return Array.from(el.classList).indexOf('rap-pin') !== -1; 3 | } 4 | 5 | export const getRangeBox = (event: MouseEvent | TouchEvent | React.MouseEvent, currentDragElement: HTMLDivElement | null) => { 6 | let rangeBox = event.target as HTMLElement; 7 | if (event.type === 'click' && isDraggable(rangeBox) && rangeBox?.parentElement?.parentElement) { 8 | rangeBox = rangeBox.parentElement.parentElement; 9 | } 10 | if (event.type === 'mousemove' && currentDragElement?.parentElement?.parentElement) { 11 | rangeBox = currentDragElement.parentElement.parentElement; 12 | } 13 | return rangeBox; 14 | }; 15 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | -------------------------------------------------------------------------------- /tests/common.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import 'jest-canvas-mock'; 5 | 6 | import { AudioPlayer } from '../src'; 7 | 8 | beforeAll(() => { 9 | Object.defineProperty(HTMLMediaElement.prototype, 'load', { 10 | configurable: true, 11 | value: jest.fn() // Mock implementation of the load method 12 | }); 13 | }); 14 | 15 | describe('Common render', () => { 16 | it('renders without crashing', () => { 17 | render(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "strict": true, 5 | "skipLibCheck": true, 6 | "jsx": "react", 7 | "module": "ESNext", 8 | "declaration": true, 9 | "declarationDir": "types", 10 | "sourceMap": true, 11 | "outDir": "dist", 12 | "moduleResolution": "node", 13 | "emitDeclarationOnly": true, 14 | "allowSyntheticDefaultImports": true, 15 | "forceConsistentCasingInFileNames": true 16 | }, 17 | "exclude": ["dist", "node_modules", "src/**/*.test.tsx", "src/**/*.stories.tsx"], 18 | "include": ["**/*.ts"] 19 | } 20 | --------------------------------------------------------------------------------