├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── angular.json ├── docs └── images │ ├── advanced-player.png │ └── basic-player.png ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── know-how.txt ├── package-lock.json ├── package.json ├── projects └── ngx-audio-player │ ├── LICENSE │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── component │ │ │ └── ngx-audio-player │ │ │ │ ├── ngx-audio-player.component.css │ │ │ │ ├── ngx-audio-player.component.html │ │ │ │ ├── ngx-audio-player.component.spec.ts │ │ │ │ └── ngx-audio-player.component.ts │ │ ├── model │ │ │ ├── track.model.mock.ts │ │ │ └── track.model.ts │ │ ├── ngx-audio-player.module.ts │ │ ├── pipe │ │ │ ├── seconds-to-minutes.spec.ts │ │ │ └── seconds-to-minutes.ts │ │ └── service │ │ │ └── audio-player-service │ │ │ ├── audio-player.service.spec.ts │ │ │ └── audio-player.service.ts │ ├── public_api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ └── tslint.json ├── src ├── .browserslistrc ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── pages │ │ ├── gettingstarted │ │ │ ├── gettingstarted.component.css │ │ │ ├── gettingstarted.component.html │ │ │ ├── gettingstarted.component.spec.ts │ │ │ └── gettingstarted.component.ts │ │ └── home │ │ │ ├── _homepage-theme.scss │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ └── shared │ │ ├── footer │ │ ├── _footer-theme.scss │ │ ├── footer.component.html │ │ ├── footer.component.scss │ │ ├── footer.component.spec.ts │ │ ├── footer.component.ts │ │ └── index.ts │ │ ├── navbar │ │ ├── _navbar-theme.scss │ │ ├── index.ts │ │ ├── nav-bar.component.html │ │ ├── nav-bar.component.scss │ │ ├── nav-bar.component.spec.ts │ │ └── nav-bar.component.ts │ │ ├── style-manager │ │ ├── index.ts │ │ ├── style-manager.spec.ts │ │ └── style-manager.ts │ │ └── theme-picker │ │ ├── index.ts │ │ ├── theme-picker.component.html │ │ ├── theme-picker.component.scss │ │ ├── theme-picker.component.spec.ts │ │ ├── theme-picker.component.ts │ │ └── theme-storage │ │ ├── theme-storage.spec.ts │ │ └── theme-storage.ts ├── assets │ ├── .gitkeep │ └── images │ │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-384x384.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ │ ├── logos │ │ ├── angular │ │ │ ├── angular-white-transparent.svg │ │ │ └── github-circle-white-transparent.svg │ │ ├── logo_with_text_dark.png │ │ ├── logo_with_text_light.png │ │ └── ngx-audio-player-logo.png │ │ └── theme-demo-icon.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: vmudigal 2 | patreon: vmudigal 3 | custom: ['https://www.paypal.me/mudigal'] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/angular 2 | # Edit at https://www.gitignore.io/?templates=angular 3 | 4 | ### Angular ### 5 | ## Angular ## 6 | # compiled output 7 | dist/ 8 | tmp/ 9 | app/**/*.js 10 | app/**/*.js.map 11 | 12 | # dependencies 13 | node_modules/ 14 | bower_components/ 15 | 16 | # IDEs and editors 17 | .idea/ 18 | 19 | # misc 20 | .sass-cache/ 21 | connect.lock/ 22 | coverage/ 23 | libpeerconnection.log/ 24 | npm-debug.log 25 | testem.log 26 | typings/ 27 | 28 | # e2e 29 | e2e/*.js 30 | e2e/*.map 31 | 32 | #System Files 33 | .DS_Store/ 34 | 35 | # End of https://www.gitignore.io/api/angular 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: node_js 5 | node_js: 6 | - "10" 7 | 8 | script: npm run build-lib 9 | 10 | cache: 11 | yarn: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Vijayendra Mudigal 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 | 2 | A library for loading and playing audio using HTML 5 for Angular 7/8/9/10/11/12. 3 | (https://mudigal-technologies.github.io/ngx-audio-player/) 4 | 5 | 6 | [![npm](https://img.shields.io/badge/demo-online-ed1c46.svg?colorB=orange)](https://mudigal-technologies.github.io/ngx-audio-player/) [![npm version](https://img.shields.io/npm/v/ngx-audio-player.svg?colorB=red)](https://www.npmjs.com/package/ngx-audio-player) [![Downloads](https://img.shields.io/npm/dm/ngx-audio-player.svg?colorB=48C9B0)](https://www.npmjs.com/package/ngx-audio-player) [![licence](https://img.shields.io/npm/l/ngx-audio-player.svg?colorB=yellow)](https://www.npmjs.com/package/ngx-audio-player) [![Support](https://img.shields.io/badge/support-Angular%207%2B-blue.svg)](https://www.npmjs.com/package/ngx-audio-player/v/7.2.0) [![Support](https://img.shields.io/badge/support-Angular%208+-brown.svg)](https://www.npmjs.com/package/ngx-audio-player/v/8.1.4) [![Support](https://img.shields.io/badge/support-Angular%209+-black.svg)](https://www.npmjs.com/package/ngx-audio-player/v/9.2.3) [![Support](https://img.shields.io/badge/support-Angular%2010+-teal.svg)](https://www.npmjs.com/package/ngx-audio-player/v/10.1.4) [![Support](https://img.shields.io/badge/support-Angular%2011+-grey.svg)](https://www.npmjs.com/package/ngx-audio-player/v/11.0.4) [![Support](https://img.shields.io/badge/support-Angular%2012+-indigo.svg)](https://www.npmjs.com/package/ngx-audio-player/v/12.0.0) 7 | 8 | ## Table of contents 9 | 10 | - [Demo](#demo) 11 | - [Installation](#installation) 12 | - [Getting Started](#getting-started) 13 | - [Usage](#usage) 14 | - [Versioning](#versioning) 15 | - [Contributors](#contributors) 16 | - [License](#license) 17 | 18 | ## Demo 19 | 20 | A simple, clean, responsive player for playing multiple audios with playlist support. 21 | 22 | ![alt tag](https://github.com/mudigal-technologies/ngx-audio-player/blob/master/docs/images/advanced-player.png?raw=true) 23 | 24 | [Working Demo](https://mudigal-technologies.github.io/ngx-audio-player/) 25 | 26 | ## Installation 27 | 28 | `ngx-audio-player` is available via [npm](https://www.npmjs.com/package/ngx-audio-player) and [yarn](https://yarnpkg.com/en/package/ngx-audio-player) 29 | 30 | Using npm: 31 | ```bash 32 | $ npm install ngx-audio-player --save 33 | ``` 34 | 35 | Using yarn: 36 | ```bash 37 | $ yarn add ngx-audio-player 38 | ``` 39 | 40 | ## Getting Started 41 | 42 | NgxAudioPlayerModule needs Angular Material. 43 | Make sure you have installed below dependencies with same or higher version than mentioned. 44 | 45 | "@angular/core": "^12.0.0" 46 | "@angular/common": "^12.0.0" 47 | "@angular/material": "^12.0.0" 48 | "rxjs": "^6.6.0" 49 | 50 | Import `NgxAudioPlayerModule` in the root module(`AppModule`): 51 | 52 | ```typescript 53 | // Import library module 54 | import { NgxAudioPlayerModule } from 'ngx-audio-player'; 55 | 56 | @NgModule({ 57 | imports: [ 58 | // ... 59 | NgxAudioPlayerModule 60 | ] 61 | }) 62 | export class AppModule { } 63 | ``` 64 | 65 | ### Usage 66 | 67 | #### Simple Audio Player 68 | 69 | ##### HTML 70 | 71 | ```html 72 | 83 | 84 | 85 | ``` 86 | 87 | ##### TS 88 | 89 | ```ts 90 | import { Track } from 'ngx-audio-player'; 91 | 92 | . 93 | . 94 | 95 | mssapDisplayTitle = true; 96 | mssapDisablePositionSlider = true; 97 | mssapDisplayRepeatControls = true; 98 | mssapDisplayVolumeControls = true; 99 | mssapDisplayVolumeSlider = false; 100 | 101 | // Material Style Simple Audio Player 102 | mssapPlaylist: Track[] = [ 103 | { 104 | title: 'Audio Title', 105 | link: 'Link to Audio URL', 106 | artist: 'Audio Artist', 107 | duration: 'Audio Duration in seconds' 108 | } 109 | ]; 110 | 111 | // For Streaming Audio From URL 112 | // set mediaType = 'stream' 113 | mssapPlaylist: Track[] = [ 114 | { 115 | title: 'Audio Title', 116 | link: 'Link to Streaming URL', 117 | mediaType: 'stream' 118 | } 119 | ]; 120 | ``` 121 | 122 | #### Advanced Audio Player 123 | 124 | ##### HTML 125 | 126 | ```html 127 | 155 | 156 | 157 | ``` 158 | 159 | ##### TS 160 | 161 | ```ts 162 | import { Track } from 'ngx-audio-player'; 163 | 164 | . 165 | . 166 | 167 | // Main Player Controls 168 | msaapDisplayPlayList = true; 169 | msaapDisablePositionSlider = true; 170 | msaapDisplayRepeatControls = true; 171 | msaapDisplayVolumeControls = true; 172 | msaapDisplayVolumeSlider = false; 173 | 174 | // Title Marquee 175 | msaapDisplayTitle = true; 176 | 177 | // Playlist Controls 178 | msaapPageSizeOptions = [2,4,6]; 179 | msaapDisplayArtist = false; 180 | msaapDisplayDuration = false; 181 | 182 | // For Localisation 183 | msaapTableHeader = 'My Playlist'; 184 | msaapTitleHeader = 'My Title'; 185 | msaapArtistHeader = 'My Artist'; 186 | msaapDurationHeader = 'My Duration'; 187 | 188 | 189 | // Material Style Advance Audio Player Playlist 190 | msaapPlaylist: Track[] = [ 191 | { 192 | title: 'Audio One Title', 193 | link: 'Link to Audio One URL', 194 | artist: 'Artist', 195 | duration: 'Duration' 196 | }, 197 | { 198 | title: 'Audio Two Title', 199 | link: 'Link to Audio Two URL', 200 | artist: 'Artist', 201 | duration: 'Duration' 202 | }, 203 | { 204 | title: 'Audio Three Title', 205 | link: 'Link to Audio Three URL', 206 | artist: 'Artist', 207 | duration: 'Duration' 208 | }, 209 | ]; 210 | 211 | // Callback Events 212 | 213 | onTrackPlaying(event) { 214 | console.log(event); 215 | // your logic which needs to 216 | // be triggered once the 217 | // track ends goes here. 218 | } 219 | 220 | 221 | onTrackPaused(event) { 222 | console.log(event); 223 | // your logic which needs to 224 | // be triggered once the 225 | // track ends goes here. 226 | } 227 | 228 | onEnded(event) { 229 | console.log(event); 230 | // your logic which needs to 231 | // be triggered once the 232 | // track ends goes here. 233 | } 234 | 235 | onNextTrackRequested(event) { 236 | console.log(event); 237 | // your logic which needs to 238 | // be triggered once the 239 | // track ends goes here. 240 | } 241 | 242 | 243 | onPreviousTrackRequested(event) { 244 | console.log(event); 245 | // your logic which needs to 246 | // be triggered once the 247 | // track ends goes here. 248 | } 249 | 250 | onTrackSelected(event) { 251 | console.log(event); 252 | // your logic which needs to 253 | // be triggered once the 254 | // track ends goes here. 255 | } 256 | ``` 257 | 258 | ##### Properties 259 | 260 | | Name | Description | Type | 261 | |-----------------------------------------------------------------|-----------------------------------------------------|-----------| 262 | | @Input() playlist: Track[]; | playlist containing array of title and link | mandatory | 263 | | @Input() autoPlay: false; | true - if the audio needs to be played automaticlly | optional | 264 | | Player Controls | 265 | | @Input() startOffset = 0; | offset from start of audio file in seconds | optional | 266 | | @Input() endOffset = 0; | offset from end of audio file in seconds | optional | 267 | | @Input() disablePositionSlider = false; | true - if the position slider needs to be disabled | optional | 268 | | @Input() displayRepeatControls = true; | false - if the repeat controls needs to be hidden | optional | 269 | | @Input() repeat: "all" | "one" | "none" = 'all'; | repeat all or one or none | optional | 270 | | @Input() displayVolumeControls = true; | false - if the volume controls needs to be hidden | optional | 271 | | @Input() displayVolumeSlider = true; | true - if the volume slider should be shown | optional | 272 | | Title Marquee Control | 273 | | @Input() displayTitle: true; | false - if the audio title needs to be hidden | optional | 274 | | Playlist Controls | 275 | | @Input() displayPlaylist: true; | false - if the playlist needs to be hidden | optional | 276 | | @Input() pageSizeOptions = [10, 20, 30]; | number of items to be displayed in the playlist | optional | 277 | | @Input() expanded = true; | false - if the playlist needs to be minimized | optional | 278 | | @Input() displayArtist = false; | true - if the artist data is to be shown | optional | 279 | | @Input() displayDuration = false; | true - if the track duration is to be shown | optional | 280 | | Localisation Controls | 281 | | @Input() tableHeader = 'Playlist'; | localised string | optional | 282 | | @Input() titleHeader = 'Title'; | localised string | optional | 283 | | @Input() artistHeader = 'Artist'; | localised string | optional | 284 | | @Input() durationHeader = 'Duration'; | localised string | optional | 285 | | Callback Events | 286 | | @Output() trackPlaying: EventEmitter | triggers when the track starts playing | optional | 287 | | @Output() trackPaused: EventEmitter | Callback method that triggers once the track ends | optional | 288 | | @Output() trackEnded: EventEmitter | Callback method that triggers once the track ends | optional | 289 | | @Output() nextTrackRequested: EventEmitter | Callback method that triggers once the track ends | optional | 290 | | @Output() previousTrackRequested: EventEmitter | Callback method that triggers once the track ends | optional | 291 | | @Output() trackSelected: EventEmitter | Callback method that triggers once the track ends | optional | 292 | 293 | 294 | ## Versioning 295 | 296 | ngx-audio-player will be maintained under the Semantic Versioning guidelines. 297 | Releases will be numbered with the following format: 298 | 299 | `..` 300 | 301 | For more information on SemVer, please visit http://semver.org. 302 | 303 | ## Contributors ✨ 304 | 305 | Thanks goes to these wonderful people: 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 322 | 331 | 340 | 350 | 360 | 370 | 380 | 390 | 400 | 401 |
314 | 315 | 316 |
Edric Chan 317 |

318 | 💻 321 |
323 | 324 | 325 |
RokiFoki 326 |

327 | 💻 330 |
332 | 333 | 334 |
ewwwgiddings 335 |

336 | 📖 339 |
341 | 342 | 343 |
Caleb Crosby 344 |

345 | 💻 348 | 349 |
351 | 352 | 353 |
Shelly 354 |

355 | 💻 358 | 359 |
361 | 362 | 363 |
Simon Reinsch 364 |

365 | 💻 368 | 369 |
371 | 372 | 373 |
AnwarTuha 374 |

375 | 💻 378 | 379 |
381 | 382 | 383 |
Bogdan Baghiu 384 |

385 | 💻 388 | 389 |
391 | 392 | 393 |
Kareem Jeiroudi 394 |

