[A] input-manager selection in profile and recorder

This commit is contained in:
Jan 2020-10-30 18:52:27 +01:00
parent 6880d9c033
commit d64dce0680
9 changed files with 212 additions and 17 deletions

View file

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ChannelService {
constructor() { }
}

View file

@ -20,6 +20,7 @@ import { ProfileComponent } from './profile/profile.component';
import { ChannelComponent } from './channel/channel.component';
import { SidebarComponent } from './sidebar/sidebar.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { InputManagerComponent } from './input-manager/input-manager.component';
@NgModule({
declarations: [
@ -31,7 +32,8 @@ import { DashboardComponent } from './dashboard/dashboard.component';
ProfileComponent,
ChannelComponent,
SidebarComponent,
DashboardComponent
DashboardComponent,
InputManagerComponent
],
imports: [
BrowserModule,

View file

@ -0,0 +1,15 @@
<video id="localVideo" autoplay muted></video>
<div class="form-group">
<label for="audioSource">Audio input source: </label>
<select class="form-control" id="audioSource" (change)="start()"></select>
</div>
<!-- <div class="select">
<label for="audioOutput">Audio output destination: </label><select id="audioOutput" (change)="changeAudioDestination()"></select>
</div> -->
<div class="form-group">
<label for="videoSource">Video source: </label>
<select class="form-control" id="videoSource" (change)="start()"></select>
</div>

View file

@ -0,0 +1,4 @@
video {
max-width: 100%;
max-height: 100%;
}

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputManagerComponent } from './input-manager.component';
describe('InputManagerComponent', () => {
let component: InputManagerComponent;
let fixture: ComponentFixture<InputManagerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InputManagerComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InputManagerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,142 @@
import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-input-manager',
templateUrl: './input-manager.component.html',
styleUrls: ['./input-manager.component.scss']
})
export class InputManagerComponent implements AfterViewInit {
@Output() streamOutput: EventEmitter<MediaStream> = new EventEmitter();
stream: MediaStream;
videoElement:any = document.querySelector('#localVideo');
audioInputSelect:any = document.querySelector('select#audioSource');
// audioOutputSelect:any = document.querySelector('select#audioOutput');
videoSelect:any = document.querySelector('select#videoSource');
// selectors = [this.audioInputSelect, this.audioOutputSelect, this.videoSelect];
selectors = [this.audioInputSelect, this.videoSelect];
constructor(
) { }
ngAfterViewInit(): void {
this.videoElement = document.querySelector('#localVideo');
this.videoElement.muted = true;
this.audioInputSelect = document.querySelector('select#audioSource');
// this.audioOutputSelect = document.querySelector('select#audioOutput');
this.videoSelect = document.querySelector('select#videoSource');
// this.selectors = [this.audioInputSelect, this.audioOutputSelect, this.videoSelect];
this.selectors = [this.audioInputSelect, this.videoSelect];
// console.log(this.videoSelect, this.selectors)
// this.audioOutputSelect.disabled = !('sinkId' in HTMLMediaElement.prototype);
navigator.mediaDevices.enumerateDevices().then(devices => this.gotDevices(devices)).catch(error => this.handleError(error));
this.start();
}
// Attach audio output device to video element using device/sink ID.
attachSinkId(element, sinkId) {
if (typeof element.sinkId !== 'undefined') {
element.setSinkId(sinkId)
.then(() => {
// console.log(`Success, audio output device attached: ${sinkId}`);
})
.catch(error => {
let errorMessage = error;
if (error.name === 'SecurityError') {
errorMessage = `You need to use HTTPS for selecting audio output device: ${error}`;
}
// console.error(errorMessage);
// Jump back to first output device in the list as it's the default.
// this.audioOutputSelect.selectedIndex = 0;
});
} else {
// console.warn('Browser does not support output device selection.');
}
}
// changeAudioDestination() {
// const audioDestination = this.audioOutputSelect.value;
// this.attachSinkId(this.videoElement, audioDestination);
// }
gotStream(stream: MediaStream) {
this.stream = stream; // make stream available to // console
this.videoElement.srcObject = stream;
this.videoElement.muted = true;
this.streamOutput.next(stream);
// Refresh button list in case labels have become available
return navigator.mediaDevices.enumerateDevices();
}
handleError(error) {
console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
gotDevices(deviceInfos) {
// Handles being called several times to update labels. Preserve values.
const values = this.selectors.map(select => select.value);
this.selectors.forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `microphone ${this.audioInputSelect.length + 1}`;
this.audioInputSelect.appendChild(option);
// } else if (deviceInfo.kind === 'audiooutput') {
// option.text = deviceInfo.label || `speaker ${this.audioOutputSelect.length + 1}`;
// this.audioOutputSelect.appendChild(option);
} else if (deviceInfo.kind === 'videoinput') {
option.text = deviceInfo.label || `camera ${this.videoSelect.length + 1}`;
this.videoSelect.appendChild(option);
} else {
// console.log('Some other kind of source/device: ', deviceInfo);
}
}
this.selectors.forEach((select, selectorIndex) => {
if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
select.value = values[selectorIndex];
}
});
}
start() {
if (this.stream) {
this.stream.getTracks().forEach(track => {
track.stop();
});
}
let audioSource = this.audioInputSelect.value;
const savedAudioSource = localStorage.getItem('async-huddle-selected-mic');
if (audioSource) {
localStorage.setItem('async-huddle-selected-mic', audioSource);
} else if (savedAudioSource) {
audioSource = savedAudioSource;
setTimeout(() => {
const e:any = document.querySelector('[value="'+savedAudioSource+'"]')
if (e) e.selected = true;
}, 200);
}
console.warn('Selecting audio:', audioSource);
let videoSource = this.videoSelect.value;
const savedVideoSource = localStorage.getItem('async-huddle-selected-video-device');
if (videoSource) {
localStorage.setItem('async-huddle-selected-video-device', videoSource);
} else if (savedVideoSource) {
videoSource = savedVideoSource;
setTimeout(() => {
const e:any = document.querySelector('[value="'+savedVideoSource+'"]')
if (e) e.selected = true;
}, 200);
}
console.warn('Selecting video:', videoSource);
const constraints = {
audio: {deviceId: audioSource ? {exact: audioSource} : undefined},
video: {deviceId: videoSource ? {exact: videoSource} : undefined}
};
navigator.mediaDevices.getUserMedia(constraints).then((stream) => this.gotStream(stream)).then(devices => this.gotDevices(devices)).catch(error => this.handleError(error));
}
}

