annotate src/app/services/audio-player/audio-player.service.ts @ 458:f8d37ce6f475

Add method for loading audio from a uri.
author Lucas Thompson <dev@lucas.im>
date Fri, 30 Jun 2017 00:58:44 +0100
parents 906dd152e333
children 34db9d45663f
rev   line source
dev@37 1 import {Injectable, Inject} from '@angular/core';
dev@236 2 import {Subject} from 'rxjs/Subject';
dev@236 3 import {Observable} from 'rxjs/Observable';
dev@346 4 import {ReplaySubject} from 'rxjs/ReplaySubject';
dev@454 5 import {UrlResourceLifetimeManager} from '../../app.module';
dev@191 6
dev@192 7 export type ResourceReader = (resource: File | Blob) => Promise<ArrayBuffer>;
dev@193 8
dev@193 9 export interface AudioResource {
dev@193 10 samples: AudioBuffer;
dev@193 11 url: string;
dev@193 12 mimeType: string;
dev@193 13 }
dev@193 14
dev@193 15 export interface AudioResourceError {
dev@193 16 message: string;
dev@193 17 }
dev@193 18
dev@193 19 export type AudioLoadResponse = AudioResource | AudioResourceError;
dev@193 20
dev@37 21 @Injectable()
dev@37 22 export class AudioPlayerService {
dev@37 23
dev@52 24 private currentObjectUrl: string;
dev@52 25 private playingStateChange: Subject<boolean>;
dev@52 26 playingStateChange$: Observable<boolean>;
dev@52 27 private seeked: Subject<number>;
dev@52 28 seeked$: Observable<number>;
dev@193 29 private audioLoaded: Subject<AudioLoadResponse>;
dev@193 30 audioLoaded$: Observable<AudioLoadResponse>;
dev@52 31
dev@37 32 constructor(@Inject(HTMLAudioElement) private audioElement: HTMLAudioElement /* TODO probably shouldn't play audio this way */,
dev@191 33 @Inject('AudioContext') private audioContext: AudioContext,
dev@192 34 @Inject('ResourceReader') private readResource: ResourceReader,
dev@191 35 @Inject(
dev@191 36 'UrlResourceLifetimeManager'
dev@191 37 ) private resourceManager: UrlResourceLifetimeManager) {
dev@52 38 this.currentObjectUrl = '';
dev@346 39 this.playingStateChange = new ReplaySubject<boolean>(1);
dev@346 40 this.playingStateChange$ = this.playingStateChange
dev@346 41 .asObservable();
dev@346 42
dev@52 43 this.seeked = new Subject<number>();
dev@52 44 this.seeked$ = this.seeked.asObservable();
dev@52 45 this.audioElement.addEventListener('ended', () => {
dev@52 46 this.playingStateChange.next(this.isPlaying());
dev@52 47 });
dev@52 48 this.audioElement.addEventListener('seeked', () => {
dev@52 49 this.seeked.next(this.audioElement.currentTime);
dev@52 50 });
dev@193 51 this.audioLoaded = new Subject<AudioLoadResponse>();
dev@193 52 this.audioLoaded$ = this.audioLoaded.asObservable();
dev@37 53 }
dev@37 54
dev@37 55 getCurrentTime(): number {
dev@37 56 return this.audioElement.currentTime;
dev@37 57 }
dev@37 58
dev@37 59 isPlaying(): boolean {
dev@37 60 return !this.audioElement.paused;
dev@37 61 }
dev@37 62
dev@458 63 loadAudioFromUri(uri: string): void {
dev@458 64 this.currentObjectUrl = uri;
dev@458 65 this.audioElement.pause();
dev@458 66 this.audioElement.src = uri;
dev@458 67 this.audioElement.load();
dev@458 68 }
dev@37 69
dev@199 70 loadAudio(resource: File | Blob): string {
dev@193 71 const url: string = this.resourceManager.createUrlToResource(resource);
dev@458 72 this.loadAudioFromUri(url);
dev@193 73
dev@193 74 const decode: (buffer: ArrayBuffer) => Promise<AudioBuffer> = buffer => {
dev@207 75 try {
dev@207 76 return this.audioContext.decodeAudioData(buffer) as Promise<AudioBuffer>;
dev@207 77 } catch (e) {
dev@207 78 console.warn('Falling back to callback style decodeAudioData');
dev@207 79 return new Promise(
dev@207 80 (res, rej) => this.audioContext.decodeAudioData(buffer, res, rej)
dev@207 81 );
dev@207 82 }
dev@193 83 };
dev@193 84
dev@193 85 this.readResource(resource)
dev@193 86 .then(decode)
dev@193 87 .then(val => {
dev@193 88 this.audioLoaded.next({
dev@193 89 samples: val,
dev@193 90 url: url,
dev@193 91 mimeType: resource.type
dev@193 92 });
dev@193 93 })
dev@193 94 .catch(err => {
dev@236 95 const message = err && err.message ? err.message : 'Read error';
dev@193 96 this.audioLoaded.next({
dev@207 97 message: message
dev@193 98 });
dev@193 99 });
dev@199 100 return url;
dev@37 101 }
dev@37 102
dev@457 103 unload(): void {
dev@458 104 const previousUri = this.currentObjectUrl;
dev@458 105 this.loadAudioFromUri('');
dev@458 106 if (previousUri) {
dev@458 107 this.resourceManager.revokeUrlToResource(previousUri);
dev@457 108 }
dev@457 109 }
dev@457 110
dev@37 111 togglePlaying(): void {
dev@57 112 if (this.audioElement.readyState >= 2) {
dev@57 113 this.isPlaying() ? this.audioElement.pause() : this.audioElement.play();
dev@57 114 this.playingStateChange.next(this.isPlaying());
dev@57 115 }
dev@37 116 }
dev@37 117
dev@37 118 setVolume(value: number): void {
dev@37 119 this.audioElement.volume = value; // TODO check bounds?
dev@37 120 }
dev@37 121
dev@82 122 seekTo(seconds: number): void {
dev@82 123 if (seconds < 0) {
dev@82 124 this.audioElement.currentTime = 0;
dev@82 125 } else if (seconds < this.getDuration()) {
dev@82 126 this.audioElement.currentTime = seconds;
dev@82 127 } else {
dev@82 128 this.audioElement.currentTime = this.getDuration();
dev@82 129 }
dev@82 130 }
dev@82 131
dev@37 132 seekBy(seconds: number): void {
dev@37 133 // TODO some kind of error handling?
dev@37 134 this.audioElement.currentTime += seconds;
dev@37 135 }
dev@37 136
dev@37 137 seekToStart(): void {
dev@37 138 this.audioElement.currentTime = 0;
dev@37 139 }
dev@37 140
dev@37 141 seekToEnd(): void {
dev@37 142 this.audioElement.currentTime = this.getDuration();
dev@37 143 }
dev@37 144
dev@37 145 getDuration(): number {
dev@153 146 return this.audioElement.duration || 0;
dev@37 147 }
dev@37 148 }