Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VIH-10946 enable audio only toggle in the hearing room #2259

Merged
merged 10 commits into from
Oct 24, 2024
337 changes: 181 additions & 156 deletions VideoWeb/VideoWeb/ClientApp/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion VideoWeb/VideoWeb/ClientApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"ng-mocks": "^14.12.2",
"nswag": "^14.1.0",
"prettier": "^3.3.3",
"puppeteer": "^23.5.1",
"puppeteer": "^23.6.0",
"run-script-os": "^1.1.6",
"sass": "^1.77.8",
"typescript": ">=5.2.0 <5.4.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ import { VideoWebService } from 'src/app/services/api/video-web.service';
import { ConferenceResponse, Role, UserProfileResponse } from 'src/app/services/clients/api-client';
import { ErrorService } from 'src/app/services/error.service';
import { Logger } from 'src/app/services/logging/logger-base';
import { UserMediaStreamService } from 'src/app/services/user-media-stream.service';
import { pageUrls } from 'src/app/shared/page-url.constants';
import { ConferenceTestData } from 'src/app/testing/mocks/data/conference-test-data';
import { MockLogger } from 'src/app/testing/mocks/mock-logger';
import { SwitchOnCameraMicrophoneComponent } from './switch-on-camera-microphone.component';
import { fakeAsync, flush, flushMicrotasks } from '@angular/core/testing';
import { ParticipantStatusUpdateService } from 'src/app/services/participant-status-update.service';
import { Subject, throwError } from 'rxjs';
import { of, Subject, throwError } from 'rxjs';
import { mockCamStream } from 'src/app/waiting-space/waiting-room-shared/tests/waiting-room-base-setup';
import { getSpiedPropertyGetter } from 'src/app/shared/jasmine-helpers/property-helpers';
import { UserMediaService } from 'src/app/services/user-media.service';

