@osobh/reactar
Version:
AR Library for React Native and Expo
212 lines (191 loc) • 6.69 kB
text/typescript
// src/core/ARSession.ts
import { Camera } from 'expo-camera';
import { EventEmitter } from 'events';
import { CloudAnchorManager, CloudAnchor } from './CloudAnchorManager';
import { UserSessionManager, ARUser } from './UserSessionManager';
export interface ARSessionOptions {
cameraType?: 'front' | 'back';
enablePlaneDetection?: boolean;
enableCloudAnchors?: boolean;
enableMultiUser?: boolean;
cloudAnchorOptions?: {
serverUrl?: string;
resolveTimeoutSeconds?: number;
};
userSessionOptions?: {
serverUrl?: string;
heartbeatInterval?: number;
inactivityTimeout?: number;
};
}
export enum ARSessionState {
UNINITIALIZED = 'UNINITIALIZED',
INITIALIZED = 'INITIALIZED',
RUNNING = 'RUNNING',
PAUSED = 'PAUSED',
STOPPED = 'STOPPED',
}
export class ARSession extends EventEmitter {
private state: ARSessionState = ARSessionState.UNINITIALIZED;
private options: ARSessionOptions;
private cloudAnchorManager: CloudAnchorManager | null = null;
private userSessionManager: UserSessionManager | null = null;
constructor(options: ARSessionOptions = {}) {
super();
this.options = options;
}
/**
* Initializes the AR session:
* - Requests camera permissions.
* - Prepares configuration based on options.
*/
public async initialize(): Promise<void> {
try {
const { status } = await Camera.requestCameraPermissionsAsync();
if (status !== 'granted') {
throw new Error('Camera permission not granted');
}
if (this.options.enableCloudAnchors) {
this.cloudAnchorManager = new CloudAnchorManager(this.options.cloudAnchorOptions);
this.cloudAnchorManager.on('error', (error) => this.emit('error', error));
this.cloudAnchorManager.on('anchorHosted', (anchor) => this.emit('cloudAnchorHosted', anchor));
this.cloudAnchorManager.on('anchorResolved', (anchor) => this.emit('cloudAnchorResolved', anchor));
this.cloudAnchorManager.on('anchorUpdated', (anchor) => this.emit('cloudAnchorUpdated', anchor));
this.cloudAnchorManager.on('anchorRemoved', (cloudId) => this.emit('cloudAnchorRemoved', cloudId));
}
if (this.options.enableMultiUser) {
this.userSessionManager = new UserSessionManager(this.options.userSessionOptions);
this.userSessionManager.on('error', (error) => this.emit('error', error));
this.userSessionManager.on('userJoined', (user) => this.emit('userJoined', user));
this.userSessionManager.on('userLeft', (user) => this.emit('userLeft', user));
this.userSessionManager.on('userUpdated', (user) => this.emit('userUpdated', user));
await this.userSessionManager.initializeSession(this.cloudAnchorManager?.getDeviceId() || 'unknown');
}
this.state = ARSessionState.INITIALIZED;
this.emit('initialized');
console.log('ARSession initialized with options:', this.options);
} catch (error) {
this.emit('error', error);
console.error('Error initializing ARSession:', error);
throw error;
}
}
/**
* Starts the AR session if it is properly initialized.
*/
public start(): void {
if (this.state !== ARSessionState.INITIALIZED && this.state !== ARSessionState.PAUSED) {
console.warn('ARSession must be initialized or paused to start.');
return;
}
this.state = ARSessionState.RUNNING;
// Start camera feed, rendering, and sensor subscriptions here.
this.emit('started');
console.log('ARSession started.');
}
/**
* Pauses the AR session, suspending rendering and sensor updates.
*/
public pause(): void {
if (this.state !== ARSessionState.RUNNING) {
console.warn('ARSession is not running. Cannot pause.');
return;
}
this.state = ARSessionState.PAUSED;
// Pause camera feed, rendering loop, and sensor updates.
this.emit('paused');
console.log('ARSession paused.');
}
/**
* Resumes the AR session if it is paused.
*/
public resume(): void {
if (this.state !== ARSessionState.PAUSED) {
console.warn('ARSession is not paused. Cannot resume.');
return;
}
this.state = ARSessionState.RUNNING;
// Resume camera feed, rendering loop, and sensor updates.
this.emit('resumed');
console.log('ARSession resumed.');
}
/**
* Hosts a local anchor to the cloud
*/
public async hostCloudAnchor(anchor: any): Promise<CloudAnchor> {
if (!this.cloudAnchorManager) {
throw new Error('Cloud anchors are not enabled');
}
return this.cloudAnchorManager.hostAnchor(anchor);
}
/**
* Resolves a cloud anchor by its ID
*/
public async resolveCloudAnchor(cloudId: string): Promise<CloudAnchor> {
if (!this.cloudAnchorManager) {
throw new Error('Cloud anchors are not enabled');
}
return this.cloudAnchorManager.resolveAnchor(cloudId);
}
/**
* Updates a cloud anchor's position
*/
public async updateCloudAnchor(cloudId: string, newPosition: { x: number; y: number; z: number }): Promise<CloudAnchor> {
if (!this.cloudAnchorManager) {
throw new Error('Cloud anchors are not enabled');
}
return this.cloudAnchorManager.updateAnchor(cloudId, newPosition);
}
/**
* Removes a cloud anchor
*/
public async removeCloudAnchor(cloudId: string): Promise<void> {
if (!this.cloudAnchorManager) {
throw new Error('Cloud anchors are not enabled');
}
return this.cloudAnchorManager.removeAnchor(cloudId);
}
/**
* Gets all hosted cloud anchors
*/
public getHostedCloudAnchors(): CloudAnchor[] {
if (!this.cloudAnchorManager) {
return [];
}
return this.cloudAnchorManager.getHostedAnchors();
}
/**
* Gets all resolved cloud anchors
*/
public getResolvedCloudAnchors(): CloudAnchor[] {
if (!this.cloudAnchorManager) {
return [];
}
return this.cloudAnchorManager.getResolvedAnchors();
}
/**
* Stops the AR session and cleans up resources.
*/
public stop(): void {
if (this.state === ARSessionState.STOPPED || this.state === ARSessionState.UNINITIALIZED) {
console.warn('ARSession is not running.');
return;
}
// Clean up managers
if (this.userSessionManager) {
this.userSessionManager.endSession();
this.userSessionManager = null;
}
this.cloudAnchorManager = null;
this.state = ARSessionState.STOPPED;
// Clean up resources: stop camera, remove sensor listeners, etc.
this.emit('stopped');
console.log('ARSession stopped.');
}
/**
* Returns the current state of the AR session.
*/
public getState(): ARSessionState {
return this.state;
}
}