annotate src/app/services/audio-player/audio-player.service.ts @ 509:041468f553e1 tip master

Merge pull request #57 from LucasThompson/fix/session-stack-max-call-stack Fix accidental recursion in PersistentStack
author Lucas Thompson <LucasThompson@users.noreply.github.com>
date Mon, 27 Nov 2017 11:04:30 +0000
parents c39df81c4dae
children
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@497 5 import {UrlResourceLifetimeManager} from '../File';
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 this.loadAudioFromUri('');
dev@457 105 }
dev@457 106
dev@37 107 togglePlaying(): void {
dev@57 108 if (this.audioElement.readyState >= 2) {
dev@57 109 this.isPlaying() ? this.audioElement.pause() : this.audioElement.play();
dev@57 110 this.playingStateChange.next(this.isPlaying());
dev@57 111 }
dev@37 112 }
dev@37 113
dev@37 114 setVolume(value: number): void {
dev@37 115 this.audioElement.volume = value; // TODO check bounds?
dev@37 116 }
dev@37 117
dev@82 118 seekTo(seconds: number): void {
dev@82 119 if (seconds < 0) {
dev@82 120 this.audioElement.currentTime = 0;
dev@82 121 } else if (seconds < this.getDuration()) {
dev@82 122 this.audioElement.currentTime = seconds;
dev@82 123 } else {
dev@82 124 this.audioElement.currentTime = this.getDuration();
dev@82 125 }
dev@82 126 }
dev@82 127
dev@37 128 seekBy(seconds: number): void {
dev@37 129 // TODO some kind of error handling?
dev@37 130 this.audioElement.currentTime += seconds;
dev@37 131 }
dev@37 132
dev@37 133 seekToStart(): void {
dev@37 134 this.audioElement.currentTime = 0;
dev@37 135 }
dev@37 136
dev@37 137 seekToEnd(): void {
dev@37 138 this.audioElement.currentTime = this.getDuration();
dev@37 139 }
dev@37 140
dev@37 141 getDuration(): number {
dev@153 142 return this.audioElement.duration || 0;
dev@37 143 }
dev@37 144 }