annotate src/app/services/audio-recorder/audio-recorder.service.ts @ 424:bb2bc6af642b

Update lock
author Lucas Thompson <dev@lucas.im>
date Tue, 06 Jun 2017 21:36:48 +0100
parents 53aaf72c2fd0
children 72d9303a1513
rev   line source
dev@132 1 /**
dev@132 2 * Created by lucas on 17/03/2017.
dev@132 3 */
dev@236 4 import {Injectable, Inject, NgZone} from '@angular/core';
dev@236 5 import {Observable} from 'rxjs/Observable';
dev@236 6 import {Subject} from 'rxjs/Subject';
dev@132 7
dev@132 8
dev@132 9 // seems the TypeScript definitions are not up to date,
dev@132 10 // introduce own types for now
dev@132 11
dev@293 12 export type AudioInputProvider = () => Promise<MediaStream>;
dev@132 13
dev@132 14 export interface MediaRecorderOptions {
dev@132 15 mimeType?: string;
dev@132 16 audioBitsPerSecond?: number;
dev@132 17 videoBitsPerSecond?: number;
dev@132 18 bitsPerSecond?: number;
dev@132 19 }
dev@132 20
dev@236 21 export type RecordingState = 'inactive' | 'recording' | 'paused';
dev@132 22
dev@132 23 export interface BlobEvent extends Event {
dev@132 24 readonly data: Blob;
dev@132 25 readonly timecode: number;
dev@132 26 }
dev@132 27
dev@132 28 export interface MediaRecorderErrorEvent extends Event {
dev@132 29 readonly error: DOMException;
dev@132 30 }
dev@132 31
dev@132 32 export interface MediaRecorder {
dev@132 33 readonly mimeType: string;
dev@132 34 readonly state: RecordingState;
dev@132 35 readonly stream: MediaStream;
dev@132 36 ignoreMutedMedia: boolean;
dev@132 37 readonly videoBitsPerSecond: number;
dev@132 38 readonly audioBitsPerSecond: number;
dev@132 39 // isTypeSupported(mimeType: string): boolean;
dev@132 40 onstart: (evt: Event) => void;
dev@132 41 onstop: (evt: Event) => void;
dev@132 42 ondataavailable: (evt: BlobEvent) => void;
dev@132 43 onpause: (evt: Event) => void;
dev@132 44 onresume: (evt: Event) => void;
dev@132 45 onerror: (evt: MediaRecorderErrorEvent) => void;
dev@236 46 pause(): void;
dev@236 47 requestData(): void;
dev@236 48 resume(): void;
dev@236 49 start(timeslice?: number): void;
dev@236 50 stop(): void;
dev@132 51 }
dev@132 52
dev@132 53 export interface MediaRecorderConstructor {
dev@132 54 new(stream: MediaStream,
dev@132 55 options?: MediaRecorderOptions): MediaRecorder;
dev@132 56 isTypeSupported(mimeType: string): boolean;
dev@132 57 }
dev@132 58
dev@236 59 export type RecorderServiceStatus = 'disabled' | 'enabled' | 'recording';
dev@132 60
dev@132 61 export class ThrowingMediaRecorder implements MediaRecorder {
dev@132 62 mimeType: string;
dev@132 63 state: RecordingState;
dev@132 64 stream: MediaStream;
dev@132 65 ignoreMutedMedia: boolean;
dev@132 66 videoBitsPerSecond: number;
dev@132 67 audioBitsPerSecond: number;
dev@132 68 onstart: (evt: Event) => void;
dev@132 69 onstop: (evt: Event) => void;
dev@132 70 ondataavailable: (evt: BlobEvent) => void;
dev@132 71 onpause: (evt: Event) => void;
dev@132 72 onresume: (evt: Event) => void;
dev@132 73 onerror: (evt: MediaRecorderErrorEvent) => void;
dev@132 74
dev@132 75 static isTypeSupported(mimeType: string): boolean {
dev@132 76 return false;
dev@132 77 }
dev@132 78
dev@236 79 constructor(stream: MediaStream,
dev@236 80 options?: MediaRecorderOptions) {
dev@236 81 throw new Error('MediaRecorder not available in this browser.');
dev@236 82 }
dev@236 83
dev@236 84
dev@132 85 pause(): void {
dev@132 86 }
dev@132 87
dev@132 88 requestData(): void {
dev@132 89 }
dev@132 90
dev@132 91 resume(): void {
dev@132 92 }
dev@132 93
dev@132 94 start(timeslice: number): void {
dev@132 95 }
dev@132 96
dev@132 97 stop(): void {
dev@132 98 }
dev@132 99 }
dev@132 100
dev@132 101 @Injectable()
dev@132 102 export class AudioRecorderService {
dev@132 103 private requestProvider: AudioInputProvider;
dev@132 104 private recorderImpl: MediaRecorderConstructor;
dev@293 105 private currentRecorder: MediaRecorder;
dev@132 106 private recordingStateChange: Subject<RecorderServiceStatus>;
dev@132 107 recordingStateChange$: Observable<RecorderServiceStatus>;
dev@132 108 private newRecording: Subject<Blob>;
dev@132 109 newRecording$: Observable<Blob>;
dev@132 110 private isRecording: boolean;
dev@132 111 private chunks: Blob[];
dev@132 112
dev@132 113 constructor(@Inject('AudioInputProvider') requestProvider: AudioInputProvider,
dev@132 114 @Inject(
dev@132 115 'MediaRecorderFactory'
dev@145 116 ) recorderImpl: MediaRecorderConstructor,
dev@145 117 private ngZone: NgZone) {
dev@132 118 this.requestProvider = requestProvider;
dev@132 119 this.recorderImpl = recorderImpl;
dev@132 120 this.recordingStateChange = new Subject<RecorderServiceStatus>();
dev@132 121 this.recordingStateChange$ = this.recordingStateChange.asObservable();
dev@132 122 this.newRecording = new Subject<Blob>();
dev@132 123 this.newRecording$ = this.newRecording.asObservable();
dev@132 124 this.isRecording = false;
dev@132 125 this.chunks = [];
dev@132 126 }
dev@132 127
dev@293 128 private getRecorderInstance(): Promise<MediaRecorder> {
dev@293 129 return this.requestProvider().then(stream => {
dev@293 130 const recorder = new this.recorderImpl(stream);
dev@293 131 recorder.ondataavailable = e => this.chunks.push(e.data);
dev@293 132 recorder.onstop = () => {
dev@293 133 const blob = new Blob(this.chunks, {'type': recorder.mimeType});
dev@293 134 this.chunks.length = 0;
dev@293 135 this.ngZone.run(() => {
dev@293 136 this.newRecording.next(
dev@293 137 blob
dev@293 138 );
dev@293 139 });
dev@293 140 };
dev@293 141 return recorder;
dev@132 142 });
dev@132 143 }
dev@132 144
dev@132 145 toggleRecording(): void {
dev@132 146 if (this.isRecording) {
dev@132 147 this.endRecording();
dev@132 148 } else {
dev@293 149 this.getRecorderInstance()
dev@293 150 .then(recorder => this.startRecording(recorder))
dev@293 151 .catch(e => {
dev@293 152 this.recordingStateChange.next('disabled'); // don't really need to do this
dev@293 153 console.warn(e); // TODO emit an error message for display?
dev@293 154 });
dev@132 155 }
dev@132 156 }
dev@132 157
dev@293 158 private startRecording(recorder: MediaRecorder): void {
dev@293 159 this.currentRecorder = recorder;
dev@293 160 this.isRecording = true;
dev@293 161 recorder.start();
dev@293 162 this.recordingStateChange.next('recording');
dev@132 163 }
dev@132 164
dev@132 165 private endRecording(): void {
dev@293 166 if (this.currentRecorder) {
dev@132 167 this.isRecording = false;
dev@293 168 this.currentRecorder.stop();
dev@293 169 for (const track of this.currentRecorder.stream.getAudioTracks()) {
dev@293 170 track.stop();
dev@293 171 }
dev@132 172 this.chunks.length = 0; // empty the array
dev@236 173 this.recordingStateChange.next('enabled');
dev@293 174 this.currentRecorder = null;
dev@132 175 }
dev@132 176 }
dev@132 177 }