describe('SwitchOnCameraMicrophoneComponent', () => {
Expand All @@ -26,7 +24,6 @@ describe('SwitchOnCameraMicrophoneComponent', () => {
let profileService: jasmine.SpyObj<ProfileService>;
let activatedRoute: ActivatedRoute = <any>{ snapshot: { paramMap: convertToParamMap({ conferenceId: conference.id }) } };
let videoWebService: jasmine.SpyObj<VideoWebService>;
let userMediaStreamService: jasmine.SpyObj<UserMediaStreamService>;
let errorService: jasmine.SpyObj<ErrorService>;
let userMediaServiceSpy: jasmine.SpyObj<UserMediaService>;
const logger: Logger = new MockLogger();
Expand All @@ -35,7 +32,7 @@ describe('SwitchOnCameraMicrophoneComponent', () => {
beforeEach(async () => {
conference = new ConferenceTestData().getConferenceDetailFuture();
activatedRoute = <any>{ snapshot: { paramMap: convertToParamMap({ conferenceId: conference.id }) } };
userMediaStreamService = jasmine.createSpyObj<UserMediaStreamService>('UserMediaStreamService', [], ['currentStream$']);

currentStreamSubject = new Subject<MediaStream>();

videoWebService = jasmine.createSpyObj<VideoWebService>('VideoWebService', [
Expand All @@ -60,7 +57,7 @@ describe('SwitchOnCameraMicrophoneComponent', () => {

participantStatusUpdateService = jasmine.createSpyObj('ParticipantStatusUpdateService', ['postParticipantStatus']);

userMediaServiceSpy = jasmine.createSpyObj<UserMediaService>(['initialise']);
userMediaServiceSpy = jasmine.createSpyObj<UserMediaService>(['hasValidCameraAndMicAvailable']);

component = new SwitchOnCameraMicrophoneComponent(
router,
Expand All @@ -70,8 +67,7 @@ describe('SwitchOnCameraMicrophoneComponent', () => {
errorService,
logger,
participantStatusUpdateService,
userMediaServiceSpy,
userMediaStreamService
userMediaServiceSpy
);
component.conference = conference;
component.conferenceId = conference.id;
Expand All @@ -97,8 +93,7 @@ describe('SwitchOnCameraMicrophoneComponent', () => {
errorService,
logger,
participantStatusUpdateService,
userMediaServiceSpy,
userMediaStreamService
userMediaServiceSpy
);

component.ngOnInit();
Expand Down Expand Up @@ -163,18 +158,17 @@ describe('SwitchOnCameraMicrophoneComponent', () => {
});

it('should update mediaAccepted and userPrompted to true when request media', fakeAsync(() => {
getSpiedPropertyGetter(userMediaStreamService, 'currentStream$').and.returnValue(currentStreamSubject.asObservable());
userMediaServiceSpy.hasValidCameraAndMicAvailable.and.returnValue(of(true));

component.requestMedia();
currentStreamSubject.next(mockCamStream);
flush();
expect(component.userPrompted).toBeTrue();
expect(component.mediaAccepted).toBeTrue();
expect(userMediaServiceSpy.initialise).toHaveBeenCalledTimes(1);
}));

it('should update mediaAccepted and userPrompted to false when request media throw an error', fakeAsync(() => {
getSpiedPropertyGetter(userMediaStreamService, 'currentStream$').and.callFake(() => throwError(new Error('Fake error')));
userMediaServiceSpy.hasValidCameraAndMicAvailable.and.returnValue(throwError(new Error('Fake error')));
spyOn(component, 'postPermissionDeniedAlert');

component.requestMedia();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { vhContactDetails } from 'src/app/shared/contact-information';
import { pageUrls } from 'src/app/shared/page-url.constants';
import { ParticipantStatusBaseDirective } from 'src/app/on-the-day/models/participant-status-base';
import { ParticipantStatusUpdateService } from 'src/app/services/participant-status-update.service';
import { UserMediaStreamService } from 'src/app/services/user-media-stream.service';
import { first } from 'rxjs/operators';
import { UserMediaService } from 'src/app/services/user-media.service';
import { HearingRole } from 'src/app/waiting-space/models/hearing-role-model';
import { catchError } from 'rxjs/operators';
import { NEVER } from 'rxjs';

@Component({
selector: 'app-switch-on-camera-microphone',
Expand Down Expand Up @@ -42,8 +42,7 @@ export class SwitchOnCameraMicrophoneComponent extends ParticipantStatusBaseDire
private errorService: ErrorService,
protected logger: Logger,
protected participantStatusUpdateService: ParticipantStatusUpdateService,
private userMediaService: UserMediaService,
private userMediaStreamService: UserMediaStreamService
private userMediaService: UserMediaService
) {
super(participantStatusUpdateService, logger);
this.userPrompted = false;
Expand Down Expand Up @@ -79,29 +78,34 @@ export class SwitchOnCameraMicrophoneComponent extends ParticipantStatusBaseDire
}
}

async requestMedia() {
this.userMediaService.initialise();
this.userMediaStreamService.currentStream$.pipe(first()).subscribe({
next: stream => {
this.mediaAccepted = true;
this.userPrompted = true;
},
error: error => {
this.mediaAccepted = false;
this.userPrompted = false;
this.logger.warn(`[SwitchOnCameraMicrophone] - ${this.participantName} denied access to camera.`, {
conference: this.conferenceId,
participant: this.participantName,
error: error
});
this.postPermissionDeniedAlert();
this.errorService.goToServiceError(
'error-camera-microphone.problem-with-camera-mic',
'error-camera-microphone.camera-mic-in-use',
false
);
}
});
requestMedia() {
this.userMediaService
.hasValidCameraAndMicAvailable()
.pipe(
catchError(error => {
this.mediaAccepted = false;
this.userPrompted = false;
this.logger.warn(`[SwitchOnCameraMicrophone] - ${this.participantName} denied access to camera.`, {
conference: this.conferenceId,
participant: this.participantName,
error: error
});
this.postPermissionDeniedAlert();
this.errorService.goToServiceError(
'error-camera-microphone.problem-with-camera-mic',
'error-camera-microphone.camera-mic-in-use',
false
);
return NEVER;
})
)
.subscribe(hasDevices => {
if (hasDevices) {
this.handleDevicesAvailable();
} else {
this.handleDevicesUnavailable();
}
});
}

goVideoTest() {
Expand Down Expand Up @@ -141,4 +145,19 @@ export class SwitchOnCameraMicrophoneComponent extends ParticipantStatusBaseDire
}
return this.conference.participants.some(x => x.user_name === this.profile.username && x.hearing_role === HearingRole.OBSERVER);
}

private handleDevicesAvailable() {
this.mediaAccepted = true;
this.userPrompted = true;
}

private handleDevicesUnavailable() {
this.mediaAccepted = false;
this.userPrompted = true;
this.errorService.goToServiceError(
'error-camera-microphone.problem-with-camera-mic',
'error-camera-microphone.camera-mic-in-use',
false
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { Subject } from 'rxjs';
import { getSpiedPropertyGetter } from '../shared/jasmine-helpers/property-helpers';
import { Logger } from './logging/logger-base';
import { NoSleepService } from './no-sleep.service';
import { UserMediaStreamService } from './user-media-stream.service';
import { UserMediaStreamServiceV2 } from './user-media-stream-v2.service';

describe('NoSleepService', () => {
let service: NoSleepService;

let userMediaStreamServiceSpy: jasmine.SpyObj<UserMediaStreamService>;
let userMediaStreamServiceSpy: jasmine.SpyObj<UserMediaStreamServiceV2>;
let renderer2FactorySpy: jasmine.SpyObj<RendererFactory2>;
let renderer2Spy: jasmine.SpyObj<Renderer2>;
let deviceServiceSpy: jasmine.SpyObj<DeviceDetectorService>;
Expand All @@ -24,7 +24,7 @@ describe('NoSleepService', () => {

beforeEach(() => {
currentStreamSubject = new Subject<MediaStream>();
userMediaStreamServiceSpy = jasmine.createSpyObj<UserMediaStreamService>([], ['currentStream$']);
userMediaStreamServiceSpy = jasmine.createSpyObj<UserMediaStreamServiceV2>([], ['currentStream$']);
getSpiedPropertyGetter(userMediaStreamServiceSpy, 'currentStream$').and.returnValue(currentStreamSubject.asObservable());

videoElementSpy = jasmine.createSpyObj<HTMLVideoElement>(['play', 'setAttribute'], ['style', 'parentElement']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Observable, Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import { Logger } from './logging/logger-base';
import { UserMediaStreamService } from './user-media-stream.service';
import { UserMediaStreamServiceV2 } from './user-media-stream-v2.service';

@Injectable({
providedIn: 'root'
Expand All @@ -16,7 +16,7 @@ export class NoSleepService {
private touchStartSubject = new Subject<void>();

constructor(
private userMediaStreamService: UserMediaStreamService,
private userMediaStreamService: UserMediaStreamServiceV2,
renderer2Factory: RendererFactory2,
private deviceService: DeviceDetectorService,
private document: Document,
Expand Down
Loading
Loading