package media import ( "context" "errors" "os" "strings" "sync" "time" "gorm.io/gorm" ) var runtimeState struct { sync.RWMutex service *Service cancel context.CancelFunc } func StartRuntime(parent context.Context, db *gorm.DB) error { mode := strings.ToLower(strings.TrimSpace(os.Getenv("SENSE_MEDIAMTX_MODE"))) if mode == "disabled" { return nil } if db == nil { return errors.New("Sense database is unavailable for MediaMTX runtime") } config, err := ConfigFromEnvironment() if err != nil { return err } controller, err := NewHTTPController(config.APIBase) if err != nil { return err } service := NewService(db, controller, NewSupervisor(config.Binary, config.ConfigPath), config) ctx, cancel := context.WithCancel(parent) if mode == "managed" || mode == "external" { if err = service.ensureControl(ctx); err != nil { cancel() return err } } runtimeState.Lock() if runtimeState.cancel != nil { runtimeState.cancel() } runtimeState.service, runtimeState.cancel = service, cancel runtimeState.Unlock() go service.Run(ctx) return nil } func ShutdownRuntime(ctx context.Context) error { runtimeState.Lock() service, cancel := runtimeState.service, runtimeState.cancel runtimeState.service, runtimeState.cancel = nil, nil runtimeState.Unlock() if cancel != nil { cancel() } if service != nil && service.process != nil { return service.process.Stop(ctx) } return nil } func serviceFor(db *gorm.DB) *Service { runtimeState.RLock() service := runtimeState.service runtimeState.RUnlock() if service != nil { return service } return NewService(db, nil, nil, RuntimeConfig{PollInterval: 5 * time.Second}) } // EnsureDeviceRoutes is an internal post-admission port. It persists only // credential-free route intent and deliberately does not fail device probing. func EnsureDeviceRoutes(ctx context.Context, db *gorm.DB, deviceID string) error { return serviceFor(db).EnsureDevice(ctx, deviceID) }