annotate src/app/services/audio-player/audio-player.service.ts @ 191:ea735ebeed0e

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