395 | 💻 398 | 399 |
402 | 403 | 404 | 405 | 406 | 407 | ## Misc 408 | 409 | 410 | ## License 411 | 412 | ##### The MIT License (MIT) 413 | 414 | ## Donate 415 | 416 | If you like my work you can buy me a :beer: or :pizza: 417 | 418 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/mudigal) 419 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-audio-player-demo": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "aot": false, 21 | "outputPath": "dist/ngx-audio-player-demo", 22 | "index": "src/index.html", 23 | "main": "src/main.ts", 24 | "polyfills": "src/polyfills.ts", 25 | "tsConfig": "src/tsconfig.app.json", 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | { 32 | "input": "src/styles.scss" 33 | }, 34 | { 35 | "inject": false, 36 | "input": "./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css", 37 | "bundleName": "pink-bluegrey" 38 | }, 39 | { 40 | "inject": false, 41 | "input": "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", 42 | "bundleName": "deeppurple-amber" 43 | }, 44 | { 45 | "inject": false, 46 | "input": "./node_modules/@angular/material/prebuilt-themes/purple-green.css", 47 | "bundleName": "purple-green" 48 | }, 49 | { 50 | "inject": true, 51 | "input": "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 52 | "bundleName": "indigo-pink" 53 | } 54 | ], 55 | 56 | "scripts": [], 57 | "vendorChunk": true, 58 | "extractLicenses": false, 59 | "buildOptimizer": false, 60 | "sourceMap": true, 61 | "optimization": false, 62 | "namedChunks": true 63 | }, 64 | "configurations": { 65 | "production": { 66 | "fileReplacements": [ 67 | { 68 | "replace": "src/environments/environment.ts", 69 | "with": "src/environments/environment.prod.ts" 70 | } 71 | ], 72 | "optimization": true, 73 | "outputHashing": "all", 74 | "sourceMap": false, 75 | "namedChunks": false, 76 | "extractLicenses": true, 77 | "vendorChunk": false, 78 | "budgets": [ 79 | { 80 | "type": "initial", 81 | "maximumWarning": "2mb", 82 | "maximumError": "5mb" 83 | }, 84 | { 85 | "type": "anyComponentStyle", 86 | "maximumWarning": "6kb" 87 | } 88 | ] 89 | } 90 | }, 91 | "defaultConfiguration": "" 92 | }, 93 | "serve": { 94 | "builder": "@angular-devkit/build-angular:dev-server", 95 | "options": { 96 | "browserTarget": "ngx-audio-player-demo:build" 97 | }, 98 | "configurations": { 99 | "production": { 100 | "browserTarget": "ngx-audio-player-demo:build:production" 101 | } 102 | } 103 | }, 104 | "extract-i18n": { 105 | "builder": "@angular-devkit/build-angular:extract-i18n", 106 | "options": { 107 | "browserTarget": "ngx-audio-player-demo:build" 108 | } 109 | }, 110 | "test": { 111 | "builder": "@angular-devkit/build-angular:karma", 112 | "options": { 113 | "main": "src/test.ts", 114 | "polyfills": "src/polyfills.ts", 115 | "tsConfig": "src/tsconfig.spec.json", 116 | "karmaConfig": "src/karma.conf.js", 117 | "styles": [], 118 | "scripts": [], 119 | "assets": [ 120 | "src/favicon.ico", 121 | "src/assets" 122 | ] 123 | } 124 | }, 125 | "lint": { 126 | "builder": "@angular-devkit/build-angular:tslint", 127 | "options": { 128 | "tsConfig": [ 129 | "src/tsconfig.app.json", 130 | "src/tsconfig.spec.json" 131 | ], 132 | "exclude": [ 133 | "**/node_modules/**" 134 | ] 135 | } 136 | } 137 | } 138 | }, 139 | "ngx-audio-player-demo-e2e": { 140 | "root": "e2e/", 141 | "projectType": "application", 142 | "prefix": "", 143 | "architect": { 144 | "e2e": { 145 | "builder": "@angular-devkit/build-angular:protractor", 146 | "options": { 147 | "protractorConfig": "e2e/protractor.conf.js", 148 | "devServerTarget": "ngx-audio-player-demo:serve" 149 | }, 150 | "configurations": { 151 | "production": { 152 | "devServerTarget": "ngx-audio-player-demo:serve:production" 153 | } 154 | } 155 | }, 156 | "lint": { 157 | "builder": "@angular-devkit/build-angular:tslint", 158 | "options": { 159 | "tsConfig": "e2e/tsconfig.e2e.json", 160 | "exclude": [ 161 | "**/node_modules/**" 162 | ] 163 | } 164 | } 165 | } 166 | }, 167 | "ngx-audio-player": { 168 | "root": "projects/ngx-audio-player", 169 | "sourceRoot": "projects/ngx-audio-player/src", 170 | "projectType": "library", 171 | "prefix": "lib", 172 | "architect": { 173 | "build": { 174 | "builder": "@angular-devkit/build-ng-packagr:build", 175 | "options": { 176 | "tsConfig": "projects/ngx-audio-player/tsconfig.lib.json", 177 | "project": "projects/ngx-audio-player/ng-package.json" 178 | }, 179 | "configurations": { 180 | "production": { 181 | "tsConfig": "projects/ngx-audio-player/tsconfig.lib.prod.json" 182 | } 183 | } 184 | }, 185 | "test": { 186 | "builder": "@angular-devkit/build-angular:karma", 187 | "options": { 188 | "main": "projects/ngx-audio-player/src/test.ts", 189 | "tsConfig": "projects/ngx-audio-player/tsconfig.spec.json", 190 | "karmaConfig": "projects/ngx-audio-player/karma.conf.js" 191 | } 192 | }, 193 | "lint": { 194 | "builder": "@angular-devkit/build-angular:tslint", 195 | "options": { 196 | "tsConfig": [ 197 | "projects/ngx-audio-player/tsconfig.lib.json", 198 | "projects/ngx-audio-player/tsconfig.spec.json" 199 | ], 200 | "exclude": [ 201 | "**/node_modules/**" 202 | ] 203 | } 204 | } 205 | } 206 | } 207 | }, 208 | "defaultProject": "ngx-audio-player-demo", 209 | "cli": { 210 | "analytics": false 211 | } 212 | } -------------------------------------------------------------------------------- /docs/images/advanced-player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/docs/images/advanced-player.png -------------------------------------------------------------------------------- /docs/images/basic-player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/docs/images/basic-player.png -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to ngx-audio-player-demo!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /know-how.txt: -------------------------------------------------------------------------------- 1 | Github Pages 2 | 3 | Build 4 | ng build --prod --base-href https://mudigal-technologies.github.io/ngx-audio-player/ 5 | 6 | Publish 7 | npx ngh --dir=dist/ngx-audio-player-demo/ 8 | 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-audio-player-demo", 3 | "version": "12.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "git+https://github.com/mudigal-technologies/ngx-audio-player.git" 7 | }, 8 | "author": "Vijayendra Mudigal (https://vijayendra.mudigal.com)", 9 | "keywords": [ 10 | "ngx", 11 | "audio", 12 | "player", 13 | "angular", 14 | "angular7", 15 | "angular8", 16 | "angular9", 17 | "angular10", 18 | "angular11", 19 | "angular12", 20 | "audio player", 21 | "angular audio player", 22 | "audio player with playlist", 23 | "material style audio player", 24 | "material style basic audio player", 25 | "material style advanced audio player" 26 | ], 27 | "license": "MIT", 28 | "scripts": { 29 | "ng": "ng", 30 | "start": "ng serve --host 0.0.0.0", 31 | "lch-build": "ng build", 32 | "prod-build": "ng build --configuration production", 33 | "build-lib": "ng build ngx-audio-player --configuration production", 34 | "publish-lib": "npm publish --f dist/ngx-audio-player --access public", 35 | "build-demo": "ng build --configuration production --base-href https://mudigal-technologies.github.io/ngx-audio-player/", 36 | "publish-demo": "npx ngh --dir=dist/ngx-audio-player-demo/", 37 | "test": "ng test --code-coverage --reporters=coverage-istanbul", 38 | "test-lib": "ng test ngx-audio-player --code-coverage --reporters=coverage-istanbul", 39 | "lint": "ng lint", 40 | "e2e": "ng e2e" 41 | }, 42 | "private": true, 43 | "dependencies": { 44 | "@angular-devkit/build-ng-packagr": "^0.1002.0", 45 | "@angular/animations": "^12.2.17", 46 | "@angular/cdk": "^12.2.13", 47 | "@angular/common": "^12.2.17", 48 | "@angular/compiler": "^12.2.17", 49 | "@angular/core": "^12.2.17", 50 | "@angular/forms": "^12.2.17", 51 | "@angular/material": "^12.2.13", 52 | "@angular/platform-browser": "^12.2.17", 53 | "@angular/platform-browser-dynamic": "^12.2.17", 54 | "@angular/router": "^12.2.17", 55 | "ngx-audio-player": "12.0.1", 56 | "core-js": "^3.32.2", 57 | "rxjs": "^6.6.7", 58 | "tslib": "^2.6.2", 59 | "zone.js": "~0.11.8" 60 | }, 61 | "devDependencies": { 62 | "@angular-devkit/build-angular": "^12.2.18", 63 | "@angular/cli": "^12.2.18", 64 | "@angular/compiler-cli": "^12.2.17", 65 | "@angular/language-service": "^12.2.17", 66 | "@types/jasmine": "^3.10.13", 67 | "@types/jasminewd2": "~2.0.11", 68 | "@types/node": "^12.20.55", 69 | "angular-cli-ghpages": "^0.6.2", 70 | "codelyzer": "^6.0.2", 71 | "jasmine-core": "~3.6.0", 72 | "jasmine-spec-reporter": "~5.0.0", 73 | "karma": "~6.4.0", 74 | "karma-chrome-launcher": "~3.1.0", 75 | "karma-coverage-istanbul-reporter": "~3.0.2", 76 | "karma-jasmine": "~4.0.0", 77 | "karma-jasmine-html-reporter": "^1.5.0", 78 | "karma-teamcity-reporter": "^1.1.0", 79 | "ng-packagr": "^12.2.7", 80 | "sass": "^1.68.0", 81 | "protractor": "~7.0.0", 82 | "ts-node": "~8.6.2", 83 | "tsickle": "~0.38.0", 84 | "tslint": "~6.1.0", 85 | "typescript": "~4.3.5", 86 | "webpack": "^4.47.0" 87 | }, 88 | "description": "A library for loading and playing audio using HTML 5 for Angular 7/8/9/10/11/12. (https://mudigal-technologies.github.io/ngx-audio-player/)", 89 | "bugs": { 90 | "url": "https://github.com/mudigal-technologies/ngx-audio-player/issues" 91 | }, 92 | "homepage": "https://github.com/mudigal-technologies/ngx-audio-player#readme", 93 | "main": "index.js", 94 | "directories": { 95 | "doc": "docs" 96 | } 97 | } -------------------------------------------------------------------------------- /projects/ngx-audio-player/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018-2021 Vijayendra Mudigal. 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /projects/ngx-audio-player/README.md: -------------------------------------------------------------------------------- 1 | 2 | A library for loading and playing audio using HTML 5 for Angular 7/8/9/10/11/12. 3 | (https://mudigal-technologies.github.io/ngx-audio-player/) 4 | 5 | 6 | [![npm](https://img.shields.io/badge/demo-online-ed1c46.svg?colorB=orange)](https://mudigal-technologies.github.io/ngx-audio-player/) [![npm version](https://img.shields.io/npm/v/ngx-audio-player.svg?colorB=red)](https://www.npmjs.com/package/ngx-audio-player) [![Downloads](https://img.shields.io/npm/dm/ngx-audio-player.svg?colorB=48C9B0)](https://www.npmjs.com/package/ngx-audio-player) [![licence](https://img.shields.io/npm/l/ngx-audio-player.svg?colorB=yellow)](https://www.npmjs.com/package/ngx-audio-player) [![Support](https://img.shields.io/badge/support-Angular%207%2B-blue.svg)](https://www.npmjs.com/package/ngx-audio-player/v/7.2.0) [![Support](https://img.shields.io/badge/support-Angular%208+-brown.svg)](https://www.npmjs.com/package/ngx-audio-player/v/8.1.4) [![Support](https://img.shields.io/badge/support-Angular%209+-black.svg)](https://www.npmjs.com/package/ngx-audio-player/v/9.2.3) [![Support](https://img.shields.io/badge/support-Angular%2010+-teal.svg)](https://www.npmjs.com/package/ngx-audio-player/v/10.1.4) [![Support](https://img.shields.io/badge/support-Angular%2011+-grey.svg)](https://www.npmjs.com/package/ngx-audio-player/v/11.0.4) [![Support](https://img.shields.io/badge/support-Angular%2012+-indigo.svg)](https://www.npmjs.com/package/ngx-audio-player/v/12.0.0) 7 | 8 | ## Table of contents 9 | 10 | - [Demo](#demo) 11 | - [Installation](#installation) 12 | - [Getting Started](#getting-started) 13 | - [Usage](#usage) 14 | - [Versioning](#versioning) 15 | - [Contributors](#contributors) 16 | - [License](#license) 17 | 18 | ## Demo 19 | 20 | A simple, clean, responsive player for playing multiple audios with playlist support. 21 | 22 | ![alt tag](https://github.com/mudigal-technologies/ngx-audio-player/blob/master/docs/images/advanced-player.png?raw=true) 23 | 24 | [Working Demo](https://mudigal-technologies.github.io/ngx-audio-player/) 25 | 26 | ## Installation 27 | 28 | `ngx-audio-player` is available via [npm](https://www.npmjs.com/package/ngx-audio-player) and [yarn](https://yarnpkg.com/en/package/ngx-audio-player) 29 | 30 | Using npm: 31 | ```bash 32 | $ npm install ngx-audio-player --save 33 | ``` 34 | 35 | Using yarn: 36 | ```bash 37 | $ yarn add ngx-audio-player 38 | ``` 39 | 40 | ## Getting Started 41 | 42 | NgxAudioPlayerModule needs Angular Material. 43 | Make sure you have installed below dependencies with same or higher version than mentioned. 44 | 45 | "@angular/core": "^12.0.0" 46 | "@angular/common": "^12.0.0" 47 | "@angular/material": "^12.0.0" 48 | "rxjs": "^6.6.0" 49 | 50 | Import `NgxAudioPlayerModule` in the root module(`AppModule`): 51 | 52 | ```typescript 53 | // Import library module 54 | import { NgxAudioPlayerModule } from 'ngx-audio-player'; 55 | 56 | @NgModule({ 57 | imports: [ 58 | // ... 59 | NgxAudioPlayerModule 60 | ] 61 | }) 62 | export class AppModule { } 63 | ``` 64 | 65 | ### Usage 66 | 67 | #### Simple Audio Player 68 | 69 | ##### HTML 70 | 71 | ```html 72 | 83 | 84 | 85 | ``` 86 | 87 | ##### TS 88 | 89 | ```ts 90 | import { Track } from 'ngx-audio-player'; 91 | 92 | . 93 | . 94 | 95 | mssapDisplayTitle = true; 96 | mssapDisablePositionSlider = true; 97 | mssapDisplayRepeatControls = true; 98 | mssapDisplayVolumeControls = true; 99 | mssapDisplayVolumeSlider = false; 100 | 101 | // Material Style Simple Audio Player 102 | mssapPlaylist: Track[] = [ 103 | { 104 | title: 'Audio Title', 105 | link: 'Link to Audio URL', 106 | artist: 'Audio Artist', 107 | duration: 'Audio Duration in seconds' 108 | } 109 | ]; 110 | 111 | // For Streaming Audio From URL 112 | // set mediaType = 'stream' 113 | mssapPlaylist: Track[] = [ 114 | { 115 | title: 'Audio Title', 116 | link: 'Link to Streaming URL', 117 | mediaType: 'stream' 118 | } 119 | ]; 120 | ``` 121 | 122 | #### Advanced Audio Player 123 | 124 | ##### HTML 125 | 126 | ```html 127 | 155 | 156 | 157 | ``` 158 | 159 | ##### TS 160 | 161 | ```ts 162 | import { Track } from 'ngx-audio-player'; 163 | 164 | . 165 | . 166 | 167 | // Main Player Controls 168 | msaapDisplayPlayList = true; 169 | msaapDisablePositionSlider = true; 170 | msaapDisplayRepeatControls = true; 171 | msaapDisplayVolumeControls = true; 172 | msaapDisplayVolumeSlider = false; 173 | 174 | // Title Marquee 175 | msaapDisplayTitle = true; 176 | 177 | // Playlist Controls 178 | msaapPageSizeOptions = [2,4,6]; 179 | msaapDisplayArtist = false; 180 | msaapDisplayDuration = false; 181 | 182 | // For Localisation 183 | msaapTableHeader = 'My Playlist'; 184 | msaapTitleHeader = 'My Title'; 185 | msaapArtistHeader = 'My Artist'; 186 | msaapDurationHeader = 'My Duration'; 187 | 188 | 189 | // Material Style Advance Audio Player Playlist 190 | msaapPlaylist: Track[] = [ 191 | { 192 | title: 'Audio One Title', 193 | link: 'Link to Audio One URL', 194 | artist: 'Artist', 195 | duration: 'Duration' 196 | }, 197 | { 198 | title: 'Audio Two Title', 199 | link: 'Link to Audio Two URL', 200 | artist: 'Artist', 201 | duration: 'Duration' 202 | }, 203 | { 204 | title: 'Audio Three Title', 205 | link: 'Link to Audio Three URL', 206 | artist: 'Artist', 207 | duration: 'Duration' 208 | }, 209 | ]; 210 | 211 | // Callback Events 212 | 213 | onTrackPlaying(event) { 214 | console.log(event); 215 | // your logic which needs to 216 | // be triggered once the 217 | // track ends goes here. 218 | } 219 | 220 | 221 | onTrackPaused(event) { 222 | console.log(event); 223 | // your logic which needs to 224 | // be triggered once the 225 | // track ends goes here. 226 | } 227 | 228 | onEnded(event) { 229 | console.log(event); 230 | // your logic which needs to 231 | // be triggered once the 232 | // track ends goes here. 233 | } 234 | 235 | onNextTrackRequested(event) { 236 | console.log(event); 237 | // your logic which needs to 238 | // be triggered once the 239 | // track ends goes here. 240 | } 241 | 242 | 243 | onPreviousTrackRequested(event) { 244 | console.log(event); 245 | // your logic which needs to 246 | // be triggered once the 247 | // track ends goes here. 248 | } 249 | 250 | onTrackSelected(event) { 251 | console.log(event); 252 | // your logic which needs to 253 | // be triggered once the 254 | // track ends goes here. 255 | } 256 | ``` 257 | 258 | ##### Properties 259 | 260 | | Name | Description | Type | 261 | |-----------------------------------------------------------------|-----------------------------------------------------|-----------| 262 | | @Input() playlist: Track[]; | playlist containing array of title and link | mandatory | 263 | | @Input() autoPlay: false; | true - if the audio needs to be played automaticlly | optional | 264 | | Player Controls | 265 | | @Input() startOffset = 0; | offset from start of audio file in seconds | optional | 266 | | @Input() endOffset = 0; | offset from end of audio file in seconds | optional | 267 | | @Input() disablePositionSlider = false; | true - if the position slider needs to be disabled | optional | 268 | | @Input() displayRepeatControls = true; | false - if the repeat controls needs to be hidden | optional | 269 | | @Input() repeat: "all" | "one" | "none" = 'all'; | repeat all or one or none | optional | 270 | | @Input() displayVolumeControls = true; | false - if the volume controls needs to be hidden | optional | 271 | | @Input() displayVolumeSlider = true; | true - if the volume slider should be shown | optional | 272 | | Title Marquee Control | 273 | | @Input() displayTitle: true; | false - if the audio title needs to be hidden | optional | 274 | | Playlist Controls | 275 | | @Input() displayPlaylist: true; | false - if the playlist needs to be hidden | optional | 276 | | @Input() pageSizeOptions = [10, 20, 30]; | number of items to be displayed in the playlist | optional | 277 | | @Input() expanded = true; | false - if the playlist needs to be minimized | optional | 278 | | @Input() displayArtist = false; | true - if the artist data is to be shown | optional | 279 | | @Input() displayDuration = false; | true - if the track duration is to be shown | optional | 280 | | Localisation Controls | 281 | | @Input() tableHeader = 'Playlist'; | localised string | optional | 282 | | @Input() titleHeader = 'Title'; | localised string | optional | 283 | | @Input() artistHeader = 'Artist'; | localised string | optional | 284 | | @Input() durationHeader = 'Duration'; | localised string | optional | 285 | | Callback Events | 286 | | @Output() trackPlaying: EventEmitter | triggers when the track starts playing | optional | 287 | | @Output() trackPaused: EventEmitter | Callback method that triggers once the track ends | optional | 288 | | @Output() trackEnded: EventEmitter | Callback method that triggers once the track ends | optional | 289 | | @Output() nextTrackRequested: EventEmitter | Callback method that triggers once the track ends | optional | 290 | | @Output() previousTrackRequested: EventEmitter | Callback method that triggers once the track ends | optional | 291 | | @Output() trackSelected: EventEmitter | Callback method that triggers once the track ends | optional | 292 | 293 | 294 | ## Versioning 295 | 296 | ngx-audio-player will be maintained under the Semantic Versioning guidelines. 297 | Releases will be numbered with the following format: 298 | 299 | `..` 300 | 301 | For more information on SemVer, please visit http://semver.org. 302 | 303 | ## Contributors ✨ 304 | 305 | Thanks goes to these wonderful people: 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 322 | 331 | 340 | 350 | 360 | 370 | 380 | 390 | 400 | 401 |
314 | 315 | 316 |
Edric Chan 317 |

