├── .angulardoc.json ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── projects ├── ng-waveform │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── drag-drop.directive.spec.ts │ │ │ ├── drag-drop.directive.ts │ │ │ ├── ng-waveform.component.spec.ts │ │ │ ├── ng-waveform.component.ts │ │ │ ├── ng-waveform.module.ts │ │ │ ├── ng-waveform.service.spec.ts │ │ │ ├── ng-waveform.service.ts │ │ │ └── region.component.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json └── waveform-demo │ ├── browserslist │ ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json │ ├── karma.conf.js │ ├── src │ ├── app │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ └── waveform │ │ │ ├── waveform.component.html │ │ │ ├── waveform.component.scss │ │ │ ├── waveform.component.spec.ts │ │ │ └── waveform.component.ts │ ├── assets │ │ ├── .gitkeep │ │ └── og-img.jpg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── tslint.json ├── tsconfig.json └── tslint.json /.angulardoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "repoId": "260e7762-074c-4d31-a179-ad374f887ae4", 3 | "lastSync": 0 4 | } -------------------------------------------------------------------------------- /.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 | # NgAudio 2 | 3 | Library to visualize audio waveform. Built as pure Angular library that doesn't wrap any of JS library. With NgWaveform you can create interactive customisable waveform of any audio file in your Angular application. 4 | 5 | ## Quick start 6 | ```bash 7 | npm install --save ng-waveform 8 | ``` 9 | 10 | ```typescript 11 | import { ITimeUpdateEvent, NgWaveformComponent, IRegionPositions } from 'ng-waveform'; 12 | 13 | @ViewChild('waveform', { static: false }) waveform: NgWaveformComponent; 14 | 15 | onPlayButtonClick() { 16 | this.waveform.play(); 17 | } 18 | onPauseButtonClick() { 19 | this.waveform.pause(); 20 | } 21 | ``` 22 | 23 | ```html 24 | 42 | 43 | ``` 44 | 45 | ## API Reference 46 | `NgWaveformComponent` 47 | 48 | 49 | ### Methods 50 | * `play(start: number = 0): void` 51 | Play audio, start - starting position 52 | 53 | * `pause(): void` 54 | Stops audio 55 | 56 | ### Events 57 | * `trackLoaded` - emits when data fetched from Url, returns time in ms spent for fetching. 58 | * `rendered` - emits when waveform is rendered, returns time in ms spent for rendering. 59 | * `durationChange` - emits when duration of audio is changed, returns duration value in seconds. 60 | * `timeUpdate` - emits when current time of audio changes, returns object that implements interface `ITimeUpdateEvent`. 61 | * `paused` - emits when audio paused. Useful for switch Play/Pause buttons in external control. 62 | * `regionChange` - emits when region start or/and end positions change, returns object that implements interface `IRegionPositions`. 63 | 64 | 65 | ### Properties 66 | Name | Description 67 | -------------|------------ 68 | `src: string` | Url of src mp3 file 69 | `height = 100` | Height of component 70 | `waveColor = '#d3d3d3'` | Color of wave 71 | `backgroundColor = 'transparent'` | Background color of component 72 | `overlayBackgroundColor = 'rgba(0, 0, 0, 0.5)'` | Background color of overlay filling component while playing 73 | `useRegion: boolean` | Turn region on 74 | `withRegionLabels: boolean` | Turn region labels on 75 | `regionBackgroundColor = 'transparent'` | Background color of region 76 | `regionStartStickColor = 'red'` | Color of region left border stick 77 | `regionEndStickColor = 'red'` | Color of region right border stick 78 | `regionTextColor = '#000'` | Color of region labels text 79 | `autoplay` | Turn autoplay on load on 80 | 81 | 82 | ### Intrfaces 83 | ```typescript 84 | interface ITimeUpdateEvent { 85 | time: number; 86 | progress: number; 87 | } 88 | ``` 89 | 90 | ```typescript 91 | interface IRegionPositions { 92 | start: number; 93 | end: number; 94 | } 95 | ``` 96 | 97 | ### Credits 98 | Thank you very much for great inspiration [wavesurfer.js](https://wavesurfer-js.org/) team. 99 | 100 | ### Conclusion 101 | Feedback, issues and stars will be very appreciated. 102 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ng-waveform": { 7 | "projectType": "library", 8 | "root": "projects/ng-waveform", 9 | "sourceRoot": "projects/ng-waveform/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-ng-packagr:build", 14 | "options": { 15 | "tsConfig": "projects/ng-waveform/tsconfig.lib.json", 16 | "project": "projects/ng-waveform/ng-package.json" 17 | } 18 | }, 19 | "test": { 20 | "builder": "@angular-devkit/build-angular:karma", 21 | "options": { 22 | "main": "projects/ng-waveform/src/test.ts", 23 | "tsConfig": "projects/ng-waveform/tsconfig.spec.json", 24 | "karmaConfig": "projects/ng-waveform/karma.conf.js" 25 | } 26 | }, 27 | "lint": { 28 | "builder": "@angular-devkit/build-angular:tslint", 29 | "options": { 30 | "tsConfig": [ 31 | "projects/ng-waveform/tsconfig.lib.json", 32 | "projects/ng-waveform/tsconfig.spec.json" 33 | ], 34 | "exclude": [ 35 | "**/node_modules/**" 36 | ] 37 | } 38 | } 39 | } 40 | }, 41 | "waveform-demo": { 42 | "projectType": "application", 43 | "schematics": { 44 | "@schematics/angular:component": { 45 | "style": "scss" 46 | } 47 | }, 48 | "root": "projects/waveform-demo", 49 | "sourceRoot": "projects/waveform-demo/src", 50 | "prefix": "app", 51 | "architect": { 52 | "build": { 53 | "builder": "@angular-devkit/build-angular:browser", 54 | "options": { 55 | "outputPath": "dist/waveform-demo", 56 | "index": "projects/waveform-demo/src/index.html", 57 | "main": "projects/waveform-demo/src/main.ts", 58 | "polyfills": "projects/waveform-demo/src/polyfills.ts", 59 | "tsConfig": "projects/waveform-demo/tsconfig.app.json", 60 | "aot": false, 61 | "assets": [ 62 | "projects/waveform-demo/src/favicon.ico", 63 | "projects/waveform-demo/src/assets" 64 | ], 65 | "styles": [ 66 | "projects/waveform-demo/src/styles.scss" 67 | ], 68 | "scripts": [] 69 | }, 70 | "configurations": { 71 | "production": { 72 | "fileReplacements": [ 73 | { 74 | "replace": "projects/waveform-demo/src/environments/environment.ts", 75 | "with": "projects/waveform-demo/src/environments/environment.prod.ts" 76 | } 77 | ], 78 | "optimization": true, 79 | "outputHashing": "all", 80 | "sourceMap": false, 81 | "extractCss": true, 82 | "namedChunks": false, 83 | "aot": true, 84 | "extractLicenses": true, 85 | "vendorChunk": false, 86 | "buildOptimizer": true, 87 | "budgets": [ 88 | { 89 | "type": "initial", 90 | "maximumWarning": "2mb", 91 | "maximumError": "5mb" 92 | }, 93 | { 94 | "type": "anyComponentStyle", 95 | "maximumWarning": "6kb", 96 | "maximumError": "10kb" 97 | } 98 | ] 99 | } 100 | } 101 | }, 102 | "serve": { 103 | "builder": "@angular-devkit/build-angular:dev-server", 104 | "options": { 105 | "browserTarget": "waveform-demo:build" 106 | }, 107 | "configurations": { 108 | "production": { 109 | "browserTarget": "waveform-demo:build:production" 110 | } 111 | } 112 | }, 113 | "extract-i18n": { 114 | "builder": "@angular-devkit/build-angular:extract-i18n", 115 | "options": { 116 | "browserTarget": "waveform-demo:build" 117 | } 118 | }, 119 | "test": { 120 | "builder": "@angular-devkit/build-angular:karma", 121 | "options": { 122 | "main": "projects/waveform-demo/src/test.ts", 123 | "polyfills": "projects/waveform-demo/src/polyfills.ts", 124 | "tsConfig": "projects/waveform-demo/tsconfig.spec.json", 125 | "karmaConfig": "projects/waveform-demo/karma.conf.js", 126 | "assets": [ 127 | "projects/waveform-demo/src/favicon.ico", 128 | "projects/waveform-demo/src/assets" 129 | ], 130 | "styles": [ 131 | "projects/waveform-demo/src/styles.scss" 132 | ], 133 | "scripts": [] 134 | } 135 | }, 136 | "lint": { 137 | "builder": "@angular-devkit/build-angular:tslint", 138 | "options": { 139 | "tsConfig": [ 140 | "projects/waveform-demo/tsconfig.app.json", 141 | "projects/waveform-demo/tsconfig.spec.json", 142 | "projects/waveform-demo/e2e/tsconfig.json" 143 | ], 144 | "exclude": [ 145 | "**/node_modules/**" 146 | ] 147 | } 148 | }, 149 | "e2e": { 150 | "builder": "@angular-devkit/build-angular:protractor", 151 | "options": { 152 | "protractorConfig": "projects/waveform-demo/e2e/protractor.conf.js", 153 | "devServerTarget": "waveform-demo:serve" 154 | }, 155 | "configurations": { 156 | "production": { 157 | "devServerTarget": "waveform-demo:serve:production" 158 | } 159 | } 160 | } 161 | } 162 | }}, 163 | "defaultProject": "ng-waveform" 164 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-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.2.14", 15 | "@angular/common": "~8.2.14", 16 | "@angular/compiler": "~8.2.14", 17 | "@angular/core": "~8.2.14", 18 | "@angular/forms": "~8.2.14", 19 | "@angular/platform-browser": "~8.2.14", 20 | "@angular/platform-browser-dynamic": "~8.2.14", 21 | "@angular/router": "~8.2.14", 22 | "ng-waveform": "^0.1.3", 23 | "rxjs": "~6.4.0", 24 | "s": "^1.0.0", 25 | "tslib": "^1.10.0", 26 | "zone.js": "~0.9.1" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.803.20", 30 | "@angular-devkit/build-ng-packagr": "~0.803.20", 31 | "@angular/cli": "~8.3.20", 32 | "@angular/compiler-cli": "~8.2.14", 33 | "@angular/language-service": "~8.2.14", 34 | "@types/node": "~8.9.4", 35 | "@types/jasmine": "~3.3.8", 36 | "@types/jasminewd2": "~2.0.3", 37 | "codelyzer": "^5.0.0", 38 | "jasmine-core": "~3.4.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "~4.1.0", 41 | "karma-chrome-launcher": "~2.2.0", 42 | "karma-coverage-istanbul-reporter": "~2.0.1", 43 | "karma-jasmine": "~2.0.1", 44 | "karma-jasmine-html-reporter": "^1.4.0", 45 | "ng-packagr": "^5.4.0", 46 | "protractor": "~5.4.0", 47 | "ts-node": "~7.0.0", 48 | "tsickle": "^0.37.0", 49 | "tslint": "~5.15.0", 50 | "typescript": "~3.5.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /projects/ng-waveform/README.md: -------------------------------------------------------------------------------- 1 | # NgWaveform 2 | 3 | Library to visualize audio waveform. Built as pure Angular library that doesn't wrap any of JS library. With NgWaveform you can create interactive customisable waveform of any audio file in your Angular application. 4 | 5 | ## Quick start 6 | ```bash 7 | npm install --save ng-waveform 8 | ``` 9 | 10 | ```typescript 11 | import { NgWaveformModule } from 'ng-waveform'; 12 | ``` 13 | 14 | ```typescript 15 | import { ITimeUpdateEvent, NgWaveformComponent, IRegionPositions } from 'ng-waveform'; 16 | 17 | @ViewChild('waveform', { static: false }) waveform: NgWaveformComponent; 18 | 19 | onPlayButtonClick() { 20 | this.waveform.play(); 21 | } 22 | onPauseButtonClick() { 23 | this.waveform.pause(); 24 | } 25 | ``` 26 | 27 | ```html 28 | 46 | 47 | ``` 48 | 49 | ## API Reference 50 | `NgWaveformComponent` 51 | 52 | 53 | ### Methods 54 | * `play(start: number = 0): void` 55 | Play audio, start - starting position 56 | 57 | * `pause(): void` 58 | Stops audio 59 | 60 | ### Events 61 | * `trackLoaded` - emits when data fetched from Url, returns time in ms spent for fetching. 62 | * `rendered` - emits when waveform is rendered, returns time in ms spent for rendering. 63 | * `durationChange` - emits when duration of audio is changed, returns duration value in seconds. 64 | * `timeUpdate` - emits when current time of audio changes, returns object that implements interface `ITimeUpdateEvent`. 65 | * `paused` - emits when audio paused. Useful for switch Play/Pause buttons in external control. 66 | * `regionChange` - emits when region start or/and end positions change, returns object that implements interface `IRegionPositions`. 67 | 68 | 69 | ### Properties 70 | Name | Description 71 | -------------|------------ 72 | `src: string` | Url of src mp3 file 73 | `height = 100` | Height of component 74 | `waveColor = '#d3d3d3'` | Color of wave 75 | `backgroundColor = 'transparent'` | Background color of component 76 | `overlayBackgroundColor = 'rgba(0, 0, 0, 0.5)'` | Background color of overlay filling component while playing 77 | `useRegion: boolean` | Turn region on 78 | `withRegionLabels: boolean` | Turn region labels on 79 | `regionBackgroundColor = 'transparent'` | Background color of region 80 | `regionStartStickColor = 'red'` | Color of region left border stick 81 | `regionEndStickColor = 'red'` | Color of region right border stick 82 | `regionTextColor = '#000'` | Color of region labels text 83 | `autoplay` | Turn autoplay on load on 84 | 85 | 86 | ### Intrfaces 87 | ```typescript 88 | interface ITimeUpdateEvent { 89 | time: number; 90 | progress: number; 91 | } 92 | ``` 93 | 94 | ```typescript 95 | interface IRegionPositions { 96 | start: number; 97 | end: number; 98 | } 99 | ``` 100 | 101 | ### Credits 102 | Thank you very much for great inspiration [wavesurfer.js](https://wavesurfer-js.org/) team. 103 | 104 | ### Conclusion 105 | Feedback, issues and stars will be very appreciated. 106 | -------------------------------------------------------------------------------- /projects/ng-waveform/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/ng-waveform'), 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 | -------------------------------------------------------------------------------- /projects/ng-waveform/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ng-waveform", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ng-waveform/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-waveform", 3 | "version": "0.2.0", 4 | "peerDependencies": { 5 | "@angular/common": "^8.2.14", 6 | "@angular/core": "^8.2.14" 7 | }, 8 | "author": "Arthur Groupp (https://groupp.org)", 9 | "license": "MIT", 10 | "repository": { 11 | "type" : "git", 12 | "url" : "https://github.com/agroupp/ng-audio.git" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/drag-drop.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { DragDropDirective } from './drag-drop.directive'; 2 | 3 | describe('DragDropDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new DragDropDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/drag-drop.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, HostListener, Output, EventEmitter, ElementRef } from '@angular/core'; 2 | 3 | 4 | /** Directive to host drag and drop of region borders */ 5 | @Directive({ 6 | selector: '[libDragDrop]' 7 | }) 8 | export class DragDropDirective { 9 | private isDragging = false; 10 | 11 | @Output() dragging = new EventEmitter(); 12 | @Output() dragFinished = new EventEmitter(); 13 | 14 | @HostListener('window:mousedown', ['$event.target']) onMouseDown(target: HTMLElement) { 15 | if (this.hostElement.nativeElement.contains(target)) { 16 | this.isDragging = true; 17 | } 18 | } 19 | 20 | @HostListener('window:mousemove', ['$event.x']) onMouseMove(x: number) { 21 | if (this.isDragging) { 22 | this.dragging.emit(x); 23 | } 24 | } 25 | 26 | @HostListener('window:mouseup', ['$event.x']) onMouseUp(x: number) { 27 | if (this.isDragging) { 28 | this.isDragging = false; 29 | this.dragFinished.emit(x); 30 | } 31 | } 32 | 33 | constructor(private hostElement: ElementRef) { } 34 | } 35 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/ng-waveform.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NgWaveformComponent } from './ng-waveform.component'; 4 | 5 | describe('NgWaveformComponent', () => { 6 | let component: NgWaveformComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ NgWaveformComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NgWaveformComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/ng-waveform.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, OnInit, OnChanges, OnDestroy, AfterViewInit, 3 | Input, ViewChild, ElementRef, Output, EventEmitter 4 | } from '@angular/core'; 5 | import { from, interval, BehaviorSubject, of } from 'rxjs'; 6 | import { switchMap, tap, takeWhile, takeUntil } from 'rxjs/operators'; 7 | 8 | import { NgWaveformService } from './ng-waveform.service'; 9 | import { IRegionPositions } from './region.component'; 10 | 11 | export interface ITimeUpdateEvent { 12 | time: number; 13 | progress: number; 14 | } 15 | 16 | @Component({ 17 | selector: 'ng-waveform', 18 | template: ` 19 |
27 | 28 |
29 | 37 |
`, 38 | styles: [ 39 | `.ng-waveform-overlay { 40 | position:absolute; 41 | top:0; 42 | bottom:0; 43 | left:0; 44 | pointer-events:none; 45 | transition:100ms linear width; 46 | }` 47 | ] 48 | }) 49 | export class NgWaveformComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit { 50 | @Input() src: string; 51 | @Input() height = 100; 52 | @Input() waveColor = '#d3d3d3'; 53 | @Input() backgroundColor = 'transparent'; 54 | @Input() overlayBackgroundColor = 'rgba(0, 0, 0, 0.5)'; 55 | @Input() regionBackgroundColor = 'transparent'; 56 | @Input() regionStartStickColor = 'red'; 57 | @Input() regionEndStickColor = 'red'; 58 | @Input() regionTextColor = '#000'; 59 | @Input() useRegion = false; 60 | @Input() withRegionLabels = true; 61 | @Input() autoplay = false; 62 | 63 | @Output() trackLoaded = new EventEmitter(); 64 | @Output() rendered = new EventEmitter(); 65 | @Output() durationChange = new EventEmitter(); 66 | @Output() timeUpdate = new EventEmitter(); 67 | @Output() paused = new EventEmitter(); 68 | @Output() regionChange = new EventEmitter(); 69 | 70 | srcUrl: string; 71 | 72 | private audioCtx: AudioContext; 73 | private audioCtxSource: AudioBufferSourceNode; 74 | 75 | // The audioBuffer objects appear differently on Safari vs Chrome hence the use of any 76 | private audioBuffer: any; 77 | 78 | private canvasCtx: CanvasRenderingContext2D; 79 | @ViewChild('wrapperEl', {static: false}) private wrapperEl: ElementRef; 80 | @ViewChild('canvasEl', {static: false}) private canvasEl: ElementRef; 81 | @ViewChild('overlayEl', {static: false}) private overlayEl: ElementRef; 82 | private wrapper: HTMLDivElement; 83 | private canvas: HTMLCanvasElement; 84 | private overlay: HTMLDivElement; 85 | 86 | // tslint:disable: variable-name 87 | private _isPlaying = false; 88 | private _isPlayingSubj = new BehaviorSubject(false); 89 | private _isPlaying$ = this._isPlayingSubj.asObservable(); 90 | private _duration: number; 91 | private _durationSubj = new BehaviorSubject(0); 92 | private _currentTime = 0; 93 | private _savedCurrentTime = 0; 94 | private _audioContextStartTime = 0; 95 | private _progress = 0; 96 | private _loaded = false; 97 | private _regionSubj = new BehaviorSubject({start: 0, end: 0}); 98 | private _region: IRegionPositions; 99 | private _stopAtRegionEnd = false; 100 | // tslint:enable: variable-name 101 | 102 | get region() { return this._region; } 103 | get progress() { return this._progress; } 104 | get loaded() { return this._loaded; } 105 | get duration() { return this._duration; } 106 | 107 | constructor(private service: NgWaveformService) { } 108 | 109 | ngOnInit() { 110 | // tslint:disable-next-line:no-string-literal 111 | const AudioContext = window['AudioContext'] || window['webkitAudioContext']; 112 | this.audioCtx = new AudioContext(); 113 | 114 | this._isPlaying$.pipe( 115 | tap(isPlaying => this._isPlaying = isPlaying), 116 | switchMap(isPlaying => interval(80).pipe( 117 | tap(t => this.setAudioCtxStartTime(t)), 118 | takeWhile(() => isPlaying)) 119 | ), 120 | switchMap(() => of(this.audioCtx.currentTime - this._audioContextStartTime + this._savedCurrentTime)), 121 | ) 122 | .subscribe(time => { 123 | this._progress = time / this._duration * 100; 124 | this.timeUpdate.emit({ time, progress: this._progress }); 125 | this._currentTime = time; 126 | if (this.useRegion) { 127 | if (this._stopAtRegionEnd && time > this.region.end) { 128 | this.pause(); 129 | this._stopAtRegionEnd = false; 130 | } 131 | } 132 | if (this._progress > 100) { 133 | this.pause(); 134 | this.setCurrentTime(this.startPosition); 135 | } 136 | }); 137 | 138 | this._durationSubj.asObservable().subscribe(duration => { 139 | this._duration = duration; 140 | /* Set initial region */ 141 | if (this.useRegion) { 142 | const start = this._region && this._region.start ? this._region.start : 0; 143 | const end = this._region && this._region.end ? this._region.end : this._duration; 144 | this._regionSubj.next({ start, end }); 145 | } 146 | this.durationChange.emit(this._duration); 147 | }); 148 | 149 | this._regionSubj.asObservable().subscribe(region => { 150 | this._region = region; 151 | this.setCurrentTime(this._region.start); 152 | this.regionChange.emit(this._region); 153 | }); 154 | } 155 | 156 | ngAfterViewInit() { 157 | this.canvas = this.canvasEl.nativeElement as HTMLCanvasElement; 158 | this.canvasCtx = this.canvas.getContext('2d'); 159 | const dpr = window.devicePixelRatio || 1; 160 | this.canvasCtx.scale(dpr, dpr); 161 | this.wrapper = this.wrapperEl.nativeElement as HTMLDivElement; 162 | this.overlay = this.overlayEl.nativeElement as HTMLDivElement; 163 | } 164 | 165 | ngOnChanges() { 166 | if (!this.src) { 167 | return; 168 | } 169 | if (!this.srcUrl || this.srcUrl !== this.src) { 170 | this.srcUrl = this.src; 171 | this.loadAudio(); 172 | } 173 | } 174 | 175 | /** 176 | * Gets the time which playback should begin from 177 | * Either the start of the region, or 0 178 | * @returns start position 179 | */ 180 | private get startPosition(): number { 181 | return this.useRegion ? this.region.start : 0; 182 | } 183 | 184 | /** 185 | * Play audio 186 | * @param start starting position 187 | */ 188 | play(start: number = 0): void { 189 | if (this._isPlaying) { 190 | return; 191 | } 192 | try { 193 | this.setAudioSource(); 194 | const startPoint = start ? start : this._savedCurrentTime; 195 | this.setAudioSource(); 196 | this.audioCtxSource.start(0, startPoint); 197 | this.audioCtxSource.connect(this.audioCtx.destination); 198 | this._isPlayingSubj.next(true); 199 | if (this.useRegion && startPoint < this.region.end - 0.1) { 200 | this._stopAtRegionEnd = true; 201 | } 202 | } catch (err) { 203 | console.error(err); 204 | this._isPlayingSubj.next(false); 205 | } 206 | } 207 | 208 | /** 209 | * Pause audio 210 | */ 211 | pause() { 212 | if (!this._isPlaying) { 213 | return; 214 | } 215 | this._savedCurrentTime = this._currentTime; 216 | try { 217 | this.audioCtxSource.disconnect(this.audioCtx.destination); 218 | this.audioCtxSource.stop(); 219 | this._isPlayingSubj.next(false); 220 | this.paused.emit(); 221 | } catch (err) { 222 | console.error(err); 223 | this._isPlayingSubj.next(false); 224 | } 225 | } 226 | 227 | /** 228 | * Change start position of region 229 | * @param time time in seconds 230 | */ 231 | setRegionStart(time: number): void { 232 | if (time == null || isNaN(time) || time < 0 || time > this._duration || time === this._region.start) { 233 | return; 234 | } 235 | this._regionSubj.next({start: time, end: this._region.end}); 236 | } 237 | 238 | /** 239 | * Change end position of region 240 | * @param time time in seconds 241 | */ 242 | setRegionEnd(time: number): void { 243 | if (time == null || isNaN(time) || time < 0 || time > this._duration || time === this._region.end) { 244 | return; 245 | } 246 | this._regionSubj.next({start: this._region.start, end: time}); 247 | } 248 | 249 | onWrapperClick(event: MouseEvent): void { 250 | if (!this.wrapper.getBoundingClientRect) { 251 | return; 252 | } 253 | const wrapperRect = this.wrapper.getBoundingClientRect(); 254 | const x = event.x - wrapperRect.left; 255 | const time = this._duration * (x / wrapperRect.width); 256 | const ROUND_MULTIPLIER = 10000000; 257 | if (Math.round(time * ROUND_MULTIPLIER) !== Math.round(this.region.end * ROUND_MULTIPLIER)) { 258 | this.setCurrentTime(time); 259 | } 260 | } 261 | 262 | onRegionChange(position: IRegionPositions) { 263 | this._regionSubj.next(position); 264 | } 265 | 266 | /** 267 | * Sets current time when something changes 268 | * @param time time to set in seconds 269 | */ 270 | private setCurrentTime(time: number) { 271 | this._savedCurrentTime = time; 272 | const isPlaying = this._isPlaying; 273 | this._isPlayingSubj.next(false); 274 | if (this.audioCtxSource && isPlaying ) { 275 | this.audioCtxSource.stop(); 276 | } 277 | this._progress = this._savedCurrentTime / this._duration * 100; 278 | this.timeUpdate.emit({ time: this._savedCurrentTime, progress: this._progress }); 279 | if (isPlaying) { 280 | this.play(); 281 | } 282 | } 283 | 284 | /** 285 | * Load mp3 and render it 286 | */ 287 | private loadAudio() { 288 | this._currentTime = 0; 289 | this._savedCurrentTime = 0; 290 | this._regionSubj.next({start: 0, end: 0}); 291 | this._loaded = false; 292 | const audioBuffer$ = this.service.loadTrack(this.srcUrl).pipe( 293 | switchMap(buff => this.decode(buff)) 294 | ); 295 | const now = Date.now(); 296 | audioBuffer$.subscribe(audioBuffer => { 297 | this.trackLoaded.emit(Date.now() - now); 298 | this.audioBuffer = audioBuffer; 299 | this._durationSubj.next(this.audioBuffer.duration); 300 | this._loaded = true; 301 | this.render(); 302 | if (this.autoplay) { 303 | this.play(); 304 | } 305 | }); 306 | } 307 | 308 | /** 309 | * Convert raw data from mp3 into Audio context decoded data 310 | * @param buffer Raw data from mp3 311 | */ 312 | private decode(buffer: ArrayBuffer) { 313 | return new Promise( 314 | (resolve, reject) => { 315 | this.audioCtx.decodeAudioData(buffer, (decodedBuffer) => { resolve(decodedBuffer) }) 316 | }) 317 | } 318 | 319 | /** 320 | * Set Audio context source used to play audio 321 | * @param buffer audio buffer array 322 | */ 323 | private setAudioSource() { 324 | this.audioCtxSource = this.audioCtx.createBufferSource(); 325 | this.audioCtxSource.buffer = this.audioBuffer; 326 | } 327 | 328 | private setupCanvas(): void { 329 | if (!this.canvas) { 330 | return; 331 | } 332 | this.canvas.height = this.height; 333 | this.canvas.width = this.wrapper.offsetWidth; 334 | this.canvasCtx.fillStyle = this.waveColor; 335 | this.canvasCtx.translate(0, this.canvas.height / 2); 336 | this.canvasCtx.fillRect(0, 0, this.canvas.width, 1); 337 | } 338 | 339 | private render() { 340 | const now = Date.now(); 341 | const rawChannel0 = this.audioBuffer.getChannelData(0); 342 | this.setupCanvas(); 343 | const length = this.canvas.width; 344 | const filteredRaw = this.service.filterRaw(rawChannel0, length); 345 | const height = this.canvas.height; 346 | filteredRaw.forEach((item, i) => { 347 | this.canvasCtx.fillRect(i + 1, (item * height) / -2, 1, item * height); 348 | }); 349 | this.rendered.emit(Date.now() - now); 350 | } 351 | 352 | private setAudioCtxStartTime(tick: number): void { 353 | if (tick !== 0) { 354 | return; 355 | } 356 | this._audioContextStartTime = this.audioCtx.currentTime; 357 | } 358 | 359 | ngOnDestroy() { 360 | if (this.audioCtxSource) { 361 | this.audioCtxSource.stop(); 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/ng-waveform.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgWaveformComponent } from './ng-waveform.component'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { DragDropDirective } from './drag-drop.directive'; 6 | 7 | import { RegionComponent } from './region.component'; 8 | 9 | @NgModule({ 10 | declarations: [NgWaveformComponent, DragDropDirective, RegionComponent], 11 | imports: [ 12 | CommonModule, 13 | HttpClientModule 14 | ], 15 | exports: [NgWaveformComponent] 16 | }) 17 | export class NgWaveformModule { } 18 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/ng-waveform.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { NgWaveformService } from './ng-waveform.service'; 4 | 5 | describe('NgWaveformService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: NgWaveformService = TestBed.get(NgWaveformService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/ng-waveform.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class NgWaveformService { 8 | 9 | constructor(private http: HttpClient) { } 10 | 11 | /** 12 | * Fetches Blod from Url 13 | * @param srcUrl Audio Blob Url 14 | */ 15 | loadTrack(srcUrl: string) { 16 | return this.http.get(srcUrl, {responseType: 'arraybuffer'}); 17 | } 18 | 19 | /** 20 | * Removes unnecessary nodes according to widget width 21 | * @param data audio buffer 22 | * @param length number of neccessary nodes 23 | */ 24 | filterRaw(data: Float32Array, length: number): Float32Array { 25 | const blockSize = Math.floor(data.length / length); 26 | const result: number[] = []; 27 | for (let i = 0; i < length; i++) { 28 | const sample = data.slice(blockSize * i, blockSize * i + blockSize); 29 | const avg = sample.reduce((prev, curr) => Math.abs(prev) + Math.abs(curr), 0) / sample.length; 30 | result.push(avg); 31 | } 32 | return this.normalize(new Float32Array(result)); 33 | } 34 | 35 | /** 36 | * Normalizes data to 1 37 | * @param data audio buffer 38 | */ 39 | normalize(data: Float32Array): Float32Array { 40 | const multiplier = Math.pow(Math.max(...data), -1); 41 | return data.map(item => item * multiplier); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/lib/region.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, OnChanges, AfterViewInit, AfterViewChecked, 3 | Input, ViewChild, ElementRef, Output, EventEmitter, ChangeDetectorRef 4 | } from '@angular/core'; 5 | 6 | export interface IRegionPositions { 7 | start: number; 8 | end: number; 9 | } 10 | 11 | /** 12 | * Region component. 13 | */ 14 | @Component({ 15 | selector: 'ng-waveform-region', 16 | template: ` 17 |
18 |
24 |
26 |
28 | {{start | number:'1.2-2'}} 30 |
31 |
32 |
34 |
36 | {{end | number:'1.2-2'}} 38 |
39 |
40 |
41 |
42 | {{(end - start) | number:'1.2-2'}}s 43 |
44 |
45 |
46 |
47 | `, 48 | styles: [ 49 | `.wrapper { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | right: 0; 54 | bottom: 0; 55 | z-index: 10; 56 | pointer-events: none; 57 | }`, 58 | `.region { 59 | position: absolute; 60 | top: 0; 61 | bottom: 0; 62 | }`, 63 | `.ng-waveform-region-start-wrapper, .ng-waveform-region-end-wrapper { 64 | position: absolute; 65 | top: 0; 66 | bottom: 0; 67 | width: 10px; 68 | cursor: e-resize; 69 | pointer-events: all; 70 | }`, 71 | `.ng-waveform-region-start-wrapper { 72 | left: -4px; 73 | }`, 74 | `.ng-waveform-region-end-wrapper { 75 | right: -4px; 76 | }`, 77 | `.ng-waveform-region-stick { 78 | width: 2px; 79 | margin: 0 auto; 80 | }`, 81 | `.ng-waveform-region-label { 82 | font-size: 13px; 83 | position: absolute; 84 | bottom: -25px; 85 | max-width: 30px; 86 | min-width: 30px; 87 | text-align: center; 88 | left: -10px; 89 | -webkit-user-select: none; 90 | -moz-user-select: none; 91 | -ms-user-select: none; 92 | user-select: none; 93 | }`, 94 | `.ng-waveform-region-length { 95 | position: absolute; 96 | bottom: -25px; 97 | display: flex; 98 | align-items: center; 99 | left: 10px; 100 | right: 10px; 101 | }`, 102 | `.ng-waveform-region-length span { 103 | text-align: center; 104 | display: block; 105 | max-width: 30px; 106 | min-width: 30px; 107 | font-size: 13px; 108 | -webkit-user-select: none; 109 | -moz-user-select: none; 110 | -ms-user-select: none; 111 | user-select: none; 112 | }`, 113 | `.ng-waveform-region-length .line { 114 | flex: 1 1 auto; 115 | margin: 0 10px; 116 | height: 1px; 117 | }` 118 | ] 119 | }) 120 | export class RegionComponent implements OnChanges, AfterViewInit, AfterViewChecked { 121 | @Input() start = 0; 122 | @Input() end = 0; 123 | @Input() duration = 0; 124 | @Input() regionBackgroundColor = 'transparent'; 125 | @Input() startStickColor = 'red'; 126 | @Input() endStickColor = 'red'; 127 | @Input() regionTextColor = '#000'; 128 | @Input() withLabels = true; 129 | position: IRegionPositions = {start: 0, end: 0}; 130 | 131 | /** Emits when region length changes */ 132 | @Output() valueChanges = new EventEmitter(); 133 | 134 | @ViewChild('wrapper', {static: false}) private wrapperEl: ElementRef; 135 | private wrapper: HTMLDivElement; 136 | 137 | constructor(private cd: ChangeDetectorRef) {} 138 | 139 | ngAfterViewInit() { 140 | this.wrapper = this.wrapperEl.nativeElement as HTMLDivElement; 141 | this.init(); 142 | } 143 | 144 | ngAfterViewChecked() { 145 | this.cd.detectChanges(); 146 | } 147 | 148 | ngOnChanges() { 149 | this.init(); 150 | } 151 | 152 | private init(): void { 153 | if (this.end === 0 || this.duration === 0 || !this.wrapper) { 154 | return; 155 | } 156 | this.position = { 157 | start: (this.start / this.duration) * 100, 158 | end: (this.end / this.duration) * 100 159 | }; 160 | } 161 | 162 | private update() { 163 | 164 | } 165 | 166 | onRegionStartDragging(x: number): void { 167 | this.onDragging('start', x); 168 | } 169 | 170 | onRegionEndDragging(x: number): void { 171 | this.onDragging('end', x); 172 | } 173 | 174 | private onDragging(element: 'start' | 'end', x: number): void { 175 | if (!this.wrapper || !this.wrapper.getBoundingClientRect) { 176 | return; 177 | } 178 | const rect = this.wrapper.getBoundingClientRect(); 179 | x = x - rect.left; 180 | const position = x > rect.width ? 100 : (x / rect.width) * 100; 181 | if (element === 'start') { 182 | this.position.start = x < 0 ? 0 : position; 183 | this.start = this.position.start * this.duration / 100; 184 | } else if (element === 'end') { 185 | this.position.end = x < 1 ? 0.01 : position; 186 | this.end = this.position.end * this.duration / 100; 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /projects/ng-waveform/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ng-waveform 3 | */ 4 | 5 | export * from './lib/ng-waveform.component'; 6 | export * from './lib/ng-waveform.module'; 7 | export { IRegionPositions } from './lib/region.component'; 8 | -------------------------------------------------------------------------------- /projects/ng-waveform/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'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: any; 12 | 13 | // First, initialize the Angular testing environment. 14 | getTestBed().initTestEnvironment( 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting() 17 | ); 18 | // Then we find all the tests. 19 | const context = require.context('./', true, /\.spec\.ts$/); 20 | // And load the modules. 21 | context.keys().map(context); 22 | -------------------------------------------------------------------------------- /projects/ng-waveform/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ] 13 | }, 14 | "angularCompilerOptions": { 15 | "annotateForClosureCompiler": true, 16 | "skipTemplateCodegen": true, 17 | "strictMetadataEmit": true, 18 | "fullTemplateTypeCheck": true, 19 | "strictInjectionParameters": true, 20 | "enableResourceInlining": true 21 | }, 22 | "exclude": [ 23 | "src/test.ts", 24 | "**/*.spec.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /projects/ng-waveform/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 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/ng-waveform/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/waveform-demo/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'. -------------------------------------------------------------------------------- /projects/waveform-demo/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 | }; -------------------------------------------------------------------------------- /projects/waveform-demo/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('waveform-demo app is running!'); 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 | -------------------------------------------------------------------------------- /projects/waveform-demo/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 .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/waveform-demo/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 | -------------------------------------------------------------------------------- /projects/waveform-demo/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/waveform-demo'), 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 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

By Arthur Groupp. Source code is here.

5 |

Feedback, issues and Git stars are highly appreciated.

6 |
7 |
8 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | width: 100%; 6 | min-height: 100vh; 7 | footer { 8 | padding: 0 2rem; 9 | margin-top: 5rem; 10 | text-align: center; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'waveform-demo'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('waveform-demo'); 23 | }); 24 | 25 | it('should render title', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('.content span').textContent).toContain('waveform-demo app is running!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | 6 | import { NgWaveformModule } from 'ng-waveform'; 7 | // import { NgWaveformModule } from '../../../../dist/ng-waveform'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { WaveformDemoComponent } from './waveform/waveform.component'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent, 15 | WaveformDemoComponent 16 | ], 17 | imports: [ 18 | BrowserModule, 19 | ReactiveFormsModule, 20 | HttpClientModule, 21 | NgWaveformModule 22 | ], 23 | providers: [], 24 | bootstrap: [AppComponent] 25 | }) 26 | export class AppModule { } 27 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/waveform/waveform.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Demo page of NgWaveform Angular component

4 |

With NgWaveform you can create interactive customisable waveform of any audio file in your Angular application.

5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 |
13 | or 14 |
15 |
16 |
17 | 18 | 19 |
20 |
Invalid URL was entered
21 |
22 |
23 |
24 |
25 |
26 |

Loading...

27 | 44 | 45 |
46 |
47 |
48 |
49 | 54 | 59 |
60 |
61 |
62 |
63 |
64 |

Telemetry

65 |

Time to load track:{{trackLoadTime}}ms

66 |

Time to render wave:{{renderingTime}}ms

67 |

Duration:{{duration | number: '1.2-2'}}s

68 |

Current time:{{currentTime?.time | number: '1.2-2'}}s

69 |

Progress:{{currentTime?.progress | number: '1.2-2'}}%

70 |
71 |
72 |

Region control

73 |
74 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 |
87 |
88 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/waveform/waveform.component.scss: -------------------------------------------------------------------------------- 1 | $width: 800px; 2 | h1, h2 { 3 | text-align: center; 4 | margin: 0 auto; 5 | margin-bottom: 2rem; 6 | } 7 | h2 { 8 | font-size: 1.2rem; 9 | font-weight: 400; 10 | max-width: 80%; 11 | } 12 | .section { 13 | margin-bottom: 2rem; 14 | .container { 15 | max-width: $width; 16 | min-width: $width; 17 | margin: 0 auto; 18 | } 19 | } 20 | 21 | .default-audio, .divider { 22 | text-align: center; 23 | margin: 1rem auto; 24 | button { 25 | background: #ff009e; 26 | border: none; 27 | outline: none; 28 | color: #fff; 29 | border-radius: 7px; 30 | padding: 16px 12px; 31 | cursor: pointer; 32 | font-size: 16px; 33 | } 34 | } 35 | 36 | .src-form { 37 | min-height: 47px; 38 | .form-row { 39 | display: flex; 40 | input { 41 | flex: 1 1 auto; 42 | padding: 6px 10px; 43 | box-sizing: border-box; 44 | outline: none; 45 | } 46 | } 47 | .error-row { 48 | font-size: 13px; 49 | color: red; 50 | } 51 | } 52 | .waveform { 53 | max-width: $width; 54 | min-width: $width; 55 | margin: 0 auto; 56 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 57 | background: linear-gradient(to right, #7240e9, #17f327); 58 | } 59 | .controls { 60 | text-align: center; 61 | 62 | .icon-button { 63 | border: none; 64 | background: none; 65 | outline: none; 66 | cursor: pointer; 67 | } 68 | } 69 | 70 | .label { 71 | min-width: 180px; 72 | max-width: 180px; 73 | font-weight: 600; 74 | display: inline-block; 75 | } 76 | .value { 77 | display: inline-block; 78 | min-width: 100px; 79 | text-align: right; 80 | margin-right: 5px; 81 | } 82 | .region-controls { 83 | .form-row { 84 | margin-bottom: 1rem; 85 | } 86 | label { 87 | margin-right: 5px; 88 | font-weight: 600; 89 | } 90 | input { 91 | margin-right: 20px; 92 | padding: 6px 10px; 93 | box-sizing: border-box; 94 | outline: none; 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/waveform/waveform.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { WaveformComponent } from './waveform.component'; 4 | 5 | describe('WaveformComponent', () => { 6 | let component: WaveformComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ WaveformComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(WaveformComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/app/waveform/waveform.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { FormGroup, FormControl } from '@angular/forms'; 3 | import { HttpClient, HttpParams } from '@angular/common/http'; 4 | import { ITimeUpdateEvent, NgWaveformComponent, IRegionPositions } from 'ng-waveform'; 5 | // import { ITimeUpdateEvent, NgWaveformComponent, IRegionPositions } from '../../../../../dist/ng-waveform'; 6 | import { map } from 'rxjs/operators'; 7 | 8 | @Component({ 9 | selector: 'app-waveform', 10 | templateUrl: './waveform.component.html', 11 | styleUrls: ['./waveform.component.scss'] 12 | }) 13 | export class WaveformDemoComponent implements OnInit { 14 | @ViewChild('waveform', { static: false }) waveform: NgWaveformComponent; 15 | play = false; 16 | isLoaded = false; 17 | trackLoadTime: number; 18 | renderingTime: number; 19 | duration: number; 20 | currentTime: ITimeUpdateEvent; 21 | regionPositions: IRegionPositions; 22 | 23 | srcForm: FormGroup; 24 | isSrcError = false; 25 | isAudioQueried = false; 26 | 27 | isRegionCtrl = new FormControl(true); 28 | regionStartCtrl = new FormControl(0); 29 | regionEndCtrl = new FormControl(0); 30 | 31 | useRegion = true; 32 | src: string; 33 | constructor(private http: HttpClient) { } 34 | 35 | ngOnInit() { 36 | this.srcForm = new FormGroup({src: new FormControl()}); 37 | this.srcForm.valueChanges.subscribe(() => this.isSrcError = false); 38 | this.isRegionCtrl.valueChanges.subscribe(value => this.useRegion = value); 39 | this.regionStartCtrl.valueChanges.subscribe(value => { 40 | if (!this.waveform) { 41 | return; 42 | } 43 | this.waveform.setRegionStart(value); 44 | }); 45 | this.regionEndCtrl.valueChanges.subscribe(value => { 46 | if (!this.waveform) { 47 | return; 48 | } 49 | this.waveform.setRegionEnd(value); 50 | }); 51 | } 52 | 53 | onSrcFormSubmit() { 54 | this.isAudioQueried = false; 55 | this.isLoaded = false; 56 | this.play = false; 57 | this.regionStartCtrl.setValue(0); 58 | this.regionEndCtrl.setValue(0); 59 | const value = this.srcForm.get('src').value; 60 | try { 61 | const url = new URL(value); 62 | if (!url) { 63 | throw new Error('Bad Url'); 64 | } 65 | this.src = value; 66 | this.isAudioQueried = true; 67 | } catch (err) { 68 | this.isSrcError = true; 69 | } 70 | } 71 | 72 | onPlayButtonClick() { 73 | this.waveform.play(); 74 | this.play = true; 75 | } 76 | 77 | onPauseButtonClick() { 78 | this.waveform.pause(); 79 | } 80 | 81 | onTrackLoaded(time: number) { 82 | this.trackLoadTime = time; 83 | } 84 | 85 | onTrackRendered(time: number) { 86 | this.renderingTime = time; 87 | this.isLoaded = true; 88 | } 89 | 90 | onDurationChange(duration: number) { 91 | this.duration = duration; 92 | } 93 | 94 | onTimeUpdate(event: ITimeUpdateEvent) { 95 | this.currentTime = event; 96 | } 97 | 98 | onPaused() { 99 | this.play = false; 100 | } 101 | 102 | onRegionChange(region: IRegionPositions) { 103 | this.regionPositions = region; 104 | this.regionStartCtrl.setValue(this.regionPositions.start); 105 | this.regionEndCtrl.setValue(this.regionPositions.end); 106 | } 107 | 108 | downloadABTopStory() { 109 | this.isLoaded = false; 110 | this.isAudioQueried = false; 111 | this.play = false; 112 | this.regionStartCtrl.setValue(0); 113 | this.regionEndCtrl.setValue(0); 114 | const params = new HttpParams().set('appKey', '5e32a885cd5244d39e88e0f1480ee45b'); 115 | this.http.get<{bursts: any[]}>(`https://sapi.audioburst.com/v2/topstories`, {params}).pipe( 116 | map(resp => resp.bursts[0]), 117 | map(b => b.contentURLs.audioURL) 118 | ) 119 | .subscribe(src => { 120 | this.src = src; 121 | this.isAudioQueried = true; 122 | }); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agroupp/ng-waveform/93bab9112ee18049f38f150af3326d9cc362b3c5/projects/waveform-demo/src/assets/.gitkeep -------------------------------------------------------------------------------- /projects/waveform-demo/src/assets/og-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agroupp/ng-waveform/93bab9112ee18049f38f150af3326d9cc362b3c5/projects/waveform-demo/src/assets/og-img.jpg -------------------------------------------------------------------------------- /projects/waveform-demo/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /projects/waveform-demo/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/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agroupp/ng-waveform/93bab9112ee18049f38f150af3326d9cc362b3c5/projects/waveform-demo/src/favicon.ico -------------------------------------------------------------------------------- /projects/waveform-demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgWaveform Angular component by Arthur Groupp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /projects/waveform-demo/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().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /projects/waveform-demo/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 | -------------------------------------------------------------------------------- /projects/waveform-demo/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | body { 3 | margin: 0; 4 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /projects/waveform-demo/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 | -------------------------------------------------------------------------------- /projects/waveform-demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.ts" 13 | ], 14 | "exclude": [ 15 | "src/test.ts", 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/waveform-demo/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 | -------------------------------------------------------------------------------- /projects/waveform-demo/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 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ng-waveform": [ 23 | "dist/ng-waveform" 24 | ], 25 | "ng-waveform/*": [ 26 | "dist/ng-waveform/*" 27 | ] 28 | } 29 | }, 30 | "angularCompilerOptions": { 31 | "fullTemplateTypeCheck": true, 32 | "strictInjectionParameters": true 33 | } 34 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "array-type": false, 8 | "arrow-parens": false, 9 | "deprecation": { 10 | "severity": "warning" 11 | }, 12 | "import-blacklist": [ 13 | true, 14 | "rxjs/Rx" 15 | ], 16 | "interface-name": false, 17 | "max-classes-per-file": false, 18 | "max-line-length": [ 19 | true, 20 | 140 21 | ], 22 | "member-access": false, 23 | "member-ordering": [ 24 | true, 25 | { 26 | "order": [ 27 | "static-field", 28 | "instance-field", 29 | "static-method", 30 | "instance-method" 31 | ] 32 | } 33 | ], 34 | "no-consecutive-blank-lines": false, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-empty": false, 44 | "no-inferrable-types": [ 45 | true, 46 | "ignore-params" 47 | ], 48 | "no-non-null-assertion": true, 49 | "no-redundant-jsdoc": true, 50 | "no-switch-case-fall-through": true, 51 | "no-var-requires": false, 52 | "object-literal-key-quotes": [ 53 | true, 54 | "as-needed" 55 | ], 56 | "object-literal-sort-keys": false, 57 | "ordered-imports": false, 58 | "quotemark": [ 59 | true, 60 | "single" 61 | ], 62 | "trailing-comma": false, 63 | "component-class-suffix": true, 64 | "contextual-lifecycle": true, 65 | "directive-class-suffix": true, 66 | "no-conflicting-lifecycle": true, 67 | "no-host-metadata-property": true, 68 | "no-input-rename": true, 69 | "no-inputs-metadata-property": true, 70 | "no-output-native": true, 71 | "no-output-on-prefix": true, 72 | "no-output-rename": true, 73 | "no-outputs-metadata-property": true, 74 | "template-banana-in-box": true, 75 | "template-no-negated-async": true, 76 | "use-lifecycle-interface": true, 77 | "use-pipe-transform-interface": true 78 | } 79 | } 80 | --------------------------------------------------------------------------------