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 }
|