├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── browserslist ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── guards │ │ ├── auth.guard.spec.ts │ │ └── auth.guard.ts │ ├── interfaces │ │ └── stream-state.ts │ ├── material.module.ts │ ├── pages │ │ ├── player │ │ │ ├── player.component.html │ │ │ ├── player.component.scss │ │ │ ├── player.component.spec.ts │ │ │ └── player.component.ts │ │ └── profile │ │ │ ├── profile.component.html │ │ │ ├── profile.component.scss │ │ │ ├── profile.component.spec.ts │ │ │ └── profile.component.ts │ └── services │ │ ├── audio.service.spec.ts │ │ ├── audio.service.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.service.ts │ │ ├── cloud.service.spec.ts │ │ └── cloud.service.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What this app is for? 2 | 3 | It is an audio player app. It allows user to play, pause the audio track. Also, user can play next or previous track. In addition to that, user can change the current playing time using a seekbar. 4 | 5 | # Running the prototype 6 | Follow the steps to run the application: 7 | 8 | * `git clone` this repo 9 | * `cd auth0-audio` and `npm install` 10 | * run `npm start` 11 | * open browser on `http://localhost:4200/` 12 | * login in the app and you will see the tracks to play 13 | * click on any track and it will start playing 14 | * in the bottom bar, you will see buttons to play/pause, next or previous track and a seekbar to change the time. 15 | 16 | # Introduction (Outline) 17 | 18 | ## Prerequisites 19 | ### Install required tools 20 | ### Scafolding the App 21 | ### Installing Project Dependencies 22 | 23 | ## Developing Audio Player UI using Angular Material 24 | ### Audio Player HTML 25 | ### Styling Audio Player 26 | 27 | ## Creating a Service to Manage the Playback 28 | ### Creating Playback Observable 29 | 30 | ## Reading the Music Files 31 | 32 | ## Managing playback state using RxJS 33 | ### Creating an Observable for State Management 34 | 35 | ## Stitching all together 36 | ### The Audio Player UI Controller 37 | ### Building and Running the Audio Player 38 | 39 | ## Authentication with Auth0 40 | ### Installing Depedencies 41 | ### Set Up an Auth0 Application 42 | ### Configuring Auth0 43 | ### Auth Service 44 | 45 | ## Conclusion and Next Steps 46 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-audio": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/angular-audio", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": false, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 32 | "src/styles.scss" 33 | ], 34 | "scripts": [] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "fileReplacements": [ 39 | { 40 | "replace": "src/environments/environment.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "extractCss": true, 48 | "namedChunks": false, 49 | "aot": true, 50 | "extractLicenses": true, 51 | "vendorChunk": false, 52 | "buildOptimizer": true, 53 | "budgets": [ 54 | { 55 | "type": "initial", 56 | "maximumWarning": "2mb", 57 | "maximumError": "5mb" 58 | } 59 | ] 60 | } 61 | } 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "options": { 66 | "browserTarget": "angular-audio:build" 67 | }, 68 | "configurations": { 69 | "production": { 70 | "browserTarget": "angular-audio:build:production" 71 | } 72 | } 73 | }, 74 | "extract-i18n": { 75 | "builder": "@angular-devkit/build-angular:extract-i18n", 76 | "options": { 77 | "browserTarget": "angular-audio:build" 78 | } 79 | }, 80 | "test": { 81 | "builder": "@angular-devkit/build-angular:karma", 82 | "options": { 83 | "main": "src/test.ts", 84 | "polyfills": "src/polyfills.ts", 85 | "tsConfig": "tsconfig.spec.json", 86 | "karmaConfig": "karma.conf.js", 87 | "assets": [ 88 | "src/favicon.ico", 89 | "src/assets" 90 | ], 91 | "styles": [ 92 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 93 | "src/styles.scss" 94 | ], 95 | "scripts": [] 96 | } 97 | }, 98 | "lint": { 99 | "builder": "@angular-devkit/build-angular:tslint", 100 | "options": { 101 | "tsConfig": [ 102 | "tsconfig.app.json", 103 | "tsconfig.spec.json", 104 | "e2e/tsconfig.json" 105 | ], 106 | "exclude": [ 107 | "**/node_modules/**" 108 | ] 109 | } 110 | }, 111 | "e2e": { 112 | "builder": "@angular-devkit/build-angular:protractor", 113 | "options": { 114 | "protractorConfig": "e2e/protractor.conf.js", 115 | "devServerTarget": "angular-audio:serve" 116 | }, 117 | "configurations": { 118 | "production": { 119 | "devServerTarget": "angular-audio:serve:production" 120 | } 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | "defaultProject": "angular-audio" 127 | } -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to angular-audio!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/angular-audio'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-audio", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~8.0.1", 15 | "@angular/cdk": "~8.0.1", 16 | "@angular/common": "~8.0.1", 17 | "@angular/compiler": "~8.0.1", 18 | "@angular/core": "~8.0.1", 19 | "@angular/forms": "~8.0.1", 20 | "@angular/material": "^8.0.1", 21 | "@angular/platform-browser": "~8.0.1", 22 | "@angular/platform-browser-dynamic": "~8.0.1", 23 | "@angular/router": "~8.0.1", 24 | "auth0-js": "^9.10.4", 25 | "hammerjs": "^2.0.8", 26 | "moment": "^2.24.0", 27 | "rxjs": "~6.4.0", 28 | "tslib": "^1.9.0", 29 | "zone.js": "~0.9.1" 30 | }, 31 | "devDependencies": { 32 | "@angular-devkit/build-angular": "~0.800.0", 33 | "@angular/cli": "~8.0.3", 34 | "@angular/compiler-cli": "~8.0.1", 35 | "@angular/language-service": "~8.0.1", 36 | "@types/node": "~8.9.4", 37 | "@types/jasmine": "~3.3.8", 38 | "@types/jasminewd2": "~2.0.3", 39 | "codelyzer": "^5.0.0", 40 | "jasmine-core": "~3.4.0", 41 | "jasmine-spec-reporter": "~4.2.1", 42 | "karma": "~4.1.0", 43 | "karma-chrome-launcher": "~2.2.0", 44 | "karma-coverage-istanbul-reporter": "~2.0.1", 45 | "karma-jasmine": "~2.0.1", 46 | "karma-jasmine-html-reporter": "^1.4.0", 47 | "protractor": "~5.4.0", 48 | "ts-node": "~7.0.0", 49 | "tslint": "~5.15.0", 50 | "typescript": "~3.4.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { PlayerComponent } from './pages/player/player.component'; 4 | import { ProfileComponent } from './pages/profile/profile.component'; 5 | 6 | const routes: Routes = [ 7 | { path: '', component: PlayerComponent }, 8 | { path: 'profile', component: ProfileComponent }, 9 | { path: '**', redirectTo: '' } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [ 14 | RouterModule.forRoot(routes) 15 | ], 16 | exports: [RouterModule], 17 | }) 18 | export class AppRoutingModule {} -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsingh/auth0-audio/c41cf9b9118854231d06d4221f6e23064f83e202/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'angular-audio'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('angular-audio'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-audio!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AuthService } from './services/auth.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | constructor(public auth: AuthService) { 11 | auth.renewAuth(); 12 | auth.handleLoginCallback(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | import { MaterialModule } from './material.module'; 8 | import { PlayerComponent } from './pages/player/player.component'; 9 | import { ProfileComponent } from './pages/profile/profile.component'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent, 14 | PlayerComponent, 15 | ProfileComponent 16 | ], 17 | imports: [ 18 | BrowserModule, 19 | AppRoutingModule, 20 | BrowserAnimationsModule, 21 | MaterialModule 22 | ], 23 | providers: [], 24 | bootstrap: [AppComponent] 25 | }) 26 | export class AppModule { } 27 | -------------------------------------------------------------------------------- /src/app/guards/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, inject } from '@angular/core/testing'; 2 | 3 | import { AuthGuard } from './auth.guard'; 4 | 5 | describe('AuthGuard', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [AuthGuard] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([AuthGuard], (guard: AuthGuard) => { 13 | expect(guard).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | CanActivate, 4 | ActivatedRouteSnapshot, 5 | RouterStateSnapshot 6 | } from '@angular/router'; 7 | import { Observable } from 'rxjs'; 8 | import { AuthService } from '../services/auth.service'; 9 | import { Router } from '@angular/router'; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | 15 | @Injectable() 16 | export class AuthGuard implements CanActivate { 17 | constructor(private authService: AuthService, private router: Router) {} 18 | 19 | canActivate( 20 | next: ActivatedRouteSnapshot, 21 | state: RouterStateSnapshot 22 | ): Observable | Promise | boolean { 23 | if (this.authService.authenticated) { 24 | return true; 25 | } else { 26 | this.router.navigate(['/']); 27 | return false; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/app/interfaces/stream-state.ts: -------------------------------------------------------------------------------- 1 | export interface StreamState { 2 | playing: boolean; 3 | readableCurrentTime: string; 4 | readableDuration: string; 5 | duration: number | undefined; 6 | currentTime: number | undefined; 7 | canplay: boolean; 8 | error: boolean; 9 | } -------------------------------------------------------------------------------- /src/app/material.module.ts: -------------------------------------------------------------------------------- 1 | // src/app/material.module.ts 2 | import { NgModule } from '@angular/core'; 3 | import { 4 | MatButtonModule, 5 | MatListModule, 6 | MatSliderModule, 7 | MatIconModule, 8 | MatToolbarModule, 9 | MatCardModule 10 | } from '@angular/material'; 11 | 12 | const modules = [ 13 | MatButtonModule, 14 | MatListModule, 15 | MatSliderModule, 16 | MatIconModule, 17 | MatToolbarModule, 18 | MatCardModule 19 | ]; 20 | 21 | @NgModule({ 22 | imports: modules, 23 | exports: modules 24 | }) 25 | export class MaterialModule {} -------------------------------------------------------------------------------- /src/app/pages/player/player.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Audio Player 4 | 5 | LOGIN 6 | LOGOUT 7 | PROFILE 8 | 9 | 10 |
11 | 15 | 16 | 17 |

