annotate src/app/services/audio-player/audio-player.service.ts @ 192:e4f38975c2bc

Introduce interface for reading a file, once again avoiding specific browser implementation.
author Lucas Thompson <dev@lucas.im>
date Thu, 23 Mar 2017 15:42:34 +0000
parents ea735ebeed0e
children ac57ddba8ba9
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@37 11 @Injectable()
dev@37 12 export class AudioPlayerService {
dev@37 13
dev@52 14 private currentObjectUrl: string;
dev@52 15 private playingStateChange: Subject<boolean>;
dev@52 16 playingStateChange$: Observable<boolean>;
dev@52 17 private seeked: Subject<number>;
dev@52 18 seeked$: Observable<number>;
dev@52 19
dev@37 20 constructor(@Inject(HTMLAudioElement) private audioElement: HTMLAudioElement /* TODO probably shouldn't play audio this way */,
dev@191 21 @Inject('AudioContext') private audioContext: AudioContext,
dev@192 22 @Inject('ResourceReader') private readResource: ResourceReader,
dev@191 23 @Inject(
dev@191 24 'UrlResourceLifetimeManager'
dev@191 25 ) private resourceManager: UrlResourceLifetimeManager) {
dev@52 26 this.currentObjectUrl = '';
dev@52 27 this.playingStateChange = new Subject<boolean>();
dev@52 28 this.playingStateChange$ = this.playingStateChange.asObservable();
dev@52 29 this.seeked = new Subject<number>();
dev@52 30 this.seeked$ = this.seeked.asObservable();
dev@52 31 this.audioElement.addEventListener('ended', () => {
dev@52 32 this.playingStateChange.next(this.isPlaying());
dev@52 33 });
dev@52 34 this.audioElement.addEventListener('seeked', () => {
dev@52 35 this.seeked.next(this.audioElement.currentTime);
dev@52 36 });
dev@37 37 }
dev@37 38
dev@37 39 getCurrentTime(): number {
dev@37 40 return this.audioElement.currentTime;
dev@37 41 }
dev@37 42
dev@37 43 isPlaying(): boolean {
dev@37 44 return !this.audioElement.paused;
dev@37 45 }
dev@37 46
dev@37 47 decodeAudioData(buffer: ArrayBuffer): Promise<AudioBuffer> {
dev@37 48 return new Promise((res, rej) => this.audioContext.decodeAudioData(buffer, res, rej));
dev@37 49 }
dev@37 50
dev@37 51 loadAudioFromUrl(url: string): void {
dev@52 52 if (this.currentObjectUrl)
dev@191 53 this.resourceManager.revokeUrlToResource(this.currentObjectUrl);
dev@52 54 this.currentObjectUrl = url;
dev@37 55 this.audioElement.pause();
dev@37 56 this.audioElement.src = url;
dev@82 57 this.audioElement.load();
dev@37 58 }
dev@37 59
dev@37 60 togglePlaying(): void {
dev@57 61 if (this.audioElement.readyState >= 2) {
dev@57 62 this.isPlaying() ? this.audioElement.pause() : this.audioElement.play();
dev@57 63 this.playingStateChange.next(this.isPlaying());
dev@57 64 }
dev@37 65 }
dev@37 66
dev@37 67 setVolume(value: number): void {
dev@37 68 this.audioElement.volume = value; // TODO check bounds?
dev@37 69 }
dev@37 70
dev@82 71 seekTo(seconds: number): void {
dev@82 72 if (seconds < 0) {
dev@82 73 this.audioElement.currentTime = 0;
dev@82 74 } else if (seconds < this.getDuration()) {
dev@82 75 this.audioElement.currentTime = seconds;
dev@82 76 } else {
dev@82 77 this.audioElement.currentTime = this.getDuration();
dev@82 78 }
dev@82 79 }
dev@82 80
dev@37 81 seekBy(seconds: number): void {
dev@37 82 // TODO some kind of error handling?
dev@37 83 this.audioElement.currentTime += seconds;
dev@37 84 }
dev@37 85
dev@37 86 seekToStart(): void {
dev@37 87 this.audioElement.currentTime = 0;
dev@37 88 }
dev@37 89
dev@37 90 seekToEnd(): void {
dev@37 91 this.audioElement.currentTime = this.getDuration();
dev@37 92 }
dev@37 93
dev@37 94 getDuration(): number {
dev@153 95 return this.audioElement.duration || 0;
dev@37 96 }
dev@37 97 }