View file

@ -9,4 +9,8 @@
<button type="submit" class="btn btn-primary" (submit)="updateUser()" (click)="updateUser()">Save</button>
</form>
</div>
<div class="col-12 col-sm-6">
<legend>Media Settings</legend>
<app-input-manager></app-input-manager>
</div>
</div>

View file

@ -23,8 +23,8 @@
</span>
</div>
<div class="modal-body d-flex flex-column">
<video *ngIf="stream" [id]="id" [srcObject]="stream" class="video-recorder" autoplay muted volume="0"></video>
<button *ngIf="!isRecording" class="btn btn-success" (click)="startRecording()">
<app-input-manager (streamOutput)="handleStreamChange($event)"></app-input-manager>
<button *ngIf="!isRecording" class="btn btn-success mt-2" (click)="startRecording()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
@ -35,6 +35,6 @@
</svg>
Start recording
</button>
<button *ngIf="isRecording" class="btn btn-danger" (click)="stopRecording()">Stop</button>
<button *ngIf="isRecording" class="btn btn-danger mt-2" (click)="stopRecording()">Stop</button>
</div>
</ng-template>

View file

@ -27,23 +27,18 @@ export class RecorderComponent implements OnInit {
this.modal = this.modalService.open(this.content, {
centered: true
});
this.stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
setTimeout(() => {
const video:any = document.getElementById(this.id);
video.volume = 0;
video.muted = true;
}, 200);
}
startRecording() {
if (!this.stream) {
handleStreamChange(stream: MediaStream) {
this.stream = stream;
}
startRecording(stream: MediaStream = this.stream) {
if (!stream) {
return;
}
this.isRecording = true;
this.recorder = new rrtc.RecordRTCPromisesHandler(this.stream, {
this.recorder = new rrtc.RecordRTCPromisesHandler(stream, {
type: 'video',
mimeType: 'video/webm;codecs=vp8',
});
@ -57,7 +52,6 @@ export class RecorderComponent implements OnInit {
let blob = await this.recorder.getDataURL();
that.recordingEnded.emit(blob);
that.modal.close();
// rrtc.invokeSaveAsDialog(blob, 'Recorded-Video.webm');
return;
}