diff --git a/src/app/api/supabase/channel.service.ts b/src/app/api/supabase/channel.service.ts
new file mode 100644
index 0000000..0d73e6c
--- /dev/null
+++ b/src/app/api/supabase/channel.service.ts
@@ -0,0 +1,9 @@
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ChannelService {
+
+ constructor() { }
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index c39149a..68d993d 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -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,
diff --git a/src/app/input-manager/input-manager.component.html b/src/app/input-manager/input-manager.component.html
new file mode 100644
index 0000000..57c27b3
--- /dev/null
+++ b/src/app/input-manager/input-manager.component.html
@@ -0,0 +1,15 @@
+
+
+
+ Audio input source:
+
+
+
+
+
+
+ Video source:
+
+
diff --git a/src/app/input-manager/input-manager.component.scss b/src/app/input-manager/input-manager.component.scss
new file mode 100644
index 0000000..441227b
--- /dev/null
+++ b/src/app/input-manager/input-manager.component.scss
@@ -0,0 +1,4 @@
+video {
+ max-width: 100%;
+ max-height: 100%;
+ }
\ No newline at end of file
diff --git a/src/app/input-manager/input-manager.component.spec.ts b/src/app/input-manager/input-manager.component.spec.ts
new file mode 100644
index 0000000..10087c0
--- /dev/null
+++ b/src/app/input-manager/input-manager.component.spec.ts
@@ -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;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ InputManagerComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(InputManagerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/input-manager/input-manager.component.ts b/src/app/input-manager/input-manager.component.ts
new file mode 100644
index 0000000..b9023f5
--- /dev/null
+++ b/src/app/input-manager/input-manager.component.ts
@@ -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 = 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));
+ }
+}
diff --git a/src/app/profile/profile.component.html b/src/app/profile/profile.component.html
index 69bab14..5e4e689 100644
--- a/src/app/profile/profile.component.html
+++ b/src/app/profile/profile.component.html
@@ -9,4 +9,8 @@
Save
+
\ No newline at end of file
diff --git a/src/app/recorder/recorder.component.html b/src/app/recorder/recorder.component.html
index 7177370..f4307dc 100644
--- a/src/app/recorder/recorder.component.html
+++ b/src/app/recorder/recorder.component.html
@@ -23,8 +23,8 @@
-
-
+
+
Start recording
- Stop
+ Stop
\ No newline at end of file
diff --git a/src/app/recorder/recorder.component.ts b/src/app/recorder/recorder.component.ts
index 63c8b59..3ff6b6d 100644
--- a/src/app/recorder/recorder.component.ts
+++ b/src/app/recorder/recorder.component.ts
@@ -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;
}