annotate src/app/services/audio-player/audio-player.service.ts @ 199:3ef1aaa2ebed

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