homebridge
Version:
HomeKit support for the impatient
146 lines • 5.18 kB
JavaScript
/**
* MediaPlayback Cluster Behavior
*
* Handles transport commands - play, pause, stop and track navigation - for
* media players and speakers.
*/
import { MediaPlaybackServer } from '@matter/main/behaviors/media-playback';
import { MediaPlayback } from '@matter/main/clusters/media-playback';
import { MatterStatus } from '../errors.js';
import { getRegistryManager } from './EndpointContext.js';
/**
* Default MediaPlayback Server.
*
* matter.js's base MediaPlaybackServer implements none of the commands -
* invoking one throws NotImplementedError - so a player without plugin
* handlers would advertise the transport controls and fail every one. This
* default reflects the command in `currentState` and answers Success, which is
* enough for the endpoint to behave sanely on its own.
*/
export class DefaultMediaPlaybackServer extends MediaPlaybackServer {
/**
* Record a new playback state, when the command implies one.
*/
reflect(state) {
if (state !== undefined) {
this.state.currentState = state;
}
return { status: MediaPlayback.Status.Success };
}
async play() {
return this.reflect(MediaPlayback.PlaybackState.Playing);
}
async pause() {
return this.reflect(MediaPlayback.PlaybackState.Paused);
}
async stop() {
return this.reflect(MediaPlayback.PlaybackState.NotPlaying);
}
// Track navigation does not change whether the player is playing, so these
// only acknowledge the command.
async startOver() {
return this.reflect();
}
async previous() {
return this.reflect();
}
async next() {
return this.reflect();
}
// The requests are ignored here - the default server has no position to
// seek. The parameters must stay so HomebridgeMediaPlaybackServer can
// forward them.
// eslint-disable-next-line unused-imports/no-unused-vars
async skipForward(_request) {
return this.reflect();
}
// eslint-disable-next-line unused-imports/no-unused-vars
async skipBackward(_request) {
return this.reflect();
}
}
/**
* The transport commands routed to plugin handlers, and the playback state
* each one implies once the handler succeeds.
*
* Kept as data rather than one method per command: every command has the same
* shape - run the handler, record the state, answer Success - so writing them
* out longhand would be eight copies of the same error handling.
*/
const PLAYBACK_COMMANDS = {
play: MediaPlayback.PlaybackState.Playing,
pause: MediaPlayback.PlaybackState.Paused,
stop: MediaPlayback.PlaybackState.NotPlaying,
startOver: undefined,
previous: undefined,
next: undefined,
skipForward: undefined,
skipBackward: undefined,
};
/**
* Custom MediaPlayback Server that calls plugin handlers.
*
* Extends the default server for its `reflect()` state handling (the matter.js
* base implements nothing at all).
*/
export class HomebridgeMediaPlaybackServer extends DefaultMediaPlaybackServer {
/**
* Get the registry for this behavior's endpoint
*/
getRegistry() {
return getRegistryManager(this.endpoint).getRegistry(this.endpoint.id);
}
/**
* Run a plugin handler and reflect the resulting playback state.
*
* A handler that throws a Matter protocol error is propagated as-is so the
* controller gets the real status; anything else is answered with a
* cluster-level status rather than an exception, which keeps the endpoint
* online.
*/
async invoke(commandName, request) {
const endpointId = this.endpoint.id;
const registry = this.getRegistry();
try {
await registry.executeHandler(endpointId, 'mediaPlayback', commandName, request);
// Only reached if the handler succeeded - update Matter state
const response = this.reflect(PLAYBACK_COMMANDS[commandName]);
registry.syncStateToCache(endpointId, 'mediaPlayback', { currentState: this.state.currentState });
return response;
}
catch (error) {
if (MatterStatus.isMatterProtocolError(error)) {
throw error;
}
// Unlike the on/off style clusters, MediaPlayback carries its own status
// in the response, so a failure is reported through that rather than as
// a protocol-level error.
return { status: MediaPlayback.Status.InvalidStateForCommand };
}
}
async play() {
return this.invoke('play');
}
async pause() {
return this.invoke('pause');
}
async stop() {
return this.invoke('stop');
}
async startOver() {
return this.invoke('startOver');
}
async previous() {
return this.invoke('previous');
}
async next() {
return this.invoke('next');
}
async skipForward(request) {
return this.invoke('skipForward', request);
}
async skipBackward(request) {
return this.invoke('skipBackward', request);
}
}
//# sourceMappingURL=MediaPlaybackBehavior.js.map