Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/components/autozoomControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ export default class AutozoomControl implements IAutozoomControl {
throw Error(`Option 'shouldAutoFrame' cannot not be set to ${options.shouldAutoFrame}`);
}

if (!options.shouldAutoFrame && options.mode !== AutozoomModes.NORMAL) {
if (
!options.shouldAutoFrame &&
![AutozoomModes.NORMAL, AutozoomModes.PLAZA].includes(options.mode)
) {
const modeName = options.mode;
throw Error(
`AutozoomMode '${modeName}' does not support option 'shouldAutoFrame' set to ${options.shouldAutoFrame}!`
Expand Down
75 changes: 62 additions & 13 deletions src/components/detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import CameraEvents from './../utilitis/events';
import semver from 'semver';
import TypeHelper from './../utilitis/typehelper';
import DetectionsConverter from './../utilitis/detectionsConverter';
import FramingModes from '@huddly/sdk-interfaces/lib/enums/FramingModes';
import MsgBusSubscriber from './msgBusSubscriber';
import { MsgBusSubscriberOptions } from '@huddly/sdk-interfaces/lib/interfaces/IMsgBusSubscriber';

const PREVIEW_IMAGE_SIZE = { width: 640, height: 480 };
const LATEST_WITHOUT_PEOPLE_COUNT = '1.3.14';
Expand Down Expand Up @@ -46,13 +49,30 @@ export default class Detector extends EventEmitter implements IDetector {
_previewStreamStarted: boolean = false;
/** @ignore */
_usePeopleCount: boolean = false;
/** @ignore */
_detectionSubscriber: MsgBusSubscriber;
/** @ignore */
_framingSubscriber: MsgBusSubscriber;
/** @ignore */
_supportedFramingSubscriptions: Array<FramingModes> = [
FramingModes.NORMAL,
FramingModes.GALLERY_VIEW,
];

constructor(manager: IDeviceManager, options?: DetectorOpts) {
super();
this._deviceManager = manager;
this._options = {};
this._validateOptions(options);
this.setMaxListeners(50);
this._detectionSubscriber = new MsgBusSubscriber(
manager.transport as IUsbTransport,
this._detectionHandler
);
this._framingSubscriber = new MsgBusSubscriber(
manager.transport as IUsbTransport,
this._framingHandler
);
}

/**
Expand Down Expand Up @@ -87,6 +107,13 @@ export default class Detector extends EventEmitter implements IDetector {
throw new Error('Unable to talk to device. Tarnsport must be UsbTransport compatible');
}

framingSubscriptionCommandMap(framing: FramingModes): string {
return {
[FramingModes.NORMAL]: 'autozoom/framing',
[FramingModes.GALLERY_VIEW]: 'autozoom/plaza/framing',
}[framing];
}

/**
* Convenience function for setting detection and/or framing data event listeners.
*
Expand Down Expand Up @@ -240,24 +267,24 @@ export default class Detector extends EventEmitter implements IDetector {
listenerConfigOpts: any = {
detectionListener: true,
framingListener: true,
framingMode: FramingModes.NORMAL,
}
) {
try {
const { detectionListener, framingListener } = listenerConfigOpts;
const framingMode = listenerConfigOpts.framingMode
? listenerConfigOpts.framingMode
: FramingModes.NORMAL;
// Detection listener setup
if (!this._subscriptionsSetup && listenerConfigOpts.detectionListener) {
await this.transport.subscribe('autozoom/predictions');
this.transport.on('autozoom/predictions', this._detectionHandler);
if (!this._subscriptionsSetup && detectionListener) {
await this._detectionSubscriber.subscribe({ msgBusCmd: 'autozoom/predictions' });
}
// Framing listener setup
if (!this._subscriptionsSetup && listenerConfigOpts.framingListener) {
await this.transport.subscribe('autozoom/framing');
this.transport.on('autozoom/framing', this._framingHandler);
if (!this._subscriptionsSetup && framingListener) {
await this.updateFramingSubscriber(framingMode, this._framingHandler);
}
this._subscriptionsSetup = true;
} catch (e) {
await this.transport.unsubscribe('autozoom/predictions');
await this.transport.unsubscribe('autozoom/framing');
Logger.error('Something went wrong getting predictions!', e, 'IQ Detector');
this._subscriptionsSetup = false;
}
}
Expand All @@ -282,14 +309,12 @@ export default class Detector extends EventEmitter implements IDetector {
) {
// Detection listener teardown
if (this._subscriptionsSetup && listenerConfigOpts.detectionListener) {
await this.transport.unsubscribe('autozoom/predictions');
this.transport.removeListener('autozoom/predictions', this._detectionHandler);
await this._detectionSubscriber.unsubscribe();
}

// Framing listener teardown
if (this._subscriptionsSetup && listenerConfigOpts.framingListener) {
await this.transport.unsubscribe('autozoom/framing');
this.transport.removeListener('autozoom/framing', this._framingHandler);
this._framingSubscriber.unsubscribe();
}
this._subscriptionsSetup = false;
}
Expand All @@ -310,4 +335,28 @@ export default class Detector extends EventEmitter implements IDetector {
};
return new DetectionsConverter(detections, converterOpts).convert();
}

/**
* Update framing subscriber with new framing mode and/or subscription handler
*
* @param framingMode New framing mode to listen to
* @param subscriptionHandler New function for handling the incoming frame buffer
*/
async updateFramingSubscriber(framingMode: FramingModes, subscriptionHandler?: Function) {
if (!this._supportedFramingSubscriptions.includes(framingMode)) {
throw new Error(
`Framing mode ${framingMode} does not have support for framing subscriptions`
);
}

const framingSubscriberOptions: MsgBusSubscriberOptions = {
msgBusCmd:
framingMode !== undefined
? this.framingSubscriptionCommandMap(framingMode)
: this._framingSubscriber.currentSubscription,
subscriptionHandler,
};

await this._framingSubscriber.subscribe(framingSubscriberOptions);
}
}
58 changes: 58 additions & 0 deletions src/components/msgBusSubscriber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import IUsbTransport from '@huddly/sdk-interfaces/lib/interfaces/IUsbTransport';
import IMsgBusSubscriber, {
MsgBusSubscriberOptions,
} from '@huddly/sdk-interfaces/lib/interfaces/IMsgBusSubscriber';
import Logger from '@huddly/sdk-interfaces/lib/statics/Logger';

/**
* Class for handling framing subscriptions
*/
export default class MsgBusSubscriber implements IMsgBusSubscriber {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want improved typing we could make this class generic, where the provided type is whatever message is passed to the subscriptionHandler

MsgBusSubscriber<Detections>

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand you correctly that means that we will have to reinstantiate the subscriber when switching framing modes? Not necessarily against that though. It also gave me an idea for the interface

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's a good point. Or you could do a discriminated union type and check for whatever is present

_transport: IUsbTransport;
_currentSubscription: string;
_subscriptionHandler: Function;

constructor(transport: IUsbTransport, subscriptionHandler: Function) {
this._transport = transport;
this._subscriptionHandler = subscriptionHandler;
this._currentSubscription = undefined;
}

get currentSubscription() {
return this._currentSubscription;
}

/**
* Initialize the framing subscriber to use a given
* subscription handler and autozoom mode.
* @param {FramingSubscriberOptions} options
*/
async subscribe({ msgBusCmd, subscriptionHandler }: MsgBusSubscriberOptions): Promise<void> {
if (this._currentSubscription) {
this.unsubscribe();
}

if (subscriptionHandler) {
this._subscriptionHandler = subscriptionHandler;
}

try {
await this._transport.subscribe(msgBusCmd);
this._transport.on(msgBusCmd, this._subscriptionHandler);
this._currentSubscription = msgBusCmd;
} catch (err) {
await this.unsubscribe();
Logger.error(`Unable to subscribe to ${msgBusCmd}`, err, MsgBusSubscriber.name);
throw err;
}
}

/**
* Removes the subscription from the transport.
*/
async unsubscribe(): Promise<void> {
await this._transport.unsubscribe(this._currentSubscription);
this._transport.removeListener(this._currentSubscription, this._subscriptionHandler);
this._currentSubscription = undefined;
}
}
8 changes: 4 additions & 4 deletions tests/components/autozoomControl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ describe('AutozoomControl', () => {

it('should throw exception for bad combination of opts', () => {
const opts: AutozoomControlOpts = {
mode: AutozoomModes.PLAZA,
mode: AutozoomModes.SPEAKER_FRAMING,
shouldAutoFrame: false,
};
const badFn = () => new AutozoomControl(deviceManager, opts);
expect(badFn).to.throw(
"AutozoomMode 'plaza' does not support option 'shouldAutoFrame' set to false!"
"AutozoomMode 'speaker_framing' does not support option 'shouldAutoFrame' set to false!"
);
});
});
Expand Down Expand Up @@ -246,12 +246,12 @@ describe('AutozoomControl', () => {
});
it('should throw exception for bad combination of opts', () => {
const opts: AutozoomControlOpts = {
mode: AutozoomModes.PLAZA,
mode: AutozoomModes.SPEAKER_FRAMING,
shouldAutoFrame: false,
};
return expect(autozoomControl.updateOpts(opts)).to.be.rejectedWith(
Error,
"AutozoomMode 'plaza' does not support option 'shouldAutoFrame' set to false!"
"AutozoomMode 'speaker_framing' does not support option 'shouldAutoFrame' set to false!"
);
});
it('should throw exception for undefined|null shouldAutoFrame option', () => {
Expand Down
100 changes: 66 additions & 34 deletions tests/components/detector.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import sinon from 'sinon';
import { expect } from 'chai';
import IDeviceManager from '@huddly/sdk-interfaces/lib/interfaces/IDeviceManager';
import DetectorOpts, { DetectionConvertion } from '@huddly/sdk-interfaces/lib/interfaces/IDetectorOpts';
import DetectorOpts, {
DetectionConvertion,
} from '@huddly/sdk-interfaces/lib/interfaces/IDetectorOpts';

import Detector from './../../src/components/detector';
import DeviceManagerMock from './../mocks/devicemanager.mock';
import * as msgpack from 'msgpack-lite';
import FramingModes from '@huddly/sdk-interfaces/lib/enums/FramingModes';

describe('Detector', () => {
let detector: Detector;
Expand Down Expand Up @@ -226,21 +229,21 @@ describe('Detector', () => {
});

describe('detection/framing subscription listener setup/teardown', () => {
let transportOnStub;
let transportSubscribeStub;
let transportUnsubscribeStub;
let transportRemoveListenerStub;
let detectorSubscribeStub;
let detectorUnsubscribeStub;
let framingSubscribeStub;
let framingUnsubscribeStub;
beforeEach(() => {
transportOnStub = sinon.stub(deviceManager.transport, 'on');
transportSubscribeStub = sinon.stub(deviceManager.transport, 'subscribe');
transportUnsubscribeStub = sinon.stub(deviceManager.transport, 'unsubscribe');
transportRemoveListenerStub = sinon.stub(deviceManager.transport, 'removeListener');
detectorSubscribeStub = sinon.stub(detector._detectionSubscriber, 'subscribe');
detectorUnsubscribeStub = sinon.stub(detector._detectionSubscriber, 'unsubscribe');
framingSubscribeStub = sinon.stub(detector._framingSubscriber, 'subscribe');
framingUnsubscribeStub = sinon.stub(detector._framingSubscriber, 'unsubscribe');
});
afterEach(() => {
transportOnStub.restore();
transportSubscribeStub.restore();
transportUnsubscribeStub.restore();
transportRemoveListenerStub.restore();
detectorSubscribeStub.restore();
detectorUnsubscribeStub.restore();
framingSubscribeStub.restore();
framingUnsubscribeStub.restore();
});

describe('on detection setup', () => {
Expand All @@ -250,33 +253,34 @@ describe('Detector', () => {
detectionListener: true,
framingListener: false,
});
expect(transportSubscribeStub.getCall(0).args[0]).to.equals('autozoom/predictions');
expect(transportOnStub.getCall(0).args[0]).to.equals('autozoom/predictions');
expect(transportSubscribeStub.callCount).to.equals(1);
expect(transportOnStub.callCount).to.equals(1);
expect(detectorSubscribeStub.callCount).to.equals(1);
expect(detector._subscriptionsSetup).to.equals(true);
});
it('should setup framing event listeners only', async () => {
await detector.setupDetectorSubscriptions({
detectionListener: false,
framingListener: true,
});
expect(transportSubscribeStub.getCall(0).args[0]).to.equals('autozoom/framing');
expect(transportOnStub.getCall(0).args[0]).to.equals('autozoom/framing');
expect(transportSubscribeStub.callCount).to.equals(1);
expect(transportOnStub.callCount).to.equals(1);
expect(framingSubscribeStub.callCount).to.equals(1);
expect(detector._subscriptionsSetup).to.equals(true);
});
});

describe('on subscription failure', () => {
describe('on detector subscription failure', () => {
beforeEach(() => {
transportSubscribeStub.rejects('Something went wrong');
detectorSubscribeStub.rejects('Something went wrong');
});
it('should unsubscribe to detection and framing events', async () => {
it('should set _subscriptionsSetup to false', async () => {
await detector.setupDetectorSubscriptions();
expect(detector._subscriptionsSetup).to.equals(false);
});
});
describe('on framing subscription failure', () => {
beforeEach(() => {
framingSubscribeStub.rejects('Something went wrong');
});
it('should set _subscriptionsSetup to false', async () => {
await detector.setupDetectorSubscriptions();
expect(transportUnsubscribeStub.getCall(0).args[0]).to.equals('autozoom/predictions');
expect(transportUnsubscribeStub.getCall(1).args[0]).to.equals('autozoom/framing');
expect(detector._subscriptionsSetup).to.equals(false);
});
});
Expand All @@ -289,10 +293,7 @@ describe('Detector', () => {
detectionListener: true,
framingListener: false,
});
expect(transportUnsubscribeStub.getCall(0).args[0]).to.equals('autozoom/predictions');
expect(transportRemoveListenerStub.getCall(0).args[0]).to.equals('autozoom/predictions');
expect(transportUnsubscribeStub.callCount).to.equals(1);
expect(transportRemoveListenerStub.callCount).to.equals(1);
expect(detectorUnsubscribeStub.callCount).to.equals(1);
expect(detector._subscriptionsSetup).to.equals(false);
});
it('should unsubscribe to framing events and remove detection listener', async () => {
Expand All @@ -301,12 +302,43 @@ describe('Detector', () => {
detectionListener: false,
framingListener: true,
});
expect(transportUnsubscribeStub.getCall(0).args[0]).to.equals('autozoom/framing');
expect(transportRemoveListenerStub.getCall(0).args[0]).to.equals('autozoom/framing');
expect(transportUnsubscribeStub.callCount).to.equals(1);
expect(transportRemoveListenerStub.callCount).to.equals(1);
expect(framingUnsubscribeStub.callCount).to.equals(1);
expect(detector._subscriptionsSetup).to.equals(false);
});
});
});

describe('#updateFramingSubsciber', () => {
let framingSubscribeStub;
beforeEach(() => {
framingSubscribeStub = sinon.stub(detector._framingSubscriber, 'subscribe');
});
afterEach(() => {
framingSubscribeStub.restore();
});
describe('new framing mode is supported', () => {
it('should pass new msg bus command and undefined subscription handler when given just framing mode', async () => {
await detector.updateFramingSubscriber(FramingModes.GALLERY_VIEW);
expect(framingSubscribeStub.getCall(0).args[0].msgBusCmd).equals('autozoom/plaza/framing');
expect(framingSubscribeStub.getCall(0).args[0].subscriptionHandler).equals(undefined);
});
it('should pass new msg bus command and undefined subscription handler when given', async () => {
const handler = () => {
return 'pass';
};
await detector.updateFramingSubscriber(FramingModes.GALLERY_VIEW, handler);
expect(framingSubscribeStub.getCall(0).args[0].msgBusCmd).equals('autozoom/plaza/framing');
expect(framingSubscribeStub.getCall(0).args[0].subscriptionHandler()).equals(handler());
});
});
describe('new framing mode is not supported', () => {
it('should throw an error', async () => {
let error = undefined;
await detector
.updateFramingSubscriber(FramingModes.SPEAKER_FRAMING)
.catch((err) => (error = err))
.finally(() => expect(error).not.to.equal(undefined));
});
});
});
});
Loading