UNPKG

mpd-avr-client

Version:
336 lines (295 loc) 9.98 kB
const { playOrPauseRegExp, stopRegExp, arrowUpKeyupRegExp, arrowDownKeyupRegExp, enterKeyupRegExp, returnKeyupRegExp, playKeyupRegExp, pauseKeyupRegExp, stopKeyupRegExp, nextKeyupRegExp, previousKeyupRegExp, redFunctionKeyupRegExp, greenFunctionKeyupRegExp, } = require('../const'); const AvrService = require('../services/avr-service'); const MpService = require('../services/mp-service'); // const { getInstance: getMpClient } = require('../clients/mp-client'); /** * @desc The AVR sends a display name request (51:46) when powering on. * After overnight sleep, this may be the ONLY power-on indicator * received before explicit power status codes (5f:72:01 / 51:90:00). */ const AppStateReducer = function (_commandOptions) { return ((commandOptions) => { const avrService = new AvrService(commandOptions); const mpService = new MpService(); /** * Reaction to AVR power status change * @param {boolean} isAudioDeviceOn Whether the AVR is ON * @param {AppState} currentAppState The current application state * @returns {AppState} The next application state */ const onAvrPowerStatusChange = (isAudioDeviceOn, currentAppState) => { // if (isAudioDeviceOn) { // getMpClient().reset(); // } // if there is no change in the power status of the AVR if (isAudioDeviceOn === currentAppState.isAudioDeviceOn) { // then no-op /** * @desc Notes * - The `on 5` command causes the AVR to stop emitting standby events. This application will not turn on the AVR programmatically */ return { ...currentAppState }; } // if there is a change in the power status of the AVR, and // if the AVR turns on if (isAudioDeviceOn) { // then update the application state return { ...currentAppState, isAudioDeviceOn, showPlaylist: !playOrPauseRegExp.test(currentAppState.state), }; } // if there is a change in the power status of the AVR, and // if the AVR turns off, // then reset application mpService.pause(); /** * @desc Unfortunately, there is a mysterious incompatibility issue between cec-client and netcat that prevents sending commands to them in proximity, some magic number is used here */ setTimeout(() => avrService.setActiveSource(), 500); return { ...currentAppState, isAudioDeviceOn, showPlaylist: true }; }; /** * Reaction to Mp status change * @param {MpStatus} mpStatus The new MP status * @param {AppState} currentAppState The current application state * @returns {AppState} The next application state */ const onMpStateChange = ( { state, song, playlistlength, elapsed, duration, repeat, random }, currentAppState ) => { if (playOrPauseRegExp.test(state)) { return { ...currentAppState, showPlaylist: false, state, song, playlistlength, elapsed, duration, repeat, random, }; } if (stopRegExp.test(state)) { return { ...currentAppState, showPlaylist: true, state, song, playlistlength, elapsed, duration, repeat, random, }; } return { ...currentAppState }; }; /** * Reaction to AVR remote control event * @param {CecTransmission} cecTransmission The remote control event * @param {AppState} currentAppState The current application state * @returns {AppState} The next application state */ const onRemoteControlKeyup = (cecTransmission, currentAppState) => { const { showPlaylist, playlists, playlistIdx } = currentAppState; // if the OSD is displaying the playlists if (showPlaylist) { // then handle playlist navigation if (!playlists.length) { return { ...currentAppState }; } /** * @desc arrow up action */ if (arrowUpKeyupRegExp.test(cecTransmission)) { return { ...currentAppState, playlistIdx: (playlists.length + playlistIdx + 1) % playlists.length, }; } /** * @desc arrow down action */ if (arrowDownKeyupRegExp.test(cecTransmission)) { return { ...currentAppState, playlistIdx: (playlists.length + playlistIdx - 1) % playlists.length, }; } /** * @desc enter action */ if (enterKeyupRegExp.test(cecTransmission)) { mpService.playPlaylist(playlists[playlistIdx]); return { ...currentAppState }; } /** * @desc close playlist */ if (returnKeyupRegExp.test(cecTransmission)) { return { ...currentAppState, showPlaylist: !playOrPauseRegExp.test(currentAppState.state), }; } return { ...currentAppState }; } // if the OSD is displaying the player status, // then handle player actions /** * @desc show playlist action */ if (returnKeyupRegExp.test(cecTransmission)) { return { ...currentAppState, showPlaylist: true }; } /** * @desc play action */ if (playKeyupRegExp.test(cecTransmission)) { /** * @desc Unfortunately, there is a mysterious incompatibility issue between cec-client and netcat that prevents sending commands to them in proximity, some magic number is used here */ setTimeout(() => void mpService.resume(), 500); return { ...currentAppState }; } /** * @desc pause action */ if (pauseKeyupRegExp.test(cecTransmission)) { /** * @desc Unfortunately, there is a mysterious incompatibility issue between cec-client and netcat that prevents sending commands to them in proximity, some magic number is used here */ setTimeout(() => mpService.pause(), 500); return { ...currentAppState }; } /** * @desc stop action */ if (stopKeyupRegExp.test(cecTransmission)) { mpService.stop(); return { ...currentAppState }; } /** * @desc next song action */ if (nextKeyupRegExp.test(cecTransmission)) { mpService.nextSong(); return { ...currentAppState }; } /** * @desc previous song action */ if (previousKeyupRegExp.test(cecTransmission)) { mpService.previousSong(); return { ...currentAppState }; } /** * @desc toggle repeat action */ if (redFunctionKeyupRegExp.test(cecTransmission)) { /** * @desc Unfortunately, there is a mysterious incompatibility issue between cec-client and netcat that prevents sending commands to them in proximity, some magic number is used here */ setTimeout( () => void mpService.setRepeat( currentAppState.repeat === '1' ? '0' : '1' ), 500 ); return { ...currentAppState }; } /** * @desc toggle random action */ if (greenFunctionKeyupRegExp.test(cecTransmission)) { /** * @desc Unfortunately, there is a mysterious incompatibility issue between cec-client and netcat that prevents sending commands to them in proximity, some magic number is used here */ setTimeout( () => void mpService.setRandom( currentAppState.random === '1' ? '0' : '1' ), 500 ); return { ...currentAppState }; } return { ...currentAppState }; }; /** * @desc The entire application state */ return [ (currentAppState, event) => { if (!event) { return; } let /** @type AppState */ appState; // if handling the initial application state if (currentAppState === undefined) { // then initialize the application state appState = /** @type AppState */ event; return { ...appState }; } appState = currentAppState; const { data, source } = /** @type {CecClientEvent|MpClientEvent} */ event; // if handling a subsequent actions or state change switch (source) { // then mutate the application state // if the event comes from the music player case 'mpClient': // then update the application state to sync up with the music player state const mpStatus = /** @type MpStatus */ data; return onMpStateChange(mpStatus, appState); // if the event comes from the CEC client case 'cecClient': // then update the application state according to the action const [isAudioDeviceOn] = avrService.decodeAvrPowerStatus( /** @type {CecClientEvent} */ event ); if (isAudioDeviceOn !== undefined) { return onAvrPowerStatusChange(isAudioDeviceOn, appState); } if (avrService.isAvrRequestDisplayName(event)) { return onAvrPowerStatusChange(true, appState); } if (!appState.isAudioDeviceOn) { return { ...appState }; } return onRemoteControlKeyup( /** @type CecTransmission */ data, appState ); // if the event comes from an unknown source default: // then no-op return { ...appState }; } }, undefined, ]; })(_commandOptions); }; module.exports = AppStateReducer;