318 | 💻 321 |
323 | 324 | 325 |
RokiFoki 326 |

327 | 💻 330 |
332 | 333 | 334 |
ewwwgiddings 335 |

336 | 📖 339 |
341 | 342 | 343 |
Caleb Crosby 344 |

345 | 💻 348 | 349 |
351 | 352 | 353 |
Shelly 354 |

355 | 💻 358 | 359 |
361 | 362 | 363 |
Simon Reinsch 364 |

365 | 💻 368 | 369 |
371 | 372 | 373 |
AnwarTuha 374 |

375 | 💻 378 | 379 |
381 | 382 | 383 |
Bogdan Baghiu 384 |

385 | 💻 388 | 389 |
391 | 392 | 393 |
Kareem Jeiroudi 394 |

395 | 💻 398 | 399 |
402 | 403 | 404 | 405 | 406 | 407 | ## Misc 408 | 409 | 410 | ## License 411 | 412 | ##### The MIT License (MIT) 413 | 414 | ## Donate 415 | 416 | If you like my work you can buy me a :beer: or :pizza: 417 | 418 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/mudigal) 419 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-teamcity-reporter'), 12 | require('karma-jasmine-html-reporter'), 13 | require('karma-coverage-istanbul-reporter'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | client: { 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageIstanbulReporter: { 20 | dir: require('path').join(__dirname, '../../coverage'), 21 | reports: ['html', 'lcovonly'], 22 | fixWebpackSourcePaths: true 23 | }, 24 | reporters: ['progress', 'kjhtml'], 25 | port: 9876, 26 | colors: true, 27 | logLevel: config.LOG_INFO, 28 | autoWatch: true, 29 | browsers: ['Chrome'], 30 | singleRun: false 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "./../../dist/ngx-audio-player", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ngx-audio-player/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-audio-player", 3 | "version": "12.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "tslib": { 8 | "version": "2.6.2", 9 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", 10 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-audio-player", 3 | "version": "12.0.2", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/mudigal-technologies/ngx-audio-player.git" 7 | }, 8 | "author": { 9 | "name": "Mudigal Technologies LLP", 10 | "email": "contact@mudigal.com", 11 | "url": "https://www.mudigal.com" 12 | }, 13 | "keywords": [ 14 | "ngx", 15 | "audio", 16 | "player", 17 | "angular", 18 | "angular7", 19 | "angular8", 20 | "angular9", 21 | "angular10", 22 | "angular11", 23 | "angular12", 24 | "audio player", 25 | "angular audio player", 26 | "audio player with playlist", 27 | "material style audio player", 28 | "material style basic audio player", 29 | "material style advanced audio player" 30 | ], 31 | "license": "MIT", 32 | "licenses": [ 33 | { 34 | "type": "MIT", 35 | "url": "https://github.com/mudigal-technologies/ngx-audio-player/blob/master/projects/ngx-audio-player/LICENSE" 36 | } 37 | ], 38 | "bugs": { 39 | "url": "https://github.com/mudigal-technologies/ngx-audio-player/issues" 40 | }, 41 | "peerDependencies": { 42 | "@angular/common": "^12.0.0", 43 | "@angular/core": "^12.0.0", 44 | "@angular/material": "^12.0.0", 45 | "rxjs": "^6.6.0" 46 | }, 47 | "dependencies": { 48 | "tslib": "^2.0.0" 49 | } 50 | } -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/component/ngx-audio-player/ngx-audio-player.component.css: -------------------------------------------------------------------------------- 1 | mat-card, 2 | mat-slider { 3 | padding: 0 !important; 4 | } 5 | 6 | button:hover, 7 | button:focus { 8 | outline: 0px !important; 9 | } 10 | 11 | svg { 12 | vertical-align: top; 13 | } 14 | 15 | .mat-icon { 16 | height: 32px ! important; 17 | width: 32px ! important; 18 | } 19 | 20 | mat-icon>.currently-playing { 21 | height: 16px ! important; 22 | width: 16px ! important; 23 | } 24 | 25 | table { 26 | width: 100%; 27 | } 28 | 29 | ::ng-deep .ngx-audio-player { 30 | min-width: 375px; 31 | } 32 | 33 | .mat-expansion-panel { 34 | min-width: 375px; 35 | } 36 | 37 | /* mat-slider { 38 | max-height: 30px; 39 | } */ 40 | 41 | ::ng-deep .mat-paginator-container { 42 | justify-content: space-between !important; 43 | } 44 | 45 | .material-icons { 46 | font-size: 16px ! important; 47 | } 48 | 49 | .play-pause { 50 | border-left: 1px solid rgba(0, 0, 0, .1); 51 | border-right: 1px solid rgba(0, 0, 0, .1); 52 | } 53 | 54 | .volume { 55 | border-left: 1px solid rgba(0, 0, 0, .1); 56 | } 57 | 58 | .skip-next { 59 | border-right: 1px solid rgba(0, 0, 0, .1); 60 | } 61 | 62 | /* Removal of bootstrap */ 63 | 64 | *, 65 | ::after, 66 | ::before { 67 | box-sizing: inherit; 68 | } 69 | 70 | .ngx-p-1 { 71 | padding: .25rem !important; 72 | } 73 | 74 | .ngx-col, 75 | .ngx-col-1, 76 | .ngx-col-10, 77 | .ngx-col-11, 78 | .ngx-col-12, 79 | .ngx-col-2, 80 | .ngx-col-3, 81 | .ngx-col-4, 82 | .ngx-col-5, 83 | .ngx-col-6, 84 | .ngx-col-7, 85 | .ngx-col-8, 86 | .ngx-col-9, 87 | .ngx-col-auto, 88 | .ngx-col-lg, 89 | .ngx-col-lg-1, 90 | .ngx-col-lg-10, 91 | .ngx-col-lg-11, 92 | .ngx-col-lg-12, 93 | .ngx-col-lg-2, 94 | .ngx-col-lg-3, 95 | .ngx-col-lg-4, 96 | .ngx-col-lg-5, 97 | .ngx-col-lg-6, 98 | .ngx-col-lg-7, 99 | .ngx-col-lg-8, 100 | .ngx-col-lg-9, 101 | .ngx-col-lg-auto, 102 | .ngx-col-md, 103 | .ngx-col-md-1, 104 | .ngx-col-md-10, 105 | .ngx-col-md-11, 106 | .ngx-col-md-12, 107 | .ngx-col-md-2, 108 | .ngx-col-md-3, 109 | .ngx-col-md-4, 110 | .ngx-col-md-5, 111 | .ngx-col-md-6, 112 | .ngx-col-md-7, 113 | .ngx-col-md-8, 114 | .ngx-col-md-9, 115 | .ngx-col-md-auto, 116 | .ngx-col-sm, 117 | .ngx-col-sm-1, 118 | .ngx-col-sm-10, 119 | .ngx-col-sm-11, 120 | .ngx-col-sm-12, 121 | .ngx-col-sm-2, 122 | .ngx-col-sm-3, 123 | .ngx-col-sm-4, 124 | .ngx-col-sm-5, 125 | .ngx-col-sm-6, 126 | .ngx-col-sm-7, 127 | .ngx-col-sm-8, 128 | .ngx-col-sm-9, 129 | .ngx-col-sm-auto, 130 | .ngx-col-xl, 131 | .ngx-col-xl-1, 132 | .ngx-col-xl-10, 133 | .ngx-col-xl-11, 134 | .ngx-col-xl-12, 135 | .ngx-col-xl-2, 136 | .ngx-col-xl-3, 137 | .ngx-col-xl-4, 138 | .ngx-col-xl-5, 139 | .ngx-col-xl-6, 140 | .ngx-col-xl-7, 141 | .ngx-col-xl-8, 142 | .ngx-col-xl-9, 143 | .ngx-col-xl-auto { 144 | position: relative; 145 | width: 100%; 146 | padding-right: 15px; 147 | padding-left: 15px; 148 | } 149 | 150 | .ngx-col { 151 | -ms-flex-preferred-size: 0; 152 | flex-basis: 0; 153 | -ms-flex-positive: 1; 154 | flex-grow: 1; 155 | max-width: 100%; 156 | } 157 | 158 | .ngx-content-center { 159 | -ms-flex-pack: center !important; 160 | justify-content: center !important; 161 | align-items: center !important; 162 | } 163 | 164 | .ngx-flex-fill { 165 | -ms-flex: 1 1 auto !important; 166 | flex: 1 1 auto !important; 167 | } 168 | 169 | .ngx-d-flex { 170 | display: -ms-flexbox !important; 171 | display: flex !important; 172 | } 173 | 174 | .ngx-pb-3, 175 | .ngx-py-3 { 176 | padding-bottom: 0.5rem !important; 177 | } 178 | 179 | .ngx-pt-3, 180 | .ngx-py-3 { 181 | padding-top: 0.5rem !important; 182 | } 183 | 184 | .ngx-pl-1, 185 | .ngx-px-1 { 186 | padding-left: .25rem !important; 187 | } 188 | 189 | .ngx-pr-1, 190 | .ngx-px-1 { 191 | padding-right: .25rem !important; 192 | } 193 | 194 | .ngx-slider { 195 | min-width: 64px; 196 | } 197 | 198 | .ngx-volume { 199 | height: 42px; 200 | margin-top: -6px; 201 | } 202 | 203 | @media (max-width: 768px) { 204 | .ngx-sm-block { 205 | display: block !important; 206 | } 207 | 208 | .ngx-d-none { 209 | display: none !important; 210 | } 211 | } -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/component/ngx-audio-player/ngx-audio-player.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 109 | 119 | 120 |
121 |
122 |
123 | 124 | {{currentTime | secondsToMinutes}} 125 | 126 |
127 | 128 | 132 | 135 | 136 |
137 | 138 | -{{duration-currentTime | secondsToMinutes }} 139 | 140 | 141 | STREAM 142 | 143 |
144 |
145 |
146 | 183 | 204 |
205 | 206 | 207 |
208 |
209 |
{{ 210 | tracks[currentIndex]?.title }}{{ 211 | displayArtist && tracks[currentIndex]?.artist ? 212 | ' | ' + tracks[currentIndex]?.artist : ''}}
213 | 214 | {{ 215 | tracks[currentIndex]?.title 216 | }}{{ displayArtist && tracks[currentIndex]?.artist ? ' | ' + tracks[currentIndex]?.artist : ''}} 217 | 218 |
219 |
220 |
221 |
222 | 223 | 224 | 225 | 226 | {{tableHeader}} 227 | 228 | 229 | 230 | 231 | 234 | 235 | 236 | 237 | 240 | 241 | 242 | 243 | 246 | 247 | 248 | 249 | 264 | 265 | 266 | 267 |
{{titleHeader}} 232 | {{element.title}} 233 | {{artistHeader}} 238 | {{element?.artist ? element?.artist : ''}} 239 | {{durationHeader}} 244 | {{element?.duration ? (element?.duration | secondsToMinutes) : ''}} 245 | 250 |
251 | 254 | 262 |
263 |
268 | 269 |
270 |
-------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/component/ngx-audio-player/ngx-audio-player.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import 'hammerjs'; 3 | import { AudioPlayerComponent } from './ngx-audio-player.component'; 4 | 5 | import { MatExpansionModule } from '@angular/material/expansion'; 6 | import { MatFormFieldModule } from '@angular/material/form-field'; 7 | import { MatPaginatorModule } from '@angular/material/paginator'; 8 | import { MatSliderModule } from '@angular/material/slider'; 9 | import { MatTableModule } from '@angular/material/table'; 10 | import { MatCardModule } from '@angular/material/card'; 11 | 12 | import { AudioPlayerService } from '../../service/audio-player-service/audio-player.service'; 13 | import { SecondsToMinutesPipe } from '../../pipe/seconds-to-minutes'; 14 | import { FormsModule } from '@angular/forms'; 15 | import { mockPlaylist } from '../../model/track.model.mock'; 16 | import { ElementRef, Injectable, Component, Type } from '@angular/core'; 17 | import { Track } from '../../model/track.model'; 18 | import { MatIconModule } from '@angular/material/icon'; 19 | import { NgxAudioPlayerModule } from '../../ngx-audio-player.module'; 20 | import { By } from '@angular/platform-browser'; 21 | 22 | @Injectable() 23 | export class MockElementRef { 24 | nativeElement: {}; 25 | } 26 | 27 | @Injectable() 28 | export class MockService extends AudioPlayerService { 29 | playlist = mockPlaylist; 30 | } 31 | 32 | describe('AudioPlayerComponent', () => { 33 | function createComponent(componentType: Type, extraDeclarations: Type[] = []) { 34 | TestBed.configureTestingModule({ 35 | imports: [MatIconModule, MatSliderModule, MatCardModule, MatFormFieldModule, MatExpansionModule, 36 | MatPaginatorModule, MatTableModule, FormsModule, NgxAudioPlayerModule], 37 | declarations: [componentType, ...extraDeclarations], 38 | providers: [{ provide: ElementRef, useClass: MockElementRef }, { provide: AudioPlayerService, useClass: MockService }] 39 | }).compileComponents(); 40 | 41 | return TestBed.createComponent(componentType); 42 | } 43 | 44 | let component: AudioPlayerComponent; 45 | let fixture: ComponentFixture; 46 | 47 | describe('Component', () => { 48 | beforeEach((() => { 49 | const MATERIAL_MODULES = [ 50 | MatCardModule, 51 | MatExpansionModule, 52 | MatFormFieldModule, 53 | MatPaginatorModule, 54 | MatSliderModule, 55 | MatTableModule 56 | ]; 57 | 58 | TestBed.configureTestingModule({ 59 | declarations: [AudioPlayerComponent, SecondsToMinutesPipe], 60 | imports: [ 61 | MatIconModule, 62 | FormsModule, 63 | MATERIAL_MODULES 64 | ], 65 | providers: [{ provide: ElementRef, useClass: MockElementRef }, { provide: AudioPlayerService, useClass: MockService }] 66 | }) 67 | .compileComponents(); 68 | })); 69 | 70 | beforeEach(() => { 71 | fixture = TestBed.createComponent(AudioPlayerComponent); 72 | component = fixture.componentInstance; 73 | component.playlist = mockPlaylist; 74 | 75 | fixture.detectChanges(); 76 | }); 77 | 78 | it('should create', () => { 79 | expect(component).toBeTruthy(); 80 | }); 81 | 82 | it('should have play button', () => { 83 | const playButton = By.css('.play-track'); 84 | expect(playButton).toBeDefined(); 85 | }); 86 | 87 | it('should select next song correctly', () => { 88 | component.nextSong(); 89 | expect(component.currentIndex).toEqual(1); 90 | component.nextSong(); 91 | expect(component.currentIndex).toEqual(2); 92 | component.nextSong(); 93 | expect(component.currentIndex).toEqual(3); 94 | component.nextSong(); 95 | expect(component.currentIndex).toEqual(0); 96 | }); 97 | 98 | it('should select previous song correctly', () => { 99 | component.previousSong(); 100 | expect(component.currentIndex).toEqual(3); 101 | component.previousSong(); 102 | expect(component.currentIndex).toEqual(2); 103 | component.previousSong(); 104 | expect(component.currentIndex).toEqual(1); 105 | component.previousSong(); 106 | expect(component.currentIndex).toEqual(0); 107 | }); 108 | 109 | it('should select track correctly', () => { 110 | component.selectTrack(2); 111 | expect(component.currentIndex).toEqual(1); 112 | }); 113 | 114 | }); 115 | 116 | describe('Advanced Audio Player', () => { 117 | beforeEach(() => { 118 | fixture = createComponent(AngularAudioPlayerApp); 119 | component = fixture.componentInstance; 120 | fixture.detectChanges(); 121 | }); 122 | 123 | it('should create', () => { 124 | expect(component).toBeTruthy(); 125 | }); 126 | 127 | }); 128 | 129 | /** Test Ngx Player */ 130 | @Component({ 131 | template: `` 132 | }) 133 | class AngularAudioPlayerApp { 134 | // Material Style Advance Audio Player Playlist 135 | msaapPlaylist: Track[] = mockPlaylist; 136 | 137 | msaapDisplayTitle = true; 138 | msaapDisplayPlayList = true; 139 | pageSizeOptions = [2, 4, 6]; 140 | 141 | msaapDisablePositionSlider = false; 142 | msaapDisplayVolumeControls = true; 143 | } 144 | 145 | }); 146 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/component/ngx-audio-player/ngx-audio-player.component.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Component, OnInit, Input, ViewChild, Output, ElementRef, OnChanges, SimpleChanges, EventEmitter } from '@angular/core'; 3 | import { Track } from '../../model/track.model'; 4 | import { MatSlider } from '@angular/material/slider'; 5 | import { MatTableDataSource } from '@angular/material/table'; 6 | import { MatPaginator } from '@angular/material/paginator'; 7 | import { AudioPlayerService } from '../../service/audio-player-service/audio-player.service'; 8 | 9 | class EventResponse { 10 | event: string; 11 | track: Track; 12 | } 13 | 14 | @Component({ 15 | selector: 'mat-advanced-audio-player,ngx-audio-player', 16 | templateUrl: './ngx-audio-player.component.html', 17 | styleUrls: ['./ngx-audio-player.component.css'] 18 | }) 19 | export class AudioPlayerComponent implements OnInit, OnChanges { 20 | 21 | audioPlayerService: AudioPlayerService; 22 | constructor(elem: ElementRef) { 23 | if (elem.nativeElement.tagName.toLowerCase() === 'mat-advanced-audio-player') { 24 | console.warn(`'mat-advanced-audio-player' selector is deprecated; use 'ngx-audio-player' instead.`); 25 | } 26 | this.audioPlayerService = new AudioPlayerService(); 27 | } 28 | 29 | @Input() 30 | set playlist(playlist: Track[]) { 31 | this.audioPlayerService.setPlaylist(playlist); 32 | } 33 | 34 | @ViewChild(MatPaginator, { static: false }) set matPaginator(mp: MatPaginator) { 35 | this.paginator = mp; 36 | this.setDataSourceAttributes(); 37 | } 38 | 39 | 40 | displayedColumns: string[]; 41 | dataSource = new MatTableDataSource(); 42 | paginator: MatPaginator; 43 | 44 | timeLineDuration: MatSlider; 45 | mediaType: string = null; 46 | 47 | tracks: Track[] = []; 48 | 49 | // all || one || none 50 | @Input() repeat: "all" | "one" | "none" = 'all'; 51 | @Input() displayTitle = true; 52 | @Input() displayPlaylist = true; 53 | @Input() displayVolumeControls = true; 54 | @Input() displayVolumeSlider = false; 55 | @Input() displayRepeatControls = true; 56 | @Input() pageSizeOptions = [10, 20, 30]; 57 | @Input() expanded = true; 58 | @Input() autoPlay = false; 59 | @Input() disablePositionSlider = false; 60 | @Input() displayArtist = false; 61 | @Input() displayDuration = false; 62 | 63 | // Support for internationalization 64 | @Input() tableHeader = 'Playlist'; 65 | @Input() titleHeader = 'Title'; 66 | @Input() artistHeader = 'Artist'; 67 | @Input() durationHeader = 'Duration'; 68 | 69 | currentIndex = 0; 70 | 71 | @Output() 72 | trackPlaying: EventEmitter = new EventEmitter(); 73 | @Output() 74 | trackPaused: EventEmitter = new EventEmitter(); 75 | @Output() 76 | trackEnded: EventEmitter = new EventEmitter(); 77 | @Output() 78 | nextTrackRequested: EventEmitter = new EventEmitter(); 79 | @Output() 80 | previousTrackRequested: EventEmitter = new EventEmitter(); 81 | @Output() 82 | trackSelected: EventEmitter = new EventEmitter(); 83 | 84 | @ViewChild('audioPlayer', { static: true }) player: ElementRef; 85 | 86 | iOS = (/iPad|iPhone|iPod/.test(navigator.platform) 87 | || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)); 88 | 89 | loaderDisplay = false; 90 | isPlaying = false; 91 | currentTime = 0; 92 | volume = 0.1; 93 | toggledVolume = 1.0; 94 | duration = 0.01; 95 | 96 | private startOffsetValue = 0; 97 | @Input() 98 | set startOffset(seconds: number) { 99 | this.startOffsetValue = seconds; 100 | this.player.nativeElement.currentTime = seconds; 101 | } 102 | get startOffset(): number { 103 | return this.startOffsetValue; 104 | } 105 | 106 | @Input() 107 | public endOffset = 0; 108 | 109 | /** 110 | * Allow to start the current track 111 | */ 112 | @Input() 113 | public play() { 114 | if (!this.isPlaying) { 115 | setTimeout(() => { 116 | this.isPlaying = true; 117 | this.player.nativeElement.play(); 118 | }, 50); 119 | } 120 | } 121 | 122 | /** 123 | * Allow to pause the current track 124 | */ 125 | @Input() 126 | public pause() { 127 | if (this.isPlaying) { 128 | setTimeout(() => { 129 | this.isPlaying = false; 130 | this.player.nativeElement.pause(); 131 | }, 50); 132 | } 133 | } 134 | 135 | /** 136 | * Allow to stop the current track 137 | */ 138 | @Input() 139 | public stop() { 140 | setTimeout(() => { 141 | this.isPlaying = false; 142 | this.player.nativeElement.pause(); 143 | this.player.nativeElement.currentTime = 0; 144 | }, 50); 145 | } 146 | 147 | currTimePosChanged(event) { 148 | this.player.nativeElement.currentTime = event.value; 149 | } 150 | 151 | currVolumeChanged(event) { 152 | this.volume = event.value; 153 | this.toggledVolume = event.value; 154 | this.player.nativeElement.volume = event.value; 155 | } 156 | 157 | bindPlayerEvent(): void { 158 | 159 | this.player.nativeElement.addEventListener('playing', () => { 160 | this.isPlaying = true; 161 | this.emitEventResponse("TrackPlaying", this.trackPlaying); 162 | this.mediaType = ''; 163 | if (this.tracks[this.currentIndex].mediaType !== 'stream') { 164 | this.duration = Math.floor(this.player.nativeElement.duration); 165 | } else { 166 | this.mediaType = 'stream'; 167 | } 168 | }); 169 | this.player.nativeElement.addEventListener('pause', () => { 170 | this.isPlaying = false; 171 | this.emitEventResponse("TrackPaused", this.trackPaused); 172 | }); 173 | this.player.nativeElement.addEventListener('timeupdate', () => { 174 | this.currentTime = Math.floor(this.player.nativeElement.currentTime); 175 | // BUG: Commenting for `ended` event not firing #66 176 | // if (this.currentTime >= this.duration - this.endOffset) { 177 | // this.player.nativeElement.pause(); 178 | // } 179 | }); 180 | this.player.nativeElement.addEventListener('volume', () => { 181 | this.volume = Math.floor(this.player.nativeElement.volume); 182 | }); 183 | if (!this.iOS) { 184 | this.player.nativeElement.addEventListener('loadstart', () => { 185 | this.loaderDisplay = true; 186 | }); 187 | } 188 | this.player.nativeElement.addEventListener('loadedmetadata', () => { 189 | this.loaderDisplay = false; 190 | if (this.tracks[this.currentIndex].mediaType !== 'stream') { 191 | this.duration = Math.floor(this.player.nativeElement.duration); 192 | } else { 193 | this.duration = 0; 194 | } 195 | }); 196 | this.player.nativeElement.addEventListener('ended', () => { 197 | this.emitEventResponse("TrackEnded", this.trackEnded); 198 | }); 199 | 200 | } 201 | emitEventResponse(event: string, emitter: EventEmitter) { 202 | let eventResponse: EventResponse = new EventResponse(); 203 | eventResponse.event = event; 204 | eventResponse.track = this.audioPlayerService.currentTrack; 205 | emitter.emit(eventResponse); 206 | } 207 | 208 | playBtnHandler(): void { 209 | if (this.loaderDisplay) { 210 | return; 211 | } 212 | if (this.player.nativeElement.paused) { 213 | if (this.currentTime >= this.duration - this.endOffset) { 214 | this.player.nativeElement.currentTime = this.startOffset; 215 | } else { 216 | this.player.nativeElement.currentTime = this.currentTime; 217 | } 218 | 219 | this.player.nativeElement.play(); 220 | } else { 221 | this.currentTime = this.player.nativeElement.currentTime; 222 | this.player.nativeElement.pause(); 223 | } 224 | } 225 | 226 | triggerPlay(track?: Track): void { 227 | 228 | if (track) { 229 | this.startOffset = track.startOffset || 0; 230 | this.endOffset = track.endOffset || 0; 231 | } 232 | 233 | setTimeout(() => { 234 | this.player.nativeElement.play(); 235 | }, 50); 236 | 237 | } 238 | 239 | toggleVolume() { 240 | if (this.volume === 0) { 241 | this.setVolume(this.toggledVolume); 242 | } else { 243 | this.toggledVolume = this.volume; 244 | this.setVolume(0); 245 | } 246 | } 247 | 248 | toggleRepeat() { 249 | if (this.repeat === 'none') { 250 | this.repeat = 'all'; 251 | } else if (this.repeat === 'all') { 252 | if (this.tracks.length > 1) { 253 | this.repeat = 'one'; 254 | } else { 255 | this.repeat = 'none'; 256 | } 257 | } else if (this.repeat === 'one' && this.tracks.length > 1) { 258 | this.repeat = 'none'; 259 | } 260 | } 261 | 262 | private setVolume(vol) { 263 | this.volume = vol; 264 | this.player.nativeElement.volume = this.volume; 265 | } 266 | 267 | ngOnInit() { 268 | 269 | this.bindPlayerEvent(); 270 | 271 | // auto play next track 272 | this.player.nativeElement.addEventListener('ended', () => { 273 | if (this.mediaType !== 'stream' && this.checkIfSongHasStartedSinceAtleastTwoSeconds()) { 274 | if (this.repeat === 'all') { 275 | this.nextSong(); 276 | } else if (this.repeat === 'one') { 277 | this.triggerPlay(); 278 | } else if (this.repeat === 'none') { 279 | // Do nothing 280 | } 281 | } else { 282 | this.triggerPlay(); 283 | } 284 | }); 285 | 286 | this.player.nativeElement.addEventListener('timeupdate', () => { 287 | this.audioPlayerService.setCurrentTime(this.player.nativeElement.currentTime); 288 | }); 289 | 290 | // Subscribe to playlist observer from AudioPlayerService and 291 | // update the playlist within MatAdvancedAudioPlayerComponent 292 | this.audioPlayerService.getPlaylist().subscribe(tracks => { 293 | if (tracks !== null && tracks.length > 0) { 294 | this.tracks = tracks; 295 | this.initialize(); 296 | } 297 | }); 298 | 299 | } 300 | 301 | ngOnChanges(changes: SimpleChanges) { 302 | if (changes.hasOwnProperty('displayArtist') || changes.hasOwnProperty('displayDuration')) { 303 | this.buildDisplayedColumns(); 304 | } 305 | } 306 | 307 | private buildDisplayedColumns() { 308 | this.displayedColumns = ['title']; 309 | if (this.displayArtist) { 310 | this.displayedColumns.push('artist'); 311 | } 312 | if (this.displayDuration) { 313 | this.displayedColumns.push('duration'); 314 | } 315 | this.displayedColumns.push('status'); 316 | } 317 | 318 | initialize() { 319 | this.buildDisplayedColumns(); 320 | 321 | // populate indexs for the track and configure 322 | // material table data source and paginator 323 | this.setDataSourceAttributes(); 324 | 325 | 326 | this.player.nativeElement.currentTime = this.startOffset; 327 | this.updateCurrentTrack(); 328 | 329 | if (this.autoPlay) { 330 | this.triggerPlay(); 331 | } 332 | } 333 | 334 | setDataSourceAttributes() { 335 | let index = 1; 336 | if (this.tracks) { 337 | this.tracks.forEach((track: Track) => { 338 | track.index = index++; 339 | }); 340 | this.dataSource = new MatTableDataSource(this.tracks); 341 | this.dataSource.paginator = this.paginator; 342 | } 343 | } 344 | 345 | nextSong(): void { 346 | if (this.displayPlaylist === true 347 | && (((this.currentIndex + 1) % this.paginator.pageSize) === 0 348 | || (this.currentIndex + 1) === this.paginator.length)) { 349 | if (this.paginator.hasNextPage()) { 350 | this.paginator.nextPage(); 351 | } else if (!this.paginator.hasNextPage()) { 352 | this.paginator.firstPage(); 353 | } 354 | } 355 | this.currentTime = 0; 356 | this.duration = 0.01; 357 | if ((this.currentIndex + 1) >= this.tracks.length) { 358 | this.currentIndex = 0; 359 | } else { 360 | this.currentIndex++; 361 | } 362 | this.updateCurrentTrack(); 363 | this.emitEventResponse("NextTrackRequested", this.nextTrackRequested); 364 | this.triggerPlay(); 365 | } 366 | 367 | previousSong(): void { 368 | this.currentTime = 0; 369 | this.duration = 0.01; 370 | if (!this.checkIfSongHasStartedSinceAtleastTwoSeconds()) { 371 | if (this.displayPlaylist === true 372 | && (((this.currentIndex) % this.paginator.pageSize) === 0 373 | || (this.currentIndex === 0))) { 374 | if (this.paginator.hasPreviousPage()) { 375 | this.paginator.previousPage(); 376 | } else if (!this.paginator.hasPreviousPage()) { 377 | this.paginator.lastPage(); 378 | } 379 | } 380 | if ((this.currentIndex - 1) < 0) { 381 | this.currentIndex = (this.tracks.length - 1); 382 | } else { 383 | this.currentIndex--; 384 | } 385 | } else { 386 | this.resetSong(); 387 | } 388 | this.updateCurrentTrack(); 389 | this.emitEventResponse("PreviousTrackRequested", this.previousTrackRequested); 390 | this.triggerPlay(); 391 | } 392 | 393 | resetSong(): void { 394 | this.player.nativeElement.src = this.tracks[this.currentIndex].link; 395 | } 396 | 397 | selectTrack(index: number): void { 398 | this.currentIndex = index - 1; 399 | this.updateCurrentTrack(); 400 | this.emitEventResponse("TrackSelected", this.trackSelected); 401 | this.triggerPlay(); 402 | } 403 | 404 | checkIfSongHasStartedSinceAtleastTwoSeconds(): boolean { 405 | return this.player.nativeElement.currentTime > 2; 406 | } 407 | 408 | updateCurrentTrack() { 409 | this.audioPlayerService.setCurrentTrack(this.tracks[this.currentIndex]); 410 | } 411 | 412 | } 413 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/model/track.model.mock.ts: -------------------------------------------------------------------------------- 1 | import { Track } from './track.model'; 2 | 3 | const fmaBaseUrl = 'https://files.freemusicarchive.org/storage-freemusicarchive-org/music'; 4 | export const mockTrack1: Track = new Track(); 5 | mockTrack1.link = `${fmaBaseUrl}/WFMU/Broke_For_Free/Directionless_EP/Broke_For_Free_-_01_-_Night_Owl.mp3`; 6 | mockTrack1.title = 'Night Owl (by Broke For Free)'; 7 | 8 | export const mockPlaylist: Track[] = [ 9 | { 10 | title: '1400 (by Yung Kartz)', 11 | link: `${fmaBaseUrl}/no_curator/Yung_Kartz/August_2018/Yung_Kartz_-_10_-_1400.mp3` 12 | }, 13 | { 14 | title: 'Epic Song (by BoxCat Games)', 15 | link: `${fmaBaseUrl}/ccCommunity/BoxCat_Games/Nameless_The_Hackers_RPG_Soundtrack/BoxCat_Games_-_10_-_Epic_Song.mp3` 16 | }, 17 | { 18 | title: 'Hachiko (The Faithful Dog) (by The Kyoto)', 19 | link: `${fmaBaseUrl}/ccCommunity/The_Kyoto_Connection/Wake_Up/The_Kyoto_Connection_-_09_-_Hachiko_The_Faithtful_Dog.mp3` 20 | }, 21 | { 22 | title: 'Starling (by Podington Bear)', 23 | link: `${fmaBaseUrl}/Music_for_Video/Podington_Bear/Solo_Instruments/Podington_Bear_-_Starling.mp3` 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/model/track.model.ts: -------------------------------------------------------------------------------- 1 | export class Track { 2 | index?: number; 3 | link: string; 4 | title: string; 5 | mediaType?: string; 6 | startOffset?: number; 7 | endOffset?: number; 8 | duration?: number; 9 | artist?: string; 10 | public toString = (): string => { 11 | return `Track (index: ${this.index}, title: ${this.title})`; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/ngx-audio-player.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { MatSliderModule } from '@angular/material/slider'; 4 | import { MatPaginatorModule } from '@angular/material/paginator'; 5 | import { MatTableModule } from '@angular/material/table'; 6 | import { MatFormFieldModule } from '@angular/material/form-field'; 7 | import { MatExpansionModule } from '@angular/material/expansion'; 8 | import { MatCardModule } from '@angular/material/card'; 9 | import { MatButtonModule } from '@angular/material/button'; 10 | 11 | import { CommonModule } from '@angular/common'; 12 | import { SecondsToMinutesPipe } from './pipe/seconds-to-minutes'; 13 | 14 | import { FormsModule } from '@angular/forms'; 15 | import { MatIconModule } from '@angular/material/icon'; 16 | import { AudioPlayerComponent } from './component/ngx-audio-player/ngx-audio-player.component'; 17 | 18 | @NgModule({ 19 | declarations: [SecondsToMinutesPipe, AudioPlayerComponent], 20 | imports: [CommonModule, FormsModule, MatButtonModule, MatCardModule, MatTableModule, MatFormFieldModule, 21 | MatSliderModule, MatExpansionModule, MatPaginatorModule, MatIconModule], 22 | exports: [AudioPlayerComponent] 23 | }) 24 | export class NgxAudioPlayerModule { 25 | } 26 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/pipe/seconds-to-minutes.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { TestBed } from '@angular/core/testing'; 3 | import { SecondsToMinutesPipe } from './seconds-to-minutes'; 4 | 5 | describe('Pipe: Default', () => { 6 | let pipe: SecondsToMinutesPipe; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | pipe = new SecondsToMinutesPipe(); 11 | }); 12 | 13 | it('should transform 125 as 02:05', () => { 14 | expect(pipe.transform(125)).toBe('02:05'); 15 | }); 16 | 17 | it('should transform 3983 as 01:06:23', () => { 18 | expect(pipe.transform(3983)).toBe('01:06:23'); 19 | }); 20 | 21 | it('should transform 279 as 04:39', () => { 22 | expect(pipe.transform(279)).toBe('04:39'); 23 | }); 24 | 25 | it('should transform 227 as 03:47', () => { 26 | expect(pipe.transform(227)).toBe('03:47'); 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/pipe/seconds-to-minutes.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | /* 3 | * Transform seconds to minutes:seconds 4 | * Example : 270 -> 02:30 5 | */ 6 | @Pipe({ name: 'secondsToMinutes' }) 7 | export class SecondsToMinutesPipe implements PipeTransform { 8 | transform(time: number): string { 9 | const hours = ('0' + Math.floor(time / 3600)).slice(-2); 10 | const minutes = ('0' + Math.floor((time % 3600) / 60)).slice(-2); 11 | const seconds = ('0' + time % 60).slice(-2); 12 | if (hours !== '00') { return `${hours}:${minutes}:${seconds}`; } 13 | return `${minutes}:${seconds}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/service/audio-player-service/audio-player.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AudioPlayerService } from './audio-player.service'; 4 | import { mockPlaylist } from '../../model/track.model.mock'; 5 | 6 | describe('AudioPlayerService', () => { 7 | let service: AudioPlayerService; 8 | 9 | beforeEach(() => TestBed.configureTestingModule({ 10 | providers: [AudioPlayerService] 11 | }).compileComponents()); 12 | 13 | beforeEach(() => { 14 | service = TestBed.inject(AudioPlayerService); 15 | service.setPlaylist(mockPlaylist); 16 | }); 17 | 18 | it('should be created', () => { 19 | expect(service).toBeTruthy(); 20 | }); 21 | 22 | it('should set playlist correctly', () => { 23 | expect(service.getPlaylist().subscribe(playlist => { 24 | return playlist.length === 3; 25 | })); 26 | }); 27 | 28 | it('should get playlist correctly', () => { 29 | expect(service.getPlaylist().subscribe((playlist) => { 30 | return playlist[0].title === (mockPlaylist[0].title); 31 | })); 32 | 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/lib/service/audio-player-service/audio-player.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, Observable } from 'rxjs'; 3 | import { Track } from '../../model/track.model'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class AudioPlayerService { 9 | 10 | // Dynamic update of playlist 11 | tracks: Track[] = []; 12 | playlistSubject$: BehaviorSubject = 13 | new BehaviorSubject(this.tracks); 14 | 15 | // Get the current track 16 | currentTrack: Track = null; 17 | currentTrackSubject$: BehaviorSubject = 18 | new BehaviorSubject(this.currentTrack); 19 | 20 | // Get the current time 21 | currentTime: any = null; 22 | currentTimeSubject$: BehaviorSubject = 23 | new BehaviorSubject(this.currentTime); 24 | 25 | setPlaylist(tracks: Track[]) { 26 | this.tracks = tracks; 27 | this.playlistSubject$.next(this.tracks); 28 | } 29 | 30 | getPlaylist(): Observable { 31 | return this.playlistSubject$.asObservable(); 32 | } 33 | 34 | setCurrentTrack(currentTrack: Track) { 35 | this.currentTrack = currentTrack; 36 | this.currentTrackSubject$.next(this.currentTrack); 37 | } 38 | 39 | getCurrentTrack(): Observable { 40 | return this.currentTrackSubject$.asObservable(); 41 | } 42 | 43 | setCurrentTime(currentTime: any) { 44 | this.currentTime = currentTime; 45 | this.currentTimeSubject$.next(this.currentTime); 46 | } 47 | 48 | getCurrentTime(): Observable { 49 | return this.currentTimeSubject$.asObservable(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-audio-player 3 | */ 4 | 5 | export * from './lib/component/ngx-audio-player/ngx-audio-player.component'; 6 | export * from './lib/ngx-audio-player.module'; 7 | export * from './lib/model/track.model'; 8 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js'; 5 | import 'zone.js/testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declarationMap": true, 6 | "target": "es2015", 7 | "module": "es2015", 8 | "moduleResolution": "node", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "inlineSources": true, 12 | "emitDecoratorMetadata": true, 13 | "experimentalDecorators": true, 14 | "importHelpers": true, 15 | "types": [], 16 | "lib": [ 17 | "dom", 18 | "es2018" 19 | ] 20 | }, 21 | "angularCompilerOptions": { 22 | "annotateForClosureCompiler": false, 23 | "skipTemplateCodegen": true, 24 | "strictMetadataEmit": true, 25 | "fullTemplateTypeCheck": true, 26 | "strictInjectionParameters": true, 27 | "enableResourceInlining": true 28 | }, 29 | "exclude": [ 30 | "src/test.ts", 31 | "**/*.spec.ts" 32 | ] 33 | } -------------------------------------------------------------------------------- /projects/ngx-audio-player/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "enableIvy": false 8 | } 9 | } -------------------------------------------------------------------------------- /projects/ngx-audio-player/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "src/test.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/ngx-audio-player/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "prettier": true, 5 | "directive-selector": [ 6 | true, 7 | "attribute", 8 | "lib", 9 | "camelCase" 10 | ], 11 | "component-selector": [ 12 | true, 13 | "element", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | app-navbar { 2 | position: relative; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | z-index: 2; 7 | } -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(waitForAsync(() => { 7 | TestBed.configureTestingModule({ 8 | declarations: [AppComponent], 9 | imports: [RouterTestingModule], 10 | providers: [] 11 | }).compileComponents(); 12 | })); 13 | it('should create the ngx-audio-player-demo', waitForAsync(() => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | })); 18 | it(`should have title as 'ngx-audio-player-demo'`, waitForAsync(() => { 19 | const fixture = TestBed.createComponent(AppComponent); 20 | const app = fixture.debugElement.componentInstance; 21 | expect(app.title).toEqual('ngx-audio-player-demo'); 22 | })); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { DOCUMENT } from '@angular/common'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | title = 'ngx-audio-player-demo'; 11 | 12 | constructor(@Inject(DOCUMENT) private document: Document) { } 13 | 14 | loadStyle(styleName: string) { 15 | const head = this.document.getElementsByTagName('head')[0]; 16 | 17 | const themeLink = this.document.getElementById( 18 | 'client-theme' 19 | ) as HTMLLinkElement; 20 | if (themeLink) { 21 | themeLink.href = styleName; 22 | } else { 23 | const style = this.document.createElement('link'); 24 | style.id = 'client-theme'; 25 | style.rel = 'stylesheet'; 26 | style.href = `${styleName}`; 27 | 28 | head.appendChild(style); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { Routes, RouterModule } from '@angular/router'; 4 | 5 | import { AppComponent } from './app.component'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | 8 | import { MatToolbarModule } from '@angular/material/toolbar'; 9 | import { MatIconModule } from '@angular/material/icon'; 10 | import { MatRadioModule } from '@angular/material/radio'; 11 | import { MatCheckboxModule } from '@angular/material/checkbox'; 12 | import { MatSlideToggleModule } from '@angular/material/slide-toggle'; 13 | import { MatCardModule } from '@angular/material/card'; 14 | 15 | import { HomeComponent } from './pages/home/home.component'; 16 | import { GettingStartedComponent } from './pages/gettingstarted/gettingstarted.component'; 17 | 18 | import { HttpClientModule } from '@angular/common/http'; 19 | import { NgxAudioPlayerModule } from 'projects/ngx-audio-player/src/public_api'; 20 | import { MatButtonModule } from '@angular/material/button'; 21 | 22 | import { NavBarModule } from './shared/navbar'; 23 | import { FooterModule } from './shared/footer'; 24 | 25 | export const appRoutes: Routes = [ 26 | { path: '', component: HomeComponent, data: { title: 'Home' } }, 27 | { 28 | path: 'guide/getting-started', 29 | component: GettingStartedComponent, 30 | data: { title: 'Getting Started' } 31 | } 32 | ]; 33 | 34 | @NgModule({ 35 | declarations: [AppComponent, HomeComponent, GettingStartedComponent], 36 | imports: [ 37 | HttpClientModule, 38 | BrowserModule, 39 | MatCardModule, 40 | MatToolbarModule, 41 | MatIconModule, 42 | MatButtonModule, 43 | MatRadioModule, 44 | MatCheckboxModule, 45 | MatSlideToggleModule, 46 | MatButtonModule, 47 | BrowserAnimationsModule, 48 | NavBarModule, FooterModule, 49 | NgxAudioPlayerModule, 50 | RouterModule.forRoot(appRoutes, { useHash: false }) 51 | ], 52 | providers: [], 53 | bootstrap: [AppComponent] 54 | }) 55 | export class AppModule { 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/app/pages/gettingstarted/gettingstarted.component.css: -------------------------------------------------------------------------------- 1 | #matLink { 2 | text-decoration: none; 3 | } 4 | 5 | .mat-h1 { 6 | font-style: normal; 7 | font-variant-ligatures: normal; 8 | font-variant-caps: normal; 9 | font-variant-numeric: normal; 10 | font-variant-east-asian: normal; 11 | font-weight: 300; 12 | font-stretch: normal; 13 | font-size: 18px; 14 | line-height: 16px; 15 | font-family: Roboto, 'Helvetica Neue', sans-serif; 16 | } 17 | 18 | .header { 19 | padding-top: 110px !important; 20 | padding: 30px; 21 | } 22 | 23 | pre code { 24 | padding: 0; 25 | font-size: inherit; 26 | color: inherit; 27 | background-color: transparent; 28 | border-radius: 0; 29 | } 30 | 31 | code { 32 | padding: .2rem .4rem; 33 | font-size: 90%; 34 | color: #bd4147; 35 | background-color: #f7f7f9; 36 | border-radius: .25rem; 37 | } 38 | 39 | code, 40 | kbd, 41 | pre, 42 | samp { 43 | font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 44 | } 45 | 46 | code, 47 | kbd, 48 | samp { 49 | font-family: monospace, monospace; 50 | font-size: 1em; 51 | } 52 | 53 | pre { 54 | max-width: 100%; 55 | margin-bottom: 1.2rem; 56 | border-radius: 3px; 57 | padding: 0.6rem 1.2rem; 58 | overflow-x: auto; 59 | -webkit-overflow-scrolling: touch; 60 | } 61 | 62 | .rougeHighlight pre, 63 | .rougeHighlight, 64 | pre { 65 | background: #292b2c; 66 | -webkit-font-smoothing: antialiased; 67 | border-left: 4px solid #2c8ebb; 68 | color: #ddd; 69 | } 70 | 71 | pre { 72 | display: block; 73 | margin-top: 0; 74 | margin-bottom: 1rem; 75 | font-size: 90%; 76 | color: #eee; 77 | } 78 | 79 | code, 80 | kbd, 81 | pre, 82 | samp { 83 | font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 84 | } 85 | 86 | pre { 87 | margin-top: 0; 88 | margin-bottom: 1rem; 89 | overflow: auto; 90 | } 91 | 92 | pre { 93 | font-family: monospace, monospace; 94 | font-size: 1em; 95 | } 96 | 97 | pre, 98 | xmp, 99 | plaintext, 100 | listing { 101 | display: block; 102 | font-family: monospace; 103 | white-space: pre; 104 | margin: 1em 0px; 105 | } 106 | 107 | .hljs-tag, 108 | .hljs-keyword, 109 | .hljs-selector-tag, 110 | .hljs-literal, 111 | .hljs-strong, 112 | .hljs-name { 113 | color: #f92672; 114 | } 115 | 116 | .hljs-string, 117 | .hljs-bullet, 118 | .hljs-subst, 119 | .hljs-title, 120 | .hljs-section, 121 | .hljs-emphasis, 122 | .hljs-type, 123 | .hljs-built_in, 124 | .hljs-builtin-name, 125 | .hljs-selector-attr, 126 | .hljs-selector-pseudo, 127 | .hljs-addition, 128 | .hljs-variable, 129 | .hljs-template-tag, 130 | .hljs-template-variable { 131 | color: #a6e22e; 132 | } 133 | 134 | .hljs-comment, 135 | .hljs-quote, 136 | .hljs-deletion, 137 | .hljs-meta { 138 | color: #75715e; 139 | } 140 | 141 | ::ng-deep a:hover { 142 | text-decoration: none !important; 143 | } -------------------------------------------------------------------------------- /src/app/pages/gettingstarted/gettingstarted.component.html: -------------------------------------------------------------------------------- 1 |

Getting Started

2 |
3 |
4 |
5 |
6 | 7 |
8 |

A library for loading and playing audio using HTML 5 for Angular 7/8/9/10/11/12.

9 | 10 |

Installation

11 |

ngx-audio-player is available via npm and yarn

15 |

Using npm:

16 |
$ npm install ngx-audio-player --save
17 |

Using yarn:

18 |
$ yarn add ngx-audio-player
19 |

Getting Started

20 |

NgxAudioPlayerModule needs Angular Material.
Make sure you have installed below 21 | dependencies with same or higher version than mentioned.

22 |

"@angular/core": "^12.0.0"
23 | "@angular/common": "^12.0.0"
24 | "@angular/material": "^12.0.0"
25 | "rxjs": "^6.6.0"

26 |

Import NgxAudioPlayerModule in the root module(AppModule): 27 |

28 |
// Import library module
 29 | import {{'{'}} NgxAudioPlayerModule {{'}'}} from 'ngx-audio-player';
 32 | 
 33 | @NgModule({{'{'}}
 34 |   imports: [
 35 |     // ...
 36 |     NgxAudioPlayerModule
 37 |   ]
 38 | {{'}'}})
 39 | export class AppModule {{'{'}} {{'}'}}
40 |

Usage

41 | 42 |
43 |

Simple Audio Player

44 |
45 |
HTML
46 |
<ngx-audio-player [autoPlay]="false" muted="muted"
 47 |     
 48 |     [playlist]="mssapPlaylist"
 49 |     [disablePositionSlider]="mssapDisablePositionSlider"
 50 |     [displayRepeatControls]="mssapDisplayRepeatControls"
 51 |     [displayVolumeControls]="mssapDisplayVolumeControls"
 52 |     [displayVolumeSlider]="mssapDisplayVolumeSlider"
 53 | 
 54 |     [displayTitle]="mssapDisplayTitle"
 55 |     
 56 |     (trackEnded)="onEnded($event)">
 57 | 
 58 | </ngx-audio-player>   
 59 | 
 60 | 
61 |
TS
62 |

 63 | import {{'{'}} Track {{'}'}} from 'ngx-audio-player';
 65 |     .   
 66 |     .   
 67 | mssapDisplayTitle = true;
 68 | mssapDisablePositionSlider = true;
 69 | mssapDisplayRepeatControls = true;
 70 | mssapDisplayVolumeControls = true;
 71 | mssapDisplayVolumeSlider = false;
 72 | 
 73 | // Material Style Simple Audio Player
 74 | mssapPlaylist: Track[] = [
 75 |     {{'{'}}
 76 |         title: 'Audio Title',
 77 |         link: 'Link to Audio URL',
 78 |         artist: 'Artist',
 79 |         duration: 'Duration'
 80 |     {{'}'}}
 81 | ];
 82 | 
 83 | // For Streaming Audio From URL 
 84 | // set mediaType = 'stream' 
 85 | mssapPlaylist: Track[] = [
 86 |     {{'{'}}
 87 |         title: 'Audio Title',
 88 |         link: 'Link to Streaming URL',
 89 |         mediaType: 'stream'
 90 |     {{'}'}}
 91 | ];
 92 | 
 93 | // Callback Event
 94 | onEnded(event) {{'{'}}
 95 |     console.log(event);
 96 |     // your logic which needs to
 97 |     // be triggered once the
 98 |     // track ends goes here.
 99 | {{'}'}}
100 |                 
101 | 102 |
103 |

Advanced Audio Player

104 |
105 |
HTML
106 |
107 | 
108 | <ngx-audio-player [autoPlay]="false" muted="muted"
109 | 
110 |     [playlist]="msaapPlaylist"
111 |     [disablePositionSlider]="msaapDisablePositionSlider"
112 |     [displayRepeatControls]="msaapDisplayRepeatControls"
113 |     [displayVolumeControls]="msaapDisplayVolumeControls"
114 |     [displayVolumeSlider]="msaapDisplayVolumeSlider"
115 |     
116 |     [displayTitle]="msaapDisplayTitle"
117 | 
118 |     [displayPlaylist]="msaapDisplayPlayList"
119 |     [pageSizeOptions]="msaapPageSizeOptions"
120 | 
121 |     [tableHeader]="msaapTableHeader"
122 |     [titleHeader]="msaapTitleHeader"
123 |     [artistHeader]="msaapArtistHeader"
124 |     [durationHeader]="msaapDurationHeader"
125 | 
126 |     [displayArtist]="msaapDisplayArtist"
127 |     [displayDuration]="msaapDisplayDuration"
128 |     [expanded]="true"
129 | 
130 |     (trackPlaying)="onTrackPlaying($event)"
131 |     (trackPaused)="onTrackPaused($event)"
132 |     (trackEnded)="onEnded($event)"
133 |     (nextTrackRequested)="onNextTrackRequested($event)"
134 |     (previousTrackRequested)="onPreviousTrackRequested($event)"
135 |     (trackSelected)="onTrackSelected($event)">
136 | 
137 | </ngx-audio-player>
138 | 
139 | 
140 |
TS
141 |

142 | import {{'{'}} Track {{'}'}} from 'ngx-audio-player';
144 | 
145 |  .   
146 |  .   
147 | 
148 | // Main Player Controls
149 | msaapDisplayPlayList = true;
150 | msaapDisablePositionSlider = true;
151 | msaapDisplayRepeatControls = true;
152 | msaapDisplayVolumeControls = true;
153 | msaapDisplayVolumeSlider = false;
154 | 
155 | // Title Marquee
156 | msaapDisplayTitle = true;
157 | 
158 | // Playlist Controls
159 | msaapPageSizeOptions = [2,4,6];
160 | msaapDisplayArtist = false;
161 | msaapDisplayDuration = false;
162 | 
163 | // For Localisation
164 | msaapTableHeader = 'My Playlist';
165 | msaapTitleHeader = 'My Title';
166 | msaapArtistHeader = 'My Artist';
167 | msaapDurationHeader = 'My Duration';
168 | 
169 | 
170 | // Material Style Advance Audio Player Playlist
171 | msaapPlaylist: Track[] = [
172 |     {{'{'}}
173 |         title: 'Audio One Title',
174 |         link: 'Link to Audio One URL',
175 |         artist: 'Artist',
176 |         duration: 'Duration'
177 |     {{'}'}},
178 |     {{'{'}}
179 |         title: 'Audio Two Title',
180 |         link: 'Link to Audio Two URL',
181 |         artist: 'Artist',
182 |         duration: 'Duration'
183 |     {{'}'}},
184 |     {{'{'}}
185 |         title: 'Audio Three Title',
186 |         link: 'Link to Audio Three URL',
187 |         artist: 'Artist',
188 |         duration: 'Duration'
189 |     {{'}'}},
190 | ];
191 | 
192 | // Callback Events
193 | 
194 | onTrackPlaying(event) {{'{'}}
195 |     console.log(event);
196 |     // your logic which needs to
197 |     // be triggered once the
198 |     // track ends goes here.
199 | {{'}'}}
200 | 
201 | 
202 | onTrackPaused(event) {{'{'}}
203 |     console.log(event);
204 |     // your logic which needs to
205 |     // be triggered once the
206 |     // track ends goes here.
207 | {{'}'}}
208 | 
209 | onEnded(event) {{'{'}}
210 |     console.log(event);
211 |     // your logic which needs to
212 |     // be triggered once the
213 |     // track ends goes here.
214 | {{'}'}}
215 | 
216 | onNextTrackRequested(event) {{'{'}}
217 |     console.log(event);
218 |     // your logic which needs to
219 |     // be triggered once the
220 |     // track ends goes here.
221 | {{'}'}}
222 | 
223 | 
224 | onPreviousTrackRequested(event) {{'{'}}
225 |     console.log(event);
226 |     // your logic which needs to
227 |     // be triggered once the
228 |     // track ends goes here.
229 | {{'}'}}
230 | 
231 | onTrackSelected(event) {{'{'}}
232 |     console.log(event);
233 |     // your logic which needs to
234 |     // be triggered once the
235 |     // track ends goes here.
236 | {{'}'}}
237 | 
238 | 239 |
Properties
240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 258 | 259 | 260 | 261 | 262 | 265 | 266 | 267 | 270 | 271 | 272 | 273 | 274 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 |
NameDescriptionType
@Input() playlist: Track[];playlist containing array of title and linkmandatory
@Input() 256 | autoPlay = false; 257 | true - if the audio needs to be played automaticallyoptional
263 |
Player Controls
264 |
@Input() 268 | startOffset = 0; 269 | offset from start of audio file in secondsoptional
@Input() 275 | endOffset = 0; 276 | offset from end of audio file in secondsoptional
@Input() disablePositionSlider = false;true - if the position slider needs to be disabledoptional
@Input() displayRepeatControls = true;false - if the repeat controls needs to be hiddenoptional
@Input() repeat: "all" | "one" | "none" = 'all';repeat all or one or noneoptional
@Input() displayVolumeControls = true;false - if the volume controls needs to be hiddenoptional
@Input() displayVolumeSlider = false;true - if the volume slider should be shownoptional
307 |
Title Marquee Control
308 |
@Input() displayTitle: true;false - if the audio title needs to be hiddenoptional
317 |
Playlist Controls
318 |
@Input() displayPlaylist: true;false - if the playlist needs to be hiddenoptional
@Input() pageSizeOptions = [10, 20, 30];number of items to be displayed in the playlistoptional
@Input() expanded = true;false - if the playlist needs to be minimizedoptional
@Input() displayArtist = false; true - if the artist data is to be shownoptional
@Input() displayDuration = false;true - if the track duration is to be shownoptional
347 |
Localisation Controls
348 |
@Input() tableHeader = 'Playlist';true - if the track duration is to be shownoptional
@Input() titleHeader = 'Title';true - if the track duration is to be shownoptional
@Input() artistHeader = 'Artist';true - if the track duration is to be shownoptional
@Input() durationHeader = 'Duration';true - if the track duration is to be shownoptional
372 |
Callback Events
373 |
@Output() trackPlaying: EventEmitter<EventResponse>triggers when the track starts playingoptional
@Output() trackPaused: EventEmitter<EventResponse>triggers when the track is pausedoptional
@Output() trackEnded: EventEmitter<EventResponse>triggers when the track endsoptional
@Output() nextTrackRequested: EventEmitter<EventResponse>triggers when the next track is requestedoptional
@Output() previousTrackRequested: EventEmitter<EventResponse>triggers when the previous track is requestedoptional
@Output() trackSelected: EventEmitter<EventResponse>triggers when any track is selected from playlistoptional
407 | 408 |

Versioning

409 |

ngx-audio-player will be maintained under the Semantic Versioning guidelines. 410 | Releases will be numbered with the following format:

411 | 412 |

<major>.<minor>.<patch>

413 | 414 |

For more information on SemVer, please visit http://semver.org. 416 |

417 | 418 |

Contributors ✨

419 |

Thanks goes to these wonderful people:

420 | 421 | 422 | 431 | 440 | 449 | 459 | 469 | 479 | 489 | 499 | 509 | 510 |
423 | 424 | 425 |
Edric Chan 426 |

427 | 💻 430 |
432 | 433 | 434 |
RokiFoki 435 |

436 | 💻 439 |
441 | 442 | 443 |
ewwwgiddings 444 |

445 | 📖 448 |
450 | 451 | 452 |
Caleb Crosby 453 |

454 | 💻 457 | 458 |
460 | 461 | 462 |
Shelly 463 |

464 | 💻 467 | 468 |
470 | 471 | 472 |
Simon Reinsch 473 |

474 | 💻 477 | 478 |
480 | 481 | 482 |
AnwarTuha 483 |

484 | 💻 487 | 488 |
490 | 491 | 492 |
Bogdan Baghiu 493 |

494 | 💻 497 | 498 |
500 | 501 | 502 |
Kareem Jeiroudi 503 |

504 | 💻 507 | 508 |
511 |

License

512 |
The MIT License (MIT)
513 |
514 |
-------------------------------------------------------------------------------- /src/app/pages/gettingstarted/gettingstarted.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { GettingStartedComponent } from './gettingstarted.component'; 4 | 5 | describe('GettingStartedComponent', () => { 6 | let component: GettingStartedComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [GettingStartedComponent] 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(GettingStartedComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/pages/gettingstarted/gettingstarted.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-gettingstarted', 5 | templateUrl: './gettingstarted.component.html', 6 | styleUrls: ['./gettingstarted.component.css'] 7 | }) 8 | export class GettingStartedComponent implements OnInit { 9 | constructor() { } 10 | 11 | ngOnInit() { } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/pages/home/_homepage-theme.scss: -------------------------------------------------------------------------------- 1 | #matLink{ 2 | text-decoration: none; 3 | } 4 | 5 | .header { 6 | text-align: center; 7 | padding: 60px; 8 | } 9 | 10 | div > .ngx-demo-content { 11 | margin: 20px 0px!important; 12 | } 13 | 14 | .ngx-demo-block button { 15 | margin-right: 8px; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 |

NGX Audio Player

6 |

Audio Player for Angular

7 |
8 |
9 | Get started 10 |
11 |
12 |
13 |
14 |
15 |
16 |
Controls
17 |
18 |
19 | 21 | 22 | displayTitle: {{msaapDisplayTitle}} 23 |
24 |
25 | 27 | 28 | displayPlayList: {{msaapDisplayPlayList}} 29 |
30 |
31 | 33 | 34 | disablePositionSlider: {{msaapDisablePositionSlider}} 35 |
36 |
37 | 39 | 40 | displayVolumeControls: {{msaapDisplayVolumeControls}} 41 |
42 |
43 | 45 | 46 | displayRepeatControls: {{msaapDisplayRepeatControls}} 47 |
48 |
49 | 51 | 52 | displayVolumeSlider: {{msaapDisplayVolumeSlider}} 53 |
54 |
55 | 57 | 58 | displayArtist: {{msaapDisplayArtist}} 59 |
60 |
61 | 63 | 64 | displayDuration: {{msaapDisplayDuration}} 65 |
66 |
67 |
68 |
Data
69 |
70 |
71 | 72 |
73 |
74 | 75 |
76 |
77 | 79 |
80 |
81 |
82 |
Logging
83 |
84 |
85 | 88 | {{currentTrack?.title}} 89 |
90 |
91 | 94 | {{currentTime}} 95 |
96 |
97 | 100 |
101 |
102 |
103 |
Player Actions
104 |
105 |
106 | 107 |
108 |
109 | 110 |
111 |
112 | 113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 131 | 132 |
133 |
135 |
136 |
EVENTS
137 | delete 138 |
139 |
140 |
142 | {{event.event}}: {{event.track.index}} - {{event.track.title}} 143 |
144 |
145 |
146 | 147 |
148 |
149 |
-------------------------------------------------------------------------------- /src/app/pages/home/home.component.scss: -------------------------------------------------------------------------------- 1 | // The margin between two sections 2 | $margin-promotion-sections: 50px; 3 | $margin-promotion-sections-small: 15px; 4 | 5 | .docs-header-section { 6 | width: 90%; 7 | position: absolute; 8 | z-index: 0; 9 | text-align: center; 10 | top: 56%; 11 | top: 56%; 12 | left: 50%; 13 | transform: translate(-50%, -50%); 14 | } 15 | 16 | .docs-header-headline { 17 | h1 { 18 | font-size: 56px; 19 | font-weight: bold; 20 | line-height: 56px; 21 | margin: 15px 5px; 22 | } 23 | 24 | h2 { 25 | font-size: 20px; 26 | font-weight: 300; 27 | line-height: 28px; 28 | margin: 15px 0 25px 0; 29 | } 30 | } 31 | 32 | .docs-homepage-row { 33 | width: 75%; 34 | display: flex; 35 | flex-direction: row; 36 | margin: $margin-promotion-sections 0; 37 | } 38 | 39 | 40 | .docs-homepage-row-column { 41 | display: flex; 42 | flex-direction: column; 43 | margin: 0 auto; 44 | width: 30%; 45 | } 46 | 47 | .docs-header-start { 48 | text-align: center; 49 | margin: $margin-promotion-sections 0 0 0; 50 | 51 | .mat-raised-button { 52 | font-size: 15px; 53 | } 54 | } 55 | 56 | /** 57 | * Rules for when the device is detected to be a small screen. 58 | */ 59 | @media (max-width: 767px) { 60 | 61 | .docs-header-background { 62 | height: 100%; 63 | } 64 | 65 | :host ::ng-deep #player { 66 | overflow-x: scroll; 67 | } 68 | 69 | .docs-header-start { 70 | margin: $margin-promotion-sections-small 0 0 0; 71 | } 72 | 73 | .docs-homepage-row, 74 | .docs-homepage-carousel-row { 75 | margin: $margin-promotion-sections-small 0; 76 | } 77 | } 78 | 79 | 80 | a:hover { 81 | color: #999; 82 | text-decoration: none; 83 | } 84 | 85 | .docs-header-background { 86 | height: 100%; 87 | } 88 | 89 | .docs-header-background:before { 90 | background-image: none; 91 | } 92 | 93 | .docs-introduction { 94 | flex-direction: column; 95 | } 96 | 97 | .docs-homepage-row-column { 98 | width: 100%; 99 | } 100 | 101 | // Small devices (landscape phones, 576px and up) 102 | @media (min-width: 576px) { 103 | .docs-header-background { 104 | height: 100%; 105 | } 106 | } 107 | 108 | @media (min-width: 578px) and (max-width: 767px) { 109 | .docs-header-background { 110 | height: 95%; 111 | } 112 | } 113 | 114 | // Medium devices (tablets, 767px and down) 115 | @media (max-width: 767px) { 116 | 117 | .logging-controls-border-right { 118 | border-right: none; 119 | } 120 | 121 | .data-border-top { 122 | margin-top: 20px; 123 | padding-top: 15px; 124 | border-top: 1px solid #999; 125 | } 126 | 127 | } 128 | 129 | // Medium devices (tablets, 768px and up) 130 | @media (min-width: 768px) { 131 | 132 | .logging-controls-border-right { 133 | border-right: 1px solid #999; 134 | } 135 | 136 | .data-border-top { 137 | margin-top: 0px; 138 | padding-top: 0px; 139 | border-top: none; 140 | } 141 | 142 | .docs-header-background { 143 | height: 85%; 144 | } 145 | 146 | .docs-header-background { 147 | overflow: hidden; 148 | position: relative; 149 | // height: 580px; 150 | } 151 | 152 | .docs-header-background::before { 153 | content: ''; 154 | position: absolute; 155 | background-image: url('../../../assets/images/logos/angular/angular-white-transparent.svg'); 156 | background-size: 50%; 157 | top: 0; 158 | bottom: 0; 159 | left: 0; 160 | right: 0; 161 | background-repeat: no-repeat; 162 | // background-position: 80% 20px; 163 | background-position: 50% 50%; 164 | opacity: 0.2; 165 | } 166 | 167 | .docs-header-section { 168 | width: 90%; 169 | position: absolute; 170 | z-index: 0; 171 | text-align: center; 172 | top: 56%; 173 | left: 50%; 174 | transform: translate(-50%, -50%); 175 | } 176 | } 177 | 178 | // Large devices (desktops, 991px and down) 179 | @media (max-width: 991px) { 180 | .data-border-right { 181 | border-right: none; 182 | } 183 | 184 | .logging-action-border-top { 185 | margin-top: 20px; 186 | padding-top: 15px; 187 | border-top: 1px solid #999; 188 | } 189 | } 190 | 191 | // Large devices (desktops, 992px and up) 192 | @media (min-width: 992px) { 193 | 194 | .logging-border-right { 195 | border-top: 1px solid #999; 196 | } 197 | 198 | .data-border-right { 199 | border-right: 1px solid #999; 200 | } 201 | 202 | .logging-action-border-top { 203 | margin-top: 0px; 204 | padding-top: 0px; 205 | border-top: none; 206 | } 207 | } 208 | 209 | // X-Large devices (large desktops, 1200px and up) 210 | @media (min-width: 1200px) { 211 | .docs-header-background { 212 | height: 80%; 213 | } 214 | 215 | .docs-header-background:before { 216 | background-image: none; 217 | } 218 | 219 | .docs-header-background { 220 | overflow: hidden; 221 | position: relative; 222 | // height: 580px; 223 | } 224 | 225 | .docs-header-background::before { 226 | content: ''; 227 | position: absolute; 228 | background-image: url('../../../assets/images/logos/angular/angular-white-transparent.svg'); 229 | background-size: 40%; 230 | top: 0; 231 | bottom: 0; 232 | left: 0; 233 | right: 0; 234 | background-repeat: no-repeat; 235 | // background-position: 80% 20px; 236 | background-position: 90% 55%; 237 | opacity: 0.2; 238 | } 239 | 240 | .docs-header-section { 241 | width: 50%; 242 | position: relative; 243 | z-index: 0; 244 | text-align: center; 245 | top: 56%; 246 | left: 40%; 247 | transform: translate(-50%, -50%); 248 | } 249 | } -------------------------------------------------------------------------------- /src/app/pages/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | import { By } from '@angular/platform-browser'; 7 | import { MatCardModule } from '@angular/material/card'; 8 | import { MatPseudoCheckboxModule } from '@angular/material/core'; 9 | import { NgxAudioPlayerModule } from 'projects/ngx-audio-player/src/public_api'; 10 | 11 | describe('HomeComponent', () => { 12 | let component: HomeComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(waitForAsync(() => { 16 | TestBed.configureTestingModule({ 17 | declarations: [HomeComponent], 18 | imports: [ 19 | BrowserAnimationsModule, 20 | RouterTestingModule, 21 | MatCardModule, 22 | MatPseudoCheckboxModule, 23 | NgxAudioPlayerModule 24 | ] 25 | }).compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(HomeComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | 38 | it('should be able to click on player controls - display title', waitForAsync(() => { 39 | const input = fixture.debugElement.query( 40 | By.css('.ngx-player-display-title .mat-checkbox-input') 41 | ).nativeElement; 42 | expect(input.checked).toBeTruthy(); 43 | input.click(); 44 | fixture.detectChanges(); 45 | expect(input.checked).toBeFalsy(); 46 | })); 47 | 48 | it('should be able to click on advanced player controls - display playlist', waitForAsync(() => { 49 | const input = fixture.debugElement.query( 50 | By.css('.mat-advanced-player-display-playlist .mat-checkbox-input') 51 | ).nativeElement; 52 | expect(input.checked).toBeTruthy(); 53 | input.click(); 54 | fixture.detectChanges(); 55 | expect(input.checked).toBeFalsy(); 56 | })); 57 | }); 58 | -------------------------------------------------------------------------------- /src/app/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { Track } from 'projects/ngx-audio-player/src/public_api'; 3 | import { AudioPlayerComponent } from 'projects/ngx-audio-player/src/public_api'; 4 | 5 | @Component({ 6 | selector: 'app-home', 7 | templateUrl: './home.component.html', 8 | styleUrls: ['./home.component.scss'] 9 | }) 10 | export class HomeComponent { 11 | 12 | constructor() { } 13 | private fmaBaseUrl = 'https://files.freemusicarchive.org/storage-freemusicarchive-org/music'; 14 | 15 | @ViewChild('player', { static: false }) 16 | advancedPlayer: AudioPlayerComponent; 17 | 18 | events: string[] = []; 19 | 20 | // Stream - set duration to 0 for streams. 21 | stream: Track[] = [ 22 | { 23 | title: 'Audio Stream', 24 | // link: `http://mediaserv33.live-streams.nl:8036/live`, 25 | link: 'http://mediaserv30.live-streams.nl:8086/live', 26 | // link: 'https://mediaserv30.live-streams.nl:2199/tunein/-stream/hionline.pls', 27 | mediaType: 'stream', 28 | artist: 'Assorted' 29 | }, 30 | ]; 31 | 32 | // Single 33 | singleTrack: Track[] = [ 34 | { 35 | title: 'In Love', 36 | link: 37 | 'https://dl.dropboxusercontent.com/s/9v0psowra7ekhxo/A%20Himitsu%20-%20In%20Love%20%28feat.%20Nori%29.flac?dl=0', 38 | duration: 227, 39 | artist: 'A Himitsu feat. Nori' 40 | } 41 | ]; 42 | 43 | // Multiple 44 | multiple: Track[] = [ 45 | { 46 | title: 'In Love', 47 | link: 48 | 'https://dl.dropboxusercontent.com/s/9v0psowra7ekhxo/A%20Himitsu%20-%20In%20Love%20%28feat.%20Nori%29.flac?dl=0', 49 | duration: 227, 50 | artist: 'A Himitsu feat. Nori' 51 | }, 52 | { 53 | title: 'On & On (feat. Daniel Levi) [NCS Release]', 54 | link: 55 | 'https://dl.dropboxusercontent.com/s/w99exjxnwoqwz0e/Cartoon-on-on-feat-daniel-levi-ncs-release.mp3?dl=0', 56 | duration: 208, 57 | artist: 'Cartoon' 58 | }, 59 | { 60 | title: '1400', 61 | link: `${this.fmaBaseUrl}/no_curator/Yung_Kartz/August_2018/Yung_Kartz_-_10_-_1400.mp3`, 62 | duration: 212, 63 | artist: 'Yung Kartz' 64 | }, 65 | { 66 | title: 'Epic Song', 67 | link: `${this.fmaBaseUrl}/ccCommunity/BoxCat_Games/Nameless_The_Hackers_RPG_Soundtrack/BoxCat_Games_-_10_-_Epic_Song.mp3`, 68 | duration: 54, 69 | artist: 'BoxCat Games' 70 | } 71 | ]; 72 | 73 | msaapPlaylist: Track[] = this.multiple; 74 | 75 | msaapDisplayTitle = true; 76 | msaapDisplayPlayList = true; 77 | pageSizeOptions = [5, 10]; 78 | 79 | msaapDisplayVolumeControls = true; 80 | msaapDisplayVolumeSlider = true; 81 | msaapDisplayRepeatControls = true; 82 | msaapDisplayArtist = false; 83 | msaapDisplayDuration = false; 84 | msaapDisablePositionSlider = false; 85 | 86 | msaapTableHeader = 'My Playlist'; 87 | msaapTitleHeader = 'My Title'; 88 | msaapArtistHeader = 'My Artist'; 89 | msaapDurationHeader = 'My Duration'; 90 | 91 | 92 | // Start: Required for demo purpose 93 | 94 | msaapPlaylist2: Track[] = [ 95 | { 96 | title: 'Hachiko (The Faithful Dog)', 97 | link: `${this.fmaBaseUrl}/ccCommunity/The_Kyoto_Connection/Wake_Up/The_Kyoto_Connection_-_09_-_Hachiko_The_Faithtful_Dog.mp3`, 98 | duration: 185, 99 | artist: 'The Kyoto' 100 | }, 101 | { 102 | title: 'Starling', 103 | link: `${this.fmaBaseUrl}/Music_for_Video/Podington_Bear/Solo_Instruments/Podington_Bear_-_Starling.mp3`, 104 | duration: 105, 105 | artist: 'Podington Bear' 106 | } 107 | ]; 108 | 109 | currentTrack: Track = null; 110 | currentTime: any; 111 | 112 | appendTracksToPlaylistDisable = false; 113 | counter = 1; 114 | 115 | onEnded(event) { 116 | this.addEvent(event); 117 | // your logic which needs to 118 | // be triggered once the 119 | // track ends goes here. 120 | 121 | // example 122 | this.currentTrack = null; 123 | } 124 | 125 | addEvent(event) { 126 | this.events.push(event); 127 | console.log(event); 128 | } 129 | 130 | onNextTrackRequested(event) { 131 | this.addEvent(event); 132 | } 133 | 134 | onPreviousTrackRequested(event) { 135 | this.addEvent(event); 136 | } 137 | 138 | onTrackPlaying(event) { 139 | this.addEvent(event); 140 | } 141 | 142 | onTrackPaused(event) { 143 | this.addEvent(event); 144 | } 145 | 146 | onTrackSelected(event) { 147 | this.addEvent(event); 148 | } 149 | 150 | logCurrentTrack() { 151 | this.advancedPlayer.audioPlayerService.getCurrentTrack().subscribe(track => { 152 | this.currentTrack = track; 153 | }); 154 | } 155 | 156 | logCurrentTime() { 157 | this.advancedPlayer.audioPlayerService.getCurrentTime().subscribe(time => { 158 | this.currentTime = time; 159 | }); 160 | } 161 | 162 | consoleLogCurrentData() { 163 | // logCurrentTrack(); 164 | // logCurrentTime(); 165 | // Make sure to subscribe (by calling above methods) 166 | // before getting the data 167 | console.log(this.currentTrack.title + ' : ' + this.currentTime); 168 | } 169 | 170 | appendTracksToPlaylist() { 171 | 172 | if (this.msaapPlaylist.length === 1) { 173 | this.msaapPlaylist = this.multiple; 174 | } else if (this.msaapPlaylist.length === 4) { 175 | this.msaapPlaylist2.map(track => { 176 | this.msaapPlaylist.push(track); 177 | }); 178 | this.advancedPlayer.audioPlayerService.setPlaylist(this.msaapPlaylist); 179 | this.appendTracksToPlaylistDisable = true; 180 | } 181 | } 182 | 183 | setStream() { 184 | this.msaapPlaylist = this.stream; 185 | this.appendTracksToPlaylistDisable = false; 186 | this.msaapDisplayRepeatControls = false; 187 | } 188 | 189 | setSingleTrack() { 190 | this.msaapPlaylist = this.singleTrack; 191 | this.appendTracksToPlaylistDisable = false; 192 | } 193 | 194 | changeMsaapDisplayTitle(event) { 195 | this.msaapDisplayTitle = event.checked; 196 | } 197 | 198 | changeMsaapDisplayPlayList(event) { 199 | this.msaapDisplayPlayList = event.checked; 200 | } 201 | 202 | changeMsaapDisplayVolumeControls(event) { 203 | this.msaapDisplayVolumeControls = event.checked; 204 | } 205 | 206 | changeMsaapDisplayRepeatControls(event) { 207 | this.msaapDisplayRepeatControls = event.checked; 208 | } 209 | 210 | changeMsaapDisplayVolumeSlider(event) { 211 | this.msaapDisplayVolumeSlider = event.checked; 212 | } 213 | 214 | changeMsaapDisplayArtist(event) { 215 | this.msaapDisplayArtist = event.checked; 216 | } 217 | 218 | changeMsaapDisplayDuration(event) { 219 | this.msaapDisplayDuration = event.checked; 220 | } 221 | 222 | changeMsaapDisablePositionSlider(event) { 223 | this.msaapDisablePositionSlider = event.checked; 224 | } 225 | // End: Required for demo purpose 226 | 227 | play() { 228 | this.advancedPlayer.play(); 229 | } 230 | 231 | pause() { 232 | this.advancedPlayer.pause(); 233 | } 234 | 235 | stop() { 236 | this.advancedPlayer.stop(); 237 | } 238 | 239 | clearEvents() { 240 | this.events = []; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/app/shared/footer/_footer-theme.scss: -------------------------------------------------------------------------------- 1 | @use '~@angular/material' as mat; 2 | @mixin footer-theme($theme) { 3 | $primary: map-get($theme, primary); 4 | $accent: map-get($theme, accent); 5 | $warn: map-get($theme, warn); 6 | $background: map-get($theme, background); 7 | $foreground: map-get($theme, foreground); 8 | 9 | app-footer { 10 | .docs-footer-links a { 11 | color: mat.get-color-from-palette($primary, default-contrast); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/shared/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 |
22 | 40 |
-------------------------------------------------------------------------------- /src/app/shared/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | ::ng-deep .mlogo svg { 2 | g { 3 | // fill:#FA2A2C; 4 | fill: #ddd; 5 | } 6 | 7 | width: 42px !important; 8 | height: 42px !important; 9 | } 10 | 11 | .copyright p b { 12 | color: white; 13 | } 14 | 15 | a { 16 | text-decoration: none; 17 | } 18 | 19 | .docs-footer { 20 | padding: 12px; 21 | font-size: 12px; 22 | } 23 | 24 | .docs-footer-list { 25 | align-items: center; 26 | display: flex; 27 | flex-flow: row wrap; 28 | padding: 8px; 29 | } 30 | 31 | .docs-footer-logo { 32 | flex: 1; 33 | } 34 | 35 | .docs-footer-angular-logo { 36 | height: 40px; 37 | } 38 | 39 | .docs-footer-version { 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | flex: 1; 44 | 45 | .angular-material-logo { 46 | height: 40px; 47 | margin: 5px 0 5px 16px; 48 | } 49 | 50 | .version { 51 | margin: 0 40px; 52 | } 53 | } 54 | 55 | .docs-footer-copyright { 56 | display: flex; 57 | flex: 1; 58 | justify-content: flex-end; 59 | flex-direction: column; 60 | min-width: 225px; 61 | margin-top: 16px; 62 | 63 | >div { 64 | display: flex; 65 | flex-direction: column; 66 | align-self: flex-end; 67 | text-align: center; 68 | } 69 | 70 | @media (min-width: 885px) { 71 | margin-top: 0; 72 | } 73 | } 74 | 75 | .docs-footer-logo span { 76 | display: inline-block; 77 | line-height: 50px; 78 | margin: 0 10px; 79 | vertical-align: bottom; 80 | 81 | a { 82 | font-size: 16px; 83 | padding: 0; 84 | } 85 | } 86 | 87 | a { 88 | text-decoration: none; 89 | color: inherit; 90 | 91 | &:hover, 92 | &:focus { 93 | text-decoration: underline; 94 | } 95 | } 96 | 97 | @media screen and (max-width: 884px) { 98 | .docs-footer-list { 99 | flex-direction: column; 100 | } 101 | } -------------------------------------------------------------------------------- /src/app/shared/footer/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import {FooterComponent, FooterModule} from './footer.component'; 4 | // import {DocsAppTestingModule} from '../../testing/testing-module'; 5 | 6 | 7 | describe('Footer', () => { 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | // imports: [FooterModule, DocsAppTestingModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FooterComponent); 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should have a link to angular.io', () => { 22 | const link = fixture.nativeElement.querySelector('.docs-footer-logo a'); 23 | const href = link.getAttribute('href'); 24 | const text = link.textContent; 25 | expect(href).toContain('angular.io'); 26 | expect(text).toContain('Learn Angular'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/shared/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import {Component, ElementRef, NgModule, ViewChild} from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-footer', 6 | templateUrl: './footer.component.html', 7 | styleUrls: ['./footer.component.scss'] 8 | }) 9 | export class FooterComponent { 10 | currentYear: number = new Date().getFullYear(); 11 | 12 | @ViewChild('mlogo') mlogo!: ElementRef; 13 | public src = 'https://raw.githubusercontent.com/mudigal-technologies/mudigal.com/master/logo/m-logo.txt'; 14 | 15 | constructor( 16 | private http: HttpClient, 17 | ) { } 18 | 19 | ngOnInit(): void { 20 | this.http.get(this.src, { responseType: 'text' }).subscribe(svg => { 21 | this.mlogo.nativeElement.innerHTML = svg; 22 | }); 23 | } 24 | } 25 | 26 | 27 | @NgModule({ 28 | exports: [FooterComponent], 29 | declarations: [FooterComponent], 30 | }) 31 | export class FooterModule {} 32 | -------------------------------------------------------------------------------- /src/app/shared/footer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './footer.component'; 2 | -------------------------------------------------------------------------------- /src/app/shared/navbar/_navbar-theme.scss: -------------------------------------------------------------------------------- 1 | @use '~@angular/material' as mat; 2 | 3 | @mixin nav-bar-theme($theme) { 4 | $primary: map-get($theme, primary); 5 | $accent: map-get($theme, accent); 6 | $warn: map-get($theme, warn); 7 | $background: map-get($theme, background); 8 | $foreground: map-get($theme, foreground); 9 | 10 | app-navbar { 11 | color: mat.get-color-from-palette($primary, default-contrast); 12 | 13 | .docs-navbar, 14 | .docs-navbar-header { 15 | background: mat.get-color-from-palette($primary); 16 | opacity: 0.9 !important; 17 | box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1) !important; 18 | backdrop-filter: blur(5px) !important; 19 | -webkit-backdrop-filter: blur(5px) !important; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/app/shared/navbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nav-bar.component'; 2 | -------------------------------------------------------------------------------- /src/app/shared/navbar/nav-bar.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/navbar/nav-bar.component.scss: -------------------------------------------------------------------------------- 1 | #sticky-navigation { 2 | background: rgba(0, 0, 0, 0.25); 3 | box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1) !important; 4 | backdrop-filter: blur(5px) !important; 5 | -webkit-backdrop-filter: blur(5px) !important; 6 | } 7 | 8 | 9 | .docs-navbar-header { 10 | position: fixed; 11 | width: 100%; 12 | top: 0; 13 | 14 | display: flex; 15 | flex-wrap: wrap; 16 | align-items: center; 17 | padding: 8px 16px; 18 | min-height: 80px; 19 | 20 | >.mat-button { 21 | &:last-child { 22 | margin-left: auto; 23 | } 24 | } 25 | } 26 | 27 | a { 28 | color: #ffffff; 29 | } 30 | 31 | a:hover { 32 | color: #ffffff; 33 | text-decoration: none ! important; 34 | } 35 | 36 | .flex-spacer { 37 | flex-grow: 1; 38 | } 39 | 40 | .docs-angular-logo { 41 | height: 36px; 42 | margin: 0 4px 3px 0; 43 | vertical-align: middle; 44 | } 45 | 46 | .docs-github-logo { 47 | height: 21px; 48 | margin: 0 7px 2px 0; 49 | vertical-align: middle; 50 | } 51 | 52 | .docs-navbar-link { 53 | text-decoration: inherit; 54 | flex: 1; 55 | } 56 | 57 | .docs-navbar { 58 | display: none; 59 | } 60 | 61 | .docs-navbar-show-small { 62 | display: none; 63 | } 64 | 65 | .skip-link-wrapper { 66 | position: absolute; 67 | top: 10px; 68 | left: 50%; 69 | transform: translateX(-50%); 70 | border-radius: 5px; 71 | } 72 | 73 | /** 74 | * Rules for when the device is detected to be a small screen. 75 | * Moves content two rows instead of one. 76 | */ 77 | @media (max-width: 720px) { 78 | .docs-navbar-hide-small { 79 | display: none; 80 | } 81 | 82 | .docs-navbar-show-small { 83 | display: block; 84 | } 85 | 86 | .docs-navbar { 87 | display: flex; 88 | } 89 | 90 | .docs-navbar--github-logo { 91 | margin: 0; 92 | } 93 | } -------------------------------------------------------------------------------- /src/app/shared/navbar/nav-bar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import {NavBarComponent, NavBarModule} from './nav-bar.component'; 4 | 5 | 6 | describe('NavBar', () => { 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [NavBarModule], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(NavBarComponent); 17 | fixture.detectChanges(); 18 | }); 19 | 20 | // Note: Add tests as logic is added to navbar class. 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/shared/navbar/nav-bar.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {MatButtonModule} from '@angular/material/button'; 4 | import {MatMenuModule} from '@angular/material/menu'; 5 | import {RouterModule} from '@angular/router'; 6 | import {ThemePickerModule} from '../theme-picker'; 7 | import {ThemeStorage} from '../theme-picker/theme-storage/theme-storage'; 8 | import {StyleManager} from '../style-manager'; 9 | import {HttpClientModule} from '@angular/common/http'; 10 | 11 | @Component({ 12 | selector: 'app-navbar', 13 | templateUrl: './nav-bar.component.html', 14 | styleUrls: ['./nav-bar.component.scss'] 15 | }) 16 | export class NavBarComponent { 17 | 18 | } 19 | 20 | @NgModule({ 21 | imports: [ 22 | CommonModule, 23 | HttpClientModule, 24 | MatButtonModule, 25 | MatMenuModule, 26 | RouterModule, 27 | ThemePickerModule, 28 | ], 29 | exports: [NavBarComponent], 30 | declarations: [NavBarComponent], 31 | providers: [StyleManager, ThemeStorage] 32 | }) 33 | export class NavBarModule {} 34 | -------------------------------------------------------------------------------- /src/app/shared/style-manager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './style-manager'; 2 | -------------------------------------------------------------------------------- /src/app/shared/style-manager/style-manager.spec.ts: -------------------------------------------------------------------------------- 1 | import {HttpClientTestingModule} from '@angular/common/http/testing'; 2 | import {inject, TestBed} from '@angular/core/testing'; 3 | import {StyleManager} from './style-manager'; 4 | 5 | 6 | describe('StyleManager', () => { 7 | let styleManager: StyleManager; 8 | 9 | beforeEach(() => TestBed.configureTestingModule({ 10 | imports: [HttpClientTestingModule], 11 | providers: [StyleManager] 12 | })); 13 | 14 | beforeEach(inject([StyleManager], (sm: StyleManager) => { 15 | styleManager = sm; 16 | })); 17 | 18 | afterEach(() => { 19 | const links = document.head.querySelectorAll('link'); 20 | for (const link of Array.prototype.slice.call(links)) { 21 | if (link.className.includes('style-manager-')) { 22 | document.head.removeChild(link); 23 | } 24 | } 25 | }); 26 | 27 | it('should add stylesheet to head', () => { 28 | styleManager.setStyle('test', 'test.css'); 29 | const styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; 30 | expect(styleEl).not.toBeNull(); 31 | expect(styleEl.href.endsWith('test.css')).toBe(true); 32 | }); 33 | 34 | it('should change existing stylesheet', () => { 35 | styleManager.setStyle('test', 'test.css'); 36 | const styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; 37 | expect(styleEl).not.toBeNull(); 38 | expect(styleEl.href.endsWith('test.css')).toBe(true); 39 | 40 | styleManager.setStyle('test', 'new.css'); 41 | expect(styleEl.href.endsWith('new.css')).toBe(true); 42 | }); 43 | 44 | it('should remove existing stylesheet', () => { 45 | styleManager.setStyle('test', 'test.css'); 46 | let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; 47 | expect(styleEl).not.toBeNull(); 48 | expect(styleEl.href.endsWith('test.css')).toBe(true); 49 | 50 | styleManager.removeStyle('test'); 51 | styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; 52 | expect(styleEl).toBeNull(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/app/shared/style-manager/style-manager.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | 4 | /** 5 | * Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be 6 | * removed or changed later. 7 | */ 8 | @Injectable() 9 | export class StyleManager { 10 | /** 11 | * Set the stylesheet with the specified key. 12 | */ 13 | setStyle(key: string, href: string) { 14 | getLinkElementForKey(key).setAttribute('href', href); 15 | } 16 | 17 | /** 18 | * Remove the stylesheet with the specified key. 19 | */ 20 | removeStyle(key: string) { 21 | const existingLinkElement = getExistingLinkElementByKey(key); 22 | if (existingLinkElement) { 23 | document.head.removeChild(existingLinkElement); 24 | } 25 | } 26 | } 27 | 28 | function getLinkElementForKey(key: string) { 29 | return getExistingLinkElementByKey(key) || createLinkElementWithKey(key); 30 | } 31 | 32 | function getExistingLinkElementByKey(key: string) { 33 | return document.head.querySelector(`link[rel="stylesheet"].${getClassNameForKey(key)}`); 34 | } 35 | 36 | function createLinkElementWithKey(key: string) { 37 | const linkEl = document.createElement('link'); 38 | linkEl.setAttribute('rel', 'stylesheet'); 39 | linkEl.classList.add(getClassNameForKey(key)); 40 | document.head.appendChild(linkEl); 41 | return linkEl; 42 | } 43 | 44 | function getClassNameForKey(key: string) { 45 | return `style-manager-${key}`; 46 | } 47 | -------------------------------------------------------------------------------- /src/app/shared/theme-picker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './theme-picker.component'; 2 | 3 | -------------------------------------------------------------------------------- /src/app/shared/theme-picker/theme-picker.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/shared/theme-picker/theme-picker.component.scss: -------------------------------------------------------------------------------- 1 | .docs-theme-picker-menu { 2 | .mat-menu-item { 3 | .mat-icon.theme-example-icon { 4 | margin-right: 0; 5 | margin-left: 8px; 6 | float: right; 7 | 8 | svg { 9 | vertical-align: middle; 10 | } 11 | 12 | // The below colors need to align with the themes defined in ThemePicker 13 | &.deeppurple-amber svg { 14 | .docs-theme-icon-background { 15 | fill: #fafafa; 16 | } 17 | .docs-theme-icon-button { 18 | fill: #FFC107; 19 | } 20 | .docs-theme-icon-toolbar { 21 | fill: #673AB7; 22 | } 23 | } 24 | 25 | &.indigo-pink svg { 26 | .docs-theme-icon-background { 27 | fill: #fafafa; 28 | } 29 | .docs-theme-icon-button { 30 | fill: #E91E63; 31 | } 32 | .docs-theme-icon-toolbar { 33 | fill: #3F51B5; 34 | } 35 | } 36 | 37 | &.pink-bluegrey svg { 38 | .docs-theme-icon-background { 39 | fill: #303030; 40 | } 41 | .docs-theme-icon-button { 42 | fill: #607D8B; 43 | } 44 | .docs-theme-icon-toolbar { 45 | fill: #E91E63; 46 | } 47 | } 48 | 49 | &.purple-green svg { 50 | .docs-theme-icon-background { 51 | fill: #303030; 52 | } 53 | .docs-theme-icon-button { 54 | fill: #4CAF50; 55 | } 56 | .docs-theme-icon-toolbar { 57 | fill: #9C27B0; 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | button:focus { 65 | outline: 0px!important; 66 | } 67 | -------------------------------------------------------------------------------- /src/app/shared/theme-picker/theme-picker.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import {ThemePickerComponent, ThemePickerModule} from './theme-picker.component'; 3 | 4 | 5 | describe('ThemePicker', () => { 6 | beforeEach(waitForAsync(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ThemePickerModule], 9 | }).compileComponents(); 10 | })); 11 | 12 | it('should install theme based on name', () => { 13 | const fixture = TestBed.createComponent(ThemePickerComponent); 14 | const component = fixture.componentInstance; 15 | const name = 'pink-bluegrey'; 16 | spyOn(component.styleManager, 'setStyle'); 17 | component.selectTheme(name); 18 | expect(component.styleManager.setStyle).toHaveBeenCalled(); 19 | expect(component.styleManager.setStyle).toHaveBeenCalledWith('theme', `assets/${name}.css`); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/shared/theme-picker/theme-picker.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | Component, 4 | NgModule, 5 | OnDestroy, 6 | OnInit, 7 | ViewEncapsulation, 8 | } from '@angular/core'; 9 | import { StyleManager } from '../style-manager'; 10 | import { DocsSiteTheme, ThemeStorage } from './theme-storage/theme-storage'; 11 | import { MatButtonModule } from '@angular/material/button'; 12 | import { MatIconModule, MatIconRegistry } from '@angular/material/icon'; 13 | import { MatMenuModule } from '@angular/material/menu'; 14 | import { MatTooltipModule } from '@angular/material/tooltip'; 15 | import { CommonModule } from '@angular/common'; 16 | import { ActivatedRoute, ParamMap } from '@angular/router'; 17 | import { Subscription } from 'rxjs'; 18 | import { map } from 'rxjs/operators'; 19 | import { DomSanitizer } from '@angular/platform-browser'; 20 | import { LiveAnnouncer } from '@angular/cdk/a11y'; 21 | 22 | @Component({ 23 | selector: 'app-theme-picker', 24 | templateUrl: 'theme-picker.component.html', 25 | styleUrls: ['theme-picker.component.scss'], 26 | changeDetection: ChangeDetectionStrategy.OnPush, 27 | encapsulation: ViewEncapsulation.None 28 | }) 29 | export class ThemePickerComponent implements OnInit, OnDestroy { 30 | private _queryParamSubscription = Subscription.EMPTY; 31 | currentTheme: DocsSiteTheme; 32 | 33 | // The below colors need to align with the themes defined in theme-picker.scss 34 | themes: DocsSiteTheme[] = [ 35 | { 36 | primary: '#673AB7', 37 | accent: '#FFC107', 38 | displayName: 'Deep Purple & Amber', 39 | name: 'deeppurple-amber', 40 | isDark: false, 41 | }, 42 | { 43 | primary: '#3F51B5', 44 | accent: '#E91E63', 45 | displayName: 'Indigo & Pink', 46 | name: 'indigo-pink', 47 | isDark: false, 48 | isDefault: true, 49 | }, 50 | { 51 | primary: '#E91E63', 52 | accent: '#607D8B', 53 | displayName: 'Pink & Blue-grey', 54 | name: 'pink-bluegrey', 55 | isDark: true, 56 | }, 57 | { 58 | primary: '#9C27B0', 59 | accent: '#4CAF50', 60 | displayName: 'Purple & Green', 61 | name: 'purple-green', 62 | isDark: true, 63 | }, 64 | ]; 65 | 66 | constructor(public styleManager: StyleManager, 67 | private _themeStorage: ThemeStorage, 68 | private _activatedRoute: ActivatedRoute, 69 | private liveAnnouncer: LiveAnnouncer, 70 | iconRegistry: MatIconRegistry, 71 | sanitizer: DomSanitizer) { 72 | iconRegistry.addSvgIcon('theme-example', 73 | sanitizer.bypassSecurityTrustResourceUrl( 74 | '/assets/images/theme-demo-icon.svg')); 75 | const themeName = this._themeStorage.getStoredThemeName(); 76 | if (themeName) { 77 | this.selectTheme(themeName); 78 | } 79 | } 80 | 81 | ngOnInit() { 82 | this._queryParamSubscription = this._activatedRoute.queryParamMap 83 | .pipe(map((params: ParamMap) => params.get('theme'))) 84 | .subscribe((themeName: string | null) => { 85 | if (themeName) { 86 | this.selectTheme(themeName); 87 | } 88 | }); 89 | } 90 | 91 | ngOnDestroy() { 92 | this._queryParamSubscription.unsubscribe(); 93 | } 94 | 95 | selectTheme(themeName: string) { 96 | const theme = this.themes.find(currentTheme => currentTheme.name === themeName); 97 | 98 | if (!theme) { 99 | return; 100 | } 101 | 102 | this.currentTheme = theme; 103 | 104 | if (theme.isDefault) { 105 | this.styleManager.removeStyle('theme'); 106 | } else { 107 | this.styleManager.setStyle('theme', `${theme.name}.css`); 108 | } 109 | 110 | if (this.currentTheme) { 111 | this.liveAnnouncer.announce(`${theme.displayName} theme selected.`, 'polite', 3000); 112 | this._themeStorage.storeTheme(this.currentTheme); 113 | } 114 | } 115 | } 116 | 117 | @NgModule({ 118 | imports: [ 119 | CommonModule, 120 | MatButtonModule, 121 | MatIconModule, 122 | MatMenuModule, 123 | MatTooltipModule, 124 | ], 125 | exports: [ThemePickerComponent], 126 | declarations: [ThemePickerComponent], 127 | providers: [StyleManager, ThemeStorage], 128 | }) 129 | export class ThemePickerModule { } 130 | -------------------------------------------------------------------------------- /src/app/shared/theme-picker/theme-storage/theme-storage.spec.ts: -------------------------------------------------------------------------------- 1 | import {ThemeStorage, DocsSiteTheme} from './theme-storage'; 2 | 3 | 4 | const testStorageKey = ThemeStorage.storageKey; 5 | const testTheme: DocsSiteTheme = { 6 | primary: '#000000', 7 | accent: '#ffffff', 8 | name: 'test-theme' 9 | }; 10 | 11 | describe('ThemeStorage Service', () => { 12 | const service = new ThemeStorage(); 13 | const getCurrTheme = () => window.localStorage.getItem(testStorageKey); 14 | const secondTestTheme = { 15 | primary: '#666666', 16 | accent: '#333333', 17 | name: 'other-test-theme' 18 | }; 19 | 20 | beforeEach(() => { 21 | window.localStorage[testStorageKey] = testTheme.name; 22 | }); 23 | 24 | afterEach(() => { 25 | window.localStorage.clear(); 26 | }); 27 | 28 | it('should set the current theme name', () => { 29 | expect(getCurrTheme()).toEqual(testTheme.name); 30 | service.storeTheme(secondTestTheme); 31 | expect(getCurrTheme()).toEqual(secondTestTheme.name); 32 | }); 33 | 34 | it('should get the current theme name', () => { 35 | const theme = service.getStoredThemeName(); 36 | expect(theme).toEqual(testTheme.name); 37 | }); 38 | 39 | it('should clear the stored theme data', () => { 40 | expect(getCurrTheme()).not.toBeNull(); 41 | service.clearStorage(); 42 | expect(getCurrTheme()).toBeNull(); 43 | }); 44 | 45 | it('should emit an event when setTheme is called', () => { 46 | spyOn(service.onThemeUpdate, 'emit'); 47 | service.storeTheme(secondTestTheme); 48 | expect(service.onThemeUpdate.emit).toHaveBeenCalled(); 49 | expect(service.onThemeUpdate.emit).toHaveBeenCalledWith(secondTestTheme); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/app/shared/theme-picker/theme-storage/theme-storage.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, EventEmitter} from '@angular/core'; 2 | 3 | export interface DocsSiteTheme { 4 | name: string; 5 | displayName?: string; 6 | accent: string; 7 | primary: string; 8 | isDark?: boolean; 9 | isDefault?: boolean; 10 | } 11 | 12 | 13 | @Injectable() 14 | export class ThemeStorage { 15 | static storageKey = 'docs-theme-storage-current-name'; 16 | 17 | onThemeUpdate: EventEmitter = new EventEmitter(); 18 | 19 | storeTheme(theme: DocsSiteTheme) { 20 | try { 21 | window.localStorage[ThemeStorage.storageKey] = theme.name; 22 | } catch { } 23 | 24 | this.onThemeUpdate.emit(theme); 25 | } 26 | 27 | getStoredThemeName(): string | null { 28 | try { 29 | return window.localStorage[ThemeStorage.storageKey] || null; 30 | } catch { 31 | return null; 32 | } 33 | } 34 | 35 | clearStorage() { 36 | try { 37 | window.localStorage.removeItem(ThemeStorage.storageKey); 38 | } catch { } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/images/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/images/icons/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/icons/android-chrome-384x384.png -------------------------------------------------------------------------------- /src/assets/images/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/images/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffc40d 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/images/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/icons/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/images/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/images/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/icons/favicon.ico -------------------------------------------------------------------------------- /src/assets/images/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/images/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | Created by potrace 1.11, written by Peter Selinger 2001-2013 7 | 8 | 10 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/assets/images/icons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NGX Audio Player", 3 | "short_name": "NGX Audio Player", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/images/logos/angular/angular-white-transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/images/logos/angular/github-circle-white-transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | github-circle-white-transparent 3 | 6 | -------------------------------------------------------------------------------- /src/assets/images/logos/logo_with_text_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/logos/logo_with_text_dark.png -------------------------------------------------------------------------------- /src/assets/images/logos/logo_with_text_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/logos/logo_with_text_light.png -------------------------------------------------------------------------------- /src/assets/images/logos/ngx-audio-player-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/assets/images/logos/ngx-audio-player-logo.png -------------------------------------------------------------------------------- /src/assets/images/theme-demo-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mudigal-technologies/ngx-audio-player/07dc041173b3076ac4cbdd567dbcbaa6ba053eb1/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ngx Audio Player Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-teamcity-reporter'), 12 | require('karma-jasmine-html-reporter'), 13 | require('karma-coverage-istanbul-reporter'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | client: { 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageIstanbulReporter: { 20 | dir: require('path').join(__dirname, '../coverage'), 21 | reports: ['html', 'lcovonly'], 22 | fixWebpackSourcePaths: true 23 | }, 24 | reporters: ['progress', 'kjhtml'], 25 | port: 9876, 26 | colors: true, 27 | logLevel: config.LOG_INFO, 28 | autoWatch: true, 29 | browsers: ['Chrome'], 30 | singleRun: false 31 | }); 32 | }; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js'; // Included with Angular CLI. 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | // @import './app-theme'; 2 | // @import './styles/api'; 3 | // @import './styles/markdown'; 4 | // @import './styles/tables'; 5 | // @import './styles/general'; 6 | 7 | // // Include material core styles. 8 | // @include mat-core(); 9 | 10 | // @include angular-material-theme($theme); 11 | // @include material-docs-app-theme($theme); 12 | 13 | html, 14 | body { 15 | height: 100%; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | font-family: Roboto, "Helvetica Neue", sans-serif; 21 | overflow-x: auto; 22 | } -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2020", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2018", 18 | "dom" 19 | ], 20 | "paths": { 21 | "ngx-audio-player": [ 22 | "dist/ngx-audio-player" 23 | ], 24 | "ngx-audio-player/*": [ 25 | "dist/ngx-audio-player/*" 26 | ], 27 | "core-js/es7/reflect": [ 28 | "node_modules/core-js/proposals/reflect-metadata" 29 | ] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "node_modules/codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-parens": false, 15 | "arrow-return-shorthand": true, 16 | "curly": true, 17 | "deprecation": { 18 | "severity": "warning" 19 | }, 20 | "eofline": true, 21 | "import-blacklist": [ 22 | true, 23 | "rxjs/Rx" 24 | ], 25 | "import-spacing": true, 26 | "interface-name": false, 27 | "max-classes-per-file": false, 28 | "max-line-length": [ 29 | true, 30 | 140 31 | ], 32 | "member-access": false, 33 | "member-ordering": [ 34 | true, 35 | { 36 | "order": [ 37 | "static-field", 38 | "instance-field", 39 | "static-method", 40 | "instance-method" 41 | ] 42 | } 43 | ], 44 | "no-consecutive-blank-lines": false, 45 | "no-console": [ 46 | true, 47 | "debug", 48 | "info", 49 | "time", 50 | "timeEnd", 51 | "trace" 52 | ], 53 | "no-empty": false, 54 | "no-inferrable-types": [ 55 | true, 56 | "ignore-params" 57 | ], 58 | "no-non-null-assertion": true, 59 | "no-redundant-jsdoc": true, 60 | "no-switch-case-fall-through": true, 61 | "no-var-requires": false, 62 | "object-literal-key-quotes": [ 63 | true, 64 | "as-needed" 65 | ], 66 | "object-literal-sort-keys": false, 67 | "ordered-imports": false, 68 | "quotemark": [ 69 | true, 70 | "single" 71 | ], 72 | "semicolon": { 73 | "options": [ 74 | "always" 75 | ] 76 | }, 77 | "space-before-function-paren": { 78 | "options": { 79 | "anonymous": "never", 80 | "asyncArrow": "always", 81 | "constructor": "never", 82 | "method": "never", 83 | "named": "never" 84 | } 85 | }, 86 | "trailing-comma": false, 87 | "component-class-suffix": true, 88 | "contextual-lifecycle": true, 89 | "directive-class-suffix": true, 90 | "no-conflicting-lifecycle": true, 91 | "no-host-metadata-property": true, 92 | "no-input-rename": true, 93 | "no-inputs-metadata-property": true, 94 | "no-output-native": true, 95 | "no-output-on-prefix": true, 96 | "no-output-rename": true, 97 | "no-outputs-metadata-property": true, 98 | "template-banana-in-box": true, 99 | "template-no-negated-async": true, 100 | "typedef-whitespace": { 101 | "options": [ 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | }, 109 | { 110 | "call-signature": "onespace", 111 | "index-signature": "onespace", 112 | "parameter": "onespace", 113 | "property-declaration": "onespace", 114 | "variable-declaration": "onespace" 115 | } 116 | ] 117 | }, 118 | "use-lifecycle-interface": true, 119 | "use-pipe-transform-interface": true, 120 | "indent": [true, "spaces", 2] 121 | , "variable-name": { 122 | "options": [ 123 | "ban-keywords", 124 | "check-format", 125 | "allow-pascal-case", 126 | "allow-leading-underscore" 127 | ] 128 | }, 129 | "whitespace": { 130 | "options": [ 131 | "check-branch", 132 | "check-decl", 133 | "check-operator", 134 | "check-separator", 135 | "check-type", 136 | "check-typecast" 137 | ] 138 | } 139 | } 140 | } 141 | --------------------------------------------------------------------------------