Songs

18 | 19 | music_note 20 |

{{ file.name }}

21 |
by {{ file.artist }}
22 | volume_up 23 |
ERROR
24 | 25 |
26 |
27 |
28 | 29 | 56 |
57 | 58 | -------------------------------------------------------------------------------- /src/app/pages/player/player.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | .main-toolbar { 3 | .spacer { 4 | flex: 1 1 auto; 5 | } 6 | .toolbar-btn { 7 | font-size: 16px; 8 | margin-right:5px; 9 | cursor: pointer; 10 | } 11 | } 12 | 13 | .content { 14 | .logo { 15 | margin: 2.5rem; 16 | text-align: center; 17 | font-size:24px; 18 | color: #3f51b5; 19 | .mat-icon{ 20 | height:160px !important; 21 | width:160px !important; 22 | font-size:160px !important; 23 | } 24 | } 25 | } 26 | 27 | .media-footer { 28 | position: absolute; 29 | bottom:0; 30 | width:100%; 31 | .time-slider { 32 | width: 100% !important; 33 | margin-left:20px; 34 | margin-right:20px; 35 | } 36 | .media-action-bar { 37 | width: 100%; 38 | padding:2.5rem; 39 | justify-content: center; 40 | .mat-icon{ 41 | height:48px !important; 42 | width:48px !important; 43 | font-size:48px !important; 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/app/pages/player/player.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PlayerComponent } from './player.component'; 4 | 5 | describe('PlayerComponent', () => { 6 | let component: PlayerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PlayerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PlayerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/pages/player/player.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AudioService } from '../../services/audio.service'; 3 | import { CloudService } from '../../services/cloud.service'; 4 | import { StreamState } from '../../interfaces/stream-state'; 5 | import { AuthService } from '../../services/auth.service'; 6 | 7 | @Component({ 8 | selector: 'app-player', 9 | templateUrl: './player.component.html', 10 | styleUrls: ['./player.component.scss'] 11 | }) 12 | export class PlayerComponent { 13 | files: Array = []; 14 | state: StreamState; 15 | currentFile: any = {}; 16 | 17 | constructor(private audioService: AudioService, cloudService: CloudService, public auth: AuthService) { 18 | // get media files 19 | cloudService.getFiles().subscribe(files => { 20 | this.files = files; 21 | }); 22 | 23 | // listen to stream state 24 | this.audioService.getState() 25 | .subscribe(state => { 26 | this.state = state; 27 | }); 28 | } 29 | 30 | playStream(url) { 31 | this.audioService.playStream(url) 32 | .subscribe(events => { 33 | // listening for fun here 34 | }); 35 | } 36 | 37 | openFile(file, index) { 38 | this.currentFile = { index, file }; 39 | this.audioService.stop(); 40 | this.playStream(file.url); 41 | } 42 | 43 | pause() { 44 | this.audioService.pause(); 45 | } 46 | 47 | play() { 48 | this.audioService.play(); 49 | } 50 | 51 | stop() { 52 | this.audioService.stop(); 53 | } 54 | 55 | next() { 56 | const index = this.currentFile.index + 1; 57 | const file = this.files[index]; 58 | this.openFile(file, index); 59 | } 60 | 61 | previous() { 62 | const index = this.currentFile.index - 1; 63 | const file = this.files[index]; 64 | this.openFile(file, index); 65 | } 66 | 67 | isFirstPlaying() { 68 | return this.currentFile.index === 0; 69 | } 70 | 71 | isLastPlaying() { 72 | return this.currentFile.index === this.files.length - 1; 73 | } 74 | 75 | onSliderChangeEnd(change) { 76 | this.audioService.seekTo(change.value); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | arrow_back 4 | Audio Player - Profile 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ userProfile?.name }} 12 | 13 | 14 |
-------------------------------------------------------------------------------- /src/app/pages/profile/profile.component.scss: -------------------------------------------------------------------------------- 1 | .back-btn { 2 | margin-right:5px; 3 | cursor: pointer; 4 | } 5 | 6 | .profile-card { 7 | margin:20px; 8 | max-width: 400px; 9 | } -------------------------------------------------------------------------------- /src/app/pages/profile/profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileComponent } from './profile.component'; 4 | 5 | describe('ProfileComponent', () => { 6 | let component: ProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProfileComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/pages/profile/profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AuthService } from 'src/app/services/auth.service'; 3 | 4 | @Component({ 5 | selector: 'app-profile', 6 | templateUrl: './profile.component.html', 7 | styleUrls: ['./profile.component.scss'] 8 | }) 9 | export class ProfileComponent implements OnInit { 10 | userProfile; 11 | constructor(authService: AuthService) { 12 | authService.userProfile$.subscribe(profile => { 13 | this.userProfile = profile; 14 | }); 15 | } 16 | 17 | ngOnInit() { 18 | } 19 | } -------------------------------------------------------------------------------- /src/app/services/audio.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AudioService } from './audio.service'; 4 | 5 | describe('AudioService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AudioService = TestBed.get(AudioService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/services/audio.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, BehaviorSubject, Subject } from 'rxjs'; 3 | import { takeUntil } from 'rxjs/operators'; 4 | import * as moment from 'moment'; 5 | import { StreamState } from '../interfaces/stream-state'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class AudioService { 11 | private stop$ = new Subject(); 12 | private audioObj = new Audio(); 13 | audioEvents = [ 14 | 'ended', 'error', 'play', 'playing', 'pause', 'timeupdate', 'canplay', 'loadedmetadata', 'loadstart' 15 | ]; 16 | private state: StreamState = { 17 | playing: false, 18 | readableCurrentTime: '', 19 | readableDuration: '', 20 | duration: undefined, 21 | currentTime: undefined, 22 | canplay: false, 23 | error: false, 24 | }; 25 | 26 | private streamObservable(url) { 27 | return new Observable(observer => { 28 | // Play audio 29 | this.audioObj.src = url; 30 | this.audioObj.load(); 31 | this.audioObj.play(); 32 | 33 | const handler = (event: Event) => { 34 | this.updateStateEvents(event); 35 | observer.next(event); 36 | }; 37 | 38 | this.addEvents(this.audioObj, this.audioEvents, handler); 39 | return () => { 40 | // Stop Playing 41 | this.audioObj.pause(); 42 | this.audioObj.currentTime = 0; 43 | // remove event listeners 44 | this.removeEvents(this.audioObj, this.audioEvents, handler); 45 | // reset state 46 | this.resetState(); 47 | }; 48 | }); 49 | } 50 | 51 | private addEvents(obj, events, handler) { 52 | events.forEach(event => { 53 | obj.addEventListener(event, handler); 54 | }); 55 | } 56 | 57 | private removeEvents(obj, events, handler) { 58 | events.forEach(event => { 59 | obj.removeEventListener(event, handler); 60 | }); 61 | } 62 | 63 | playStream(url) { 64 | return this.streamObservable(url).pipe(takeUntil(this.stop$)); 65 | } 66 | 67 | play() { 68 | this.audioObj.play(); 69 | } 70 | 71 | pause() { 72 | this.audioObj.pause(); 73 | } 74 | 75 | stop() { 76 | this.stop$.next(); 77 | } 78 | 79 | seekTo(seconds) { 80 | this.audioObj.currentTime = seconds; 81 | } 82 | 83 | formatTime(time: number, format: string = 'HH:mm:ss') { 84 | const momentTime = time * 1000; 85 | return moment.utc(momentTime).format(format); 86 | } 87 | 88 | private stateChange: BehaviorSubject = new BehaviorSubject(this.state); 89 | 90 | private updateStateEvents(event: Event): void { 91 | switch (event.type) { 92 | case 'canplay': 93 | this.state.duration = this.audioObj.duration; 94 | this.state.readableDuration = this.formatTime(this.state.duration); 95 | this.state.canplay = true; 96 | break; 97 | case 'playing': 98 | this.state.playing = true; 99 | break; 100 | case 'pause': 101 | this.state.playing = false; 102 | break; 103 | case 'timeupdate': 104 | this.state.currentTime = this.audioObj.currentTime; 105 | this.state.readableCurrentTime = this.formatTime(this.state.currentTime); 106 | break; 107 | case 'error': 108 | this.resetState(); 109 | this.state.error = true; 110 | break; 111 | } 112 | this.stateChange.next(this.state); 113 | } 114 | 115 | private resetState() { 116 | this.state = { 117 | playing: false, 118 | readableCurrentTime: '', 119 | readableDuration: '', 120 | duration: undefined, 121 | currentTime: undefined, 122 | canplay: false, 123 | error: false 124 | }; 125 | } 126 | 127 | getState(): Observable { 128 | return this.stateChange.asObservable(); 129 | } 130 | } -------------------------------------------------------------------------------- /src/app/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthService } from './auth.service'; 4 | 5 | describe('AuthService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AuthService = TestBed.get(AuthService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import * as auth0 from 'auth0-js'; 3 | import { environment } from '../../environments/environment'; 4 | import { Observable, BehaviorSubject, bindNodeCallback, of } from 'rxjs'; 5 | import { Router } from '@angular/router'; 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthService { 10 | 11 | auth0 = new auth0.WebAuth({ 12 | clientID: environment.auth0.clientID, 13 | domain: environment.auth0.domain, 14 | responseType: 'token id_token', 15 | redirectUri: environment.auth0.redirectUri, 16 | scope: 'openid profile email' 17 | }); 18 | 19 | // Track whether or not to renew token 20 | private _authFlag = 'isLoggedIn'; 21 | private _userProfileFlag = 'userProfile'; 22 | 23 | // Store authentication data 24 | // Create stream for token 25 | token$: Observable; 26 | // Create stream for user profile data 27 | userProfile$ = new BehaviorSubject(null); 28 | 29 | // Authentication Navigation 30 | onAuthSuccessUrl = '/'; 31 | onAuthFailureUrl = '/'; 32 | logoutUrl = environment.auth0.logoutUrl; 33 | 34 | // Create observable of Auth0 parseHash method to gather auth results 35 | parseHash$ = bindNodeCallback(this.auth0.parseHash.bind(this.auth0)); 36 | // Create observable of Auth0 checkSession method to 37 | // verify authorization server session and renew tokens 38 | checkSession$ = bindNodeCallback(this.auth0.checkSession.bind(this.auth0)); 39 | 40 | constructor(private router: Router) { 41 | const userProfile = localStorage.getItem(this._userProfileFlag); 42 | if (userProfile) { 43 | this.userProfile$.next(JSON.parse(userProfile)); 44 | } 45 | } 46 | 47 | login = () => this.auth0.authorize(); 48 | 49 | handleLoginCallback = () => { 50 | if (window.location.hash && !this.authenticated) { 51 | this.parseHash$().subscribe({ 52 | next: authResult => { 53 | this._setAuth(authResult); 54 | window.location.hash = ''; 55 | this.router.navigate([this.onAuthSuccessUrl]); 56 | }, 57 | error: err => this._handleError(err) 58 | }); 59 | } 60 | } 61 | 62 | private _setAuth = authResult => { 63 | // Save authentication data and update login status subject 64 | // Observable of token 65 | this.token$ = of(authResult.accessToken); 66 | 67 | const userProfile = authResult.idTokenPayload; 68 | // Emit value for user data subject 69 | this.userProfile$.next(userProfile); 70 | // save userProfile in localStorage 71 | localStorage.setItem(this._userProfileFlag, JSON.stringify(userProfile)); 72 | 73 | // Set flag in local storage stating this app is logged in 74 | localStorage.setItem(this._authFlag, JSON.stringify(true)); 75 | } 76 | 77 | get authenticated(): boolean { 78 | return JSON.parse(localStorage.getItem(this._authFlag)); 79 | } 80 | 81 | renewAuth() { 82 | if (this.authenticated) { 83 | this.checkSession$({}).subscribe({ 84 | next: authResult => this._setAuth(authResult), 85 | error: err => { 86 | localStorage.removeItem(this._authFlag); 87 | localStorage.removeItem(this._userProfileFlag); 88 | this.router.navigate([this.onAuthFailureUrl]); 89 | } 90 | }); 91 | } 92 | } 93 | 94 | logout = () => { 95 | // Set authentication status flag in local storage to false 96 | localStorage.setItem(this._authFlag, JSON.stringify(false)); 97 | // remove the userProfile data 98 | localStorage.removeItem(this._userProfileFlag); 99 | 100 | // This does a refresh and redirects back to the homepage 101 | // Make sure you have the logout URL in your Auth0 102 | // Dashboard Application settings in Allowed Logout URLs 103 | this.auth0.logout({ 104 | returnTo: this.logoutUrl, 105 | clientID: environment.auth0.clientID 106 | }); 107 | }; 108 | 109 | // Utility functions 110 | 111 | private _handleError = err => { 112 | if (err.error_description) { 113 | console.error(`Error: ${err.error_description}`); 114 | } else { 115 | console.error(`Error: ${JSON.stringify(err)}`); 116 | } 117 | }; 118 | } -------------------------------------------------------------------------------- /src/app/services/cloud.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { CloudService } from './cloud.service'; 4 | 5 | describe('CloudService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: CloudService = TestBed.get(CloudService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/services/cloud.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { of } from 'rxjs'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class CloudService { 8 | files: any = [ 9 | // tslint:disable-next-line: max-line-length 10 | { url: 'https://ia801504.us.archive.org/3/items/EdSheeranPerfectOfficialMusicVideoListenVid.com/Ed_Sheeran_-_Perfect_Official_Music_Video%5BListenVid.com%5D.mp3', 11 | name: 'Perfect', 12 | artist: ' Ed Sheeran' 13 | }, 14 | { 15 | // tslint:disable-next-line: max-line-length 16 | url: 'https://ia801609.us.archive.org/16/items/nusratcollection_20170414_0953/Man%20Atkiya%20Beparwah%20De%20Naal%20Nusrat%20Fateh%20Ali%20Khan.mp3', 17 | name: 'Man Atkeya Beparwah', 18 | artist: 'Nusrat Fateh Ali Khan' 19 | }, 20 | { url: 'https://ia801503.us.archive.org/15/items/TheBeatlesPennyLane_201805/The%20Beatles%20-%20Penny%20Lane.mp3', 21 | name: 'Penny Lane', 22 | artist: 'The Beatles' 23 | } 24 | ]; 25 | 26 | getFiles() { 27 | return of(this.files); 28 | } 29 | } -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsingh/auth0-audio/c41cf9b9118854231d06d4221f6e23064f83e202/src/assets/.gitkeep -------------------------------------------------------------------------------- /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 | auth0: { 8 | clientID: 'fpgeZTWjHn8Yb5rC5FjmdfNl63yl5KK9', 9 | domain: 'angular-auth0-app.eu.auth0.com', 10 | redirectUri: 'http://localhost:4200', 11 | logoutUrl: 'http://localhost:4200' 12 | } 13 | }; 14 | /* 15 | * For easier debugging in development mode, you can import the following file 16 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 17 | * 18 | * This import should be commented out in production mode because it will have a negative impact 19 | * on performance if an error is thrown. 20 | */ 21 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 22 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsingh/auth0-audio/c41cf9b9118854231d06d4221f6e23064f83e202/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularAudio 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'hammerjs'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().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/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /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/dist/zone-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 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "src/test.ts", 12 | "src/**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "importHelpers": true, 14 | "target": "es2015", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es2018", 20 | "dom" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warn" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } --------------------------------------------------------------------------------