UNPKG

@osobh/reactar

Version:

AR Library for React Native and Expo

212 lines (191 loc) 6.69 kB
// 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; } }