331 lines
10 KiB
TypeScript

import { HttpClient } from '@angular/common/http';
import { Component, HostListener, OnDestroy } from '@angular/core';
import { AndroidPermissions } from '@ionic-native/android-permissions/ngx';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { AlertController, Platform } from '@ionic/angular';
import {
Device,
OpenVidu,
Publisher,
PublisherProperties,
Session,
StreamEvent,
StreamManager,
Subscriber
} from 'openvidu-browser';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css']
})
export class AppComponent implements OnDestroy {
APPLICATION_SERVER_URL = location.protocol + '//' + location.hostname + ':5000/';
ANDROID_PERMISSIONS = [
this.androidPermissions.PERMISSION.CAMERA,
this.androidPermissions.PERMISSION.RECORD_AUDIO,
this.androidPermissions.PERMISSION.MODIFY_AUDIO_SETTINGS
];
// OpenVidu objects
OV: OpenVidu;
session: Session;
publisher: StreamManager; // Local
subscribers: StreamManager[] = []; // Remotes
// Join form
mySessionId: string;
myUserName: string;
cameraIcon = 'videocam';
microphoneIcon = 'mic';
private devices: Device[];
private cameras: Device[];
private microphones: Device[];
private cameraSelected: Device;
private microphoneSelected: Device;
private isFrontCamera: boolean = false;
constructor(
private httpClient: HttpClient,
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
private androidPermissions: AndroidPermissions,
private alertController: AlertController
) {
this.initializeApp();
this.generateParticipantInfo();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.overlaysWebView(false);
this.splashScreen.hide();
});
}
@HostListener('window:beforeunload')
beforeunloadHandler() {
// On window closed leave session
this.leaveSession();
}
ngOnDestroy() {
// On component destroyed leave session
this.leaveSession();
}
async joinSession() {
// --- 1) Get an OpenVidu object ---
this.OV = new OpenVidu();
// --- 2) Init a session ---
this.session = this.OV.initSession();
// --- 3) Specify the actions when events take place in the session ---
// On every new Stream received...
this.session.on('streamCreated', (event: StreamEvent) => {
// Subscribe to the Stream to receive it. Second parameter is undefined
// so OpenVidu doesn't create an HTML video on its own
const subscriber: Subscriber = this.session.subscribe(event.stream, undefined);
this.subscribers.push(subscriber);
});
// On every Stream destroyed...
this.session.on('streamDestroyed', (event: StreamEvent) => {
// Remove the stream from 'subscribers' array
this.deleteSubscriber(event.stream.streamManager);
});
// On every asynchronous exception...
this.session.on('exception', (exception) => {
console.warn(exception);
});
// --- 4) Connect to the session with a valid user token ---
try {
// Get a token from the OpenVidu deployment
const token = await this.getToken();
// First param is the token got from OpenVidu deployment. Second param will be used by every user on event
// 'streamCreated' (property Stream.connection.data), and will be appended to DOM as the user's nickname
await this.session.connect(token, { clientData: this.myUserName });
// --- 5) Requesting and Checking Android Permissions
if (this.platform.is('cordova')) {
// Ionic platform
if (this.platform.is('android')) {
console.log('Android platform');
await this.checkAndroidPermissions();
this.initPublisher();
} else if (this.platform.is('ios')) {
console.log('iOS platform');
this.initPublisher();
}
} else {
this.initPublisher();
}
} catch (error) {
console.log('There was an error connecting to the session:', error.code, error.message);
}
}
async initPublisher() {
// Init a publisher passing undefined as targetElement (we don't want OpenVidu to insert a video
// element: we will manage it on our own) and with the desired properties
const publisher: Publisher = await this.OV.initPublisherAsync(undefined, {
audioSource: undefined, // The source of audio. If undefined default microphone
videoSource: undefined, // The source of video. If undefined default webcam
publishAudio: true, // Whether you want to start publishing with your audio unmuted or not
publishVideo: true, // Whether you want to start publishing with your video enabled or not
resolution: '640x480', // The resolution of your video
frameRate: 30, // The frame rate of your video
insertMode: 'APPEND', // How the video is inserted in the target element 'video-container'
mirror: this.isFrontCamera // Whether to mirror your local video or not
});
publisher.on('accessAllowed', () => this.initDevices());
// --- 6) Publish your stream ---
await this.session.publish(publisher);
// Store our Publisher
this.publisher = publisher;
}
leaveSession() {
// --- 7) Leave the session by calling 'disconnect' method over the Session object ---
if (this.session) {
this.session.disconnect();
}
// Empty all properties...
this.subscribers = [];
delete this.publisher;
delete this.session;
delete this.OV;
this.generateParticipantInfo();
}
async swapCamera() {
try {
const newCamera = this.cameras.find(cam => cam.deviceId !== this.cameraSelected.deviceId);
if (!!newCamera) {
this.isFrontCamera = !this.isFrontCamera;
const pp: PublisherProperties = {
videoSource: newCamera.deviceId,
audioSource: false,
mirror: this.isFrontCamera
};
// Stopping the video tracks before request for another MediaStream
// Only one unique device can be used at same time
this.publisher.stream.getMediaStream().getVideoTracks()[0].stop();
const newTrack = await this.OV.getUserMedia(pp);
const videoTrack: MediaStreamTrack = newTrack.getVideoTracks()[0];
await (this.publisher as Publisher).replaceTrack(videoTrack);
this.cameraSelected = newCamera;
}
} catch (error) {
console.error(error);
}
}
toggleCamera() {
const publish = !this.publisher.stream.videoActive;
(this.publisher as Publisher).publishVideo(publish, true);
this.cameraIcon = publish ? 'videocam' : 'eye-off';
}
toggleMicrophone() {
const publish = !this.publisher.stream.audioActive;
(this.publisher as Publisher).publishAudio(publish);
this.microphoneIcon = publish ? 'mic' : 'mic-off';
}
private async initDevices() {
this.devices = await this.OV.getDevices();
this.cameras = this.devices.filter(d => d.kind === 'videoinput');
this.microphones = this.devices.filter(d => d.kind === 'audioinput' && d.label !== 'Default');
this.cameraSelected = this.cameras[0];
this.microphoneSelected = this.microphones[0];
}
private async checkAndroidPermissions(): Promise<void> {
await this.platform.ready();
try {
await this.androidPermissions.requestPermissions(this.ANDROID_PERMISSIONS);
const camera = await this.androidPermissions.checkPermission(this.androidPermissions.PERMISSION.CAMERA);
const audio = await this.androidPermissions.checkPermission(this.androidPermissions.PERMISSION.RECORD_AUDIO);
const modifyAudio = await this.androidPermissions.checkPermission(this.androidPermissions.PERMISSION.MODIFY_AUDIO_SETTINGS);
if (!camera.hasPermission || !audio.hasPermission || !modifyAudio.hasPermission) {
throw new Error('Permissions denied: \n CAMERA = ' + camera.hasPermission +
'\n AUDIO = ' + audio.hasPermission +
'\n AUDIO_SETTINGS = ' + modifyAudio.hasPermission
);
}
} catch (error) {
console.error('Error requesting or checking permissions: ', error);
throw (error);
}
}
private generateParticipantInfo() {
// Random user nickname and sessionId
this.mySessionId = 'SessionA';
this.myUserName = 'Participant' + Math.floor(Math.random() * 100);
}
private deleteSubscriber(streamManager: StreamManager): void {
const index = this.subscribers.indexOf(streamManager, 0);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
async presentSettingsAlert() {
const alert = await this.alertController.create({
header: 'OpenVidu deployment',
inputs: [
{
name: 'url',
type: 'text',
value: 'https://demos.openvidu.io/',
placeholder: 'URL',
}
],
buttons: [
{
text: 'Cancel',
role: 'cancel',
cssClass: 'secondary',
},
{
text: 'Ok',
handler: (data) => {
this.APPLICATION_SERVER_URL = data.url;
},
},
],
});
await alert.present();
}
/**
* --------------------------------------------
* GETTING A TOKEN FROM YOUR APPLICATION SERVER
* --------------------------------------------
* The methods below request the creation of a Session and a Token to
* your application server. This keeps your OpenVidu deployment secure.
*
* In this sample code, there is no user control at all. Anybody could
* access your application server endpoints! In a real production
* environment, your application server must identify the user to allow
* access to the endpoints.
*
* Visit https://docs.openvidu.io/en/stable/application-server to learn
* more about the integration of OpenVidu in your application server.
*/
async getToken(): Promise<string> {
if (
this.platform.is('ios') &&
this.platform.is('cordova') &&
this.APPLICATION_SERVER_URL === 'http://localhost:5000/'
) {
// To make easier first steps with iOS apps, use demos OpenVidu deployment when no custom deployment is configured
this.APPLICATION_SERVER_URL = 'https://demos.openvidu.io/';
}
const sessionId = await this.createSession(this.mySessionId);
return await this.createToken(sessionId);
}
createSession(sessionId) {
return this.httpClient.post(
this.APPLICATION_SERVER_URL + 'api/sessions',
{ customSessionId: sessionId },
{ headers: { 'Content-Type': 'application/json' }, responseType: 'text' }
).toPromise();
}
createToken(sessionId) {
return this.httpClient.post(
this.APPLICATION_SERVER_URL + 'api/sessions/' + sessionId + '/connections',
{},
{ headers: { 'Content-Type': 'application/json' }, responseType: 'text' }
).toPromise();
}
}