UNPKG

homebridge-androidtv

Version:

Homebridge plugin to control your AndroidTV / Freebox Player Pop / Freemox Mini 4k

284 lines (235 loc) 15 kB
import { AdminServer } from "./admin/index.js"; import { DeviceManager } from "./DeviceManager.js"; export const PLATFORM_NAME = 'HomebridgeAndroidTV'; export const PLUGIN_NAME = 'homebridge-androidtv'; import {RemoteKeyCode, RemoteDirection} from "androidtv-remote"; class AndroidTV { constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.keys = {}; this.keys[this.api.hap.Characteristic.RemoteKey.REWIND] = RemoteKeyCode.KEYCODE_MEDIA_REWIND; this.keys[this.api.hap.Characteristic.RemoteKey.FAST_FORWARD] = RemoteKeyCode.KEYCODE_MEDIA_FAST_FORWARD; this.keys[this.api.hap.Characteristic.RemoteKey.NEXT_TRACK ] = RemoteKeyCode.KEYCODE_MEDIA_NEXT; this.keys[this.api.hap.Characteristic.RemoteKey.PREVIOUS_TRACK ] = RemoteKeyCode.KEYCODE_MEDIA_PREVIOUS; this.keys[this.api.hap.Characteristic.RemoteKey.ARROW_UP] = RemoteKeyCode.KEYCODE_DPAD_UP; this.keys[this.api.hap.Characteristic.RemoteKey.ARROW_DOWN] = RemoteKeyCode.KEYCODE_DPAD_DOWN; this.keys[this.api.hap.Characteristic.RemoteKey.ARROW_LEFT] = RemoteKeyCode.KEYCODE_DPAD_LEFT; this.keys[this.api.hap.Characteristic.RemoteKey.ARROW_RIGHT] = RemoteKeyCode.KEYCODE_DPAD_RIGHT; this.keys[this.api.hap.Characteristic.RemoteKey.SELECT] = RemoteKeyCode.KEYCODE_DPAD_CENTER; this.keys[this.api.hap.Characteristic.RemoteKey.BACK] = RemoteKeyCode.KEYCODE_BACK; this.keys[this.api.hap.Characteristic.RemoteKey.EXIT] = RemoteKeyCode.KEYCODE_HOME; this.keys[this.api.hap.Characteristic.RemoteKey.PLAY_PAUSE] = RemoteKeyCode.KEYCODE_MEDIA_PLAY_PAUSE; this.keys[this.api.hap.Characteristic.RemoteKey.INFORMATION] = RemoteKeyCode.KEYCODE_INFO; this.channelskeys = {}; this.channelskeys[0] = RemoteKeyCode.KEYCODE_0; this.channelskeys[1] = RemoteKeyCode.KEYCODE_1; this.channelskeys[2] = RemoteKeyCode.KEYCODE_2; this.channelskeys[3] = RemoteKeyCode.KEYCODE_3; this.channelskeys[4] = RemoteKeyCode.KEYCODE_4; this.channelskeys[5] = RemoteKeyCode.KEYCODE_5; this.channelskeys[6] = RemoteKeyCode.KEYCODE_6; this.channelskeys[7] = RemoteKeyCode.KEYCODE_7; this.channelskeys[8] = RemoteKeyCode.KEYCODE_8; this.channelskeys[9] = RemoteKeyCode.KEYCODE_9; this.deviceManager = new DeviceManager(log, config, api); this.deviceManager.load(); this.deviceManager.on('discover', this.discover.bind(this)); this.adminServer = new AdminServer(this.config.port?this.config.port:8182, this.deviceManager) this.adminServer.listen() .then(port => { this.log.info(`Admin server is running on port ${port}`); }) .catch(e => { this.log.error('Failed to launch the admin server:', e.message); }); api.on("didFinishLaunching" , () => { this.log.info("didFinishLaunching"); this.deviceManager.listen(); this.log.info("end didFinishLaunching"); }); } discover(device){ this.log.info("Discover : ", device.toJSON()); const tvName = device.name; const uuid = this.api.hap.uuid.generate('homebridge:androidtv-' + tvName); this.log.info(tvName, 'Registering device', uuid); this.tvAccessory = new this.api.platformAccessory(tvName, uuid); this.tvAccessory.category = device.type; this.infoService = this.tvAccessory.getService(this.api.hap.Service.AccessoryInformation); this.infoService .setCharacteristic(this.api.hap.Characteristic.Manufacturer, "MANUFACTURER") .setCharacteristic(this.api.hap.Characteristic.Model, "MODEL") .setCharacteristic(this.api.hap.Characteristic.Name, tvName) .setCharacteristic(this.api.hap.Characteristic.SerialNumber, uuid) .setCharacteristic(this.api.hap.Characteristic.SoftwareRevision, "VERSION") .setCharacteristic(this.api.hap.Characteristic.FirmwareRevision, PLUGIN_NAME) .setCharacteristic(this.api.hap.Characteristic.HardwareRevision, "PLUGIN_AUTHOR"); const tvService = this.tvAccessory.addService(this.api.hap.Service.Television); tvService.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, tvName); tvService.setCharacteristic(this.api.hap.Characteristic.SleepDiscoveryMode, this.api.hap.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE); tvService.setCharacteristic(this.api.hap.Characteristic.PowerModeSelection, this.api.hap.Characteristic.PowerModeSelection.SHOW); tvService.getCharacteristic(this.api.hap.Characteristic.Active).onSet((newValue, old) => { this.log.info(tvName, 'set Active => setNewValue: ' + newValue); if(device.powered !== (newValue === this.api.hap.Characteristic.Active.ACTIVE)){ device.android_remote.sendPower(); } tvService.updateCharacteristic(this.api.hap.Characteristic.Active, newValue); }); tvService.getCharacteristic(this.api.hap.Characteristic.Active).on('get', function (callback) { this.log.info(tvName, 'get Active', device.powered); callback(null, device.powered?this.api.hap.Characteristic.Active.ACTIVE:this.api.hap.Characteristic.Active.INACTIVE);//tvService.updateCharacteristic(this.api.hap.Characteristic.Active, 1); }.bind(this)); this.deviceManager.on('powered', function (device){ this.log.info(tvName, 'powered', device.powered); tvService.updateCharacteristic(this.api.hap.Characteristic.Active, device.powered?this.api.hap.Characteristic.Active.ACTIVE:this.api.hap.Characteristic.Active.INACTIVE); }.bind(this)); tvService.getCharacteristic(this.api.hap.Characteristic.RemoteKey) .onSet((newValue) => { this.log.info(tvName, 'Set RemoteKey ' + newValue); device.android_remote.sendKey(this.keys[newValue], RemoteDirection.SHORT); }); const speakerService = this.tvAccessory.addService(this.api.hap.Service.TelevisionSpeaker); speakerService .setCharacteristic(this.api.hap.Characteristic.Active, this.api.hap.Characteristic.Active.ACTIVE) .setCharacteristic(this.api.hap.Characteristic.VolumeControlType, this.api.hap.Characteristic.VolumeControlType.RELATIVE_WITH_CURRENT); speakerService.getCharacteristic(this.api.hap.Characteristic.VolumeSelector) .onSet((volumeSelector) => { this.log.info(tvName, 'set VolumeSelector => setNewValue: ' + volumeSelector); if(volumeSelector === this.api.hap.Characteristic.VolumeSelector.INCREMENT){ device.android_remote.sendKey(RemoteKeyCode.KEYCODE_VOLUME_UP, RemoteDirection.SHORT); } else if(volumeSelector === this.api.hap.Characteristic.VolumeSelector.DECREMENT){ device.android_remote.sendKey(RemoteKeyCode.KEYCODE_VOLUME_DOWN, RemoteDirection.SHORT); } }); speakerService.getCharacteristic(this.api.hap.Characteristic.Volume) .on('get', function(callback) { let volume = 0 ; if(device.volume_max > 0){ volume = Math.round(device.volume_current*100/device.volume_max); } this.log.info(tvName, 'get VolumeSelector ' + volume); callback(null, volume); }.bind(this)); this.deviceManager.on('volume', function (device){ let volume = 0; if(device.volume_max > 0){ volume = Math.round(device.volume_current*100/device.volume_max); } this.log.info(tvName, 'Device set Volume', volume); speakerService.updateCharacteristic(this.api.hap.Characteristic.Volume, volume); }.bind(this)); speakerService.getCharacteristic(this.api.hap.Characteristic.Mute) .onSet(function(muted) { this.log.info(tvName, 'set Mute => muted: ' + muted); device.android_remote.sendKey(RemoteKeyCode.KEYCODE_VOLUME_MUTE,RemoteDirection.SHORT); speakerService.updateCharacteristic(this.api.hap.Characteristic.Mute, muted); }); this.deviceManager.on('muted', function (device){ this.log.info(tvName, 'Device set mute', device.volume_muted); speakerService.updateCharacteristic(this.api.hap.Characteristic.Mute, device.volume_muted); }.bind(this)); speakerService.getCharacteristic(this.api.hap.Characteristic.Mute) .on("get", function (callback){ let muted = device.volume_muted; this.log.info(tvName, 'get VolumeMutedSelector'); callback(null, muted); }.bind(this)); let identifier = 0; if(this.config.channels){ for (let channel of this.config.channels){ const uuid = this.api.hap.uuid.generate('homebridge:androidtv-' + tvName + '-channel-' + channel.name); const service = this.tvAccessory.addService(this.api.hap.Service.InputSource, uuid, channel.name); this.log.info(tvName, 'Adding channel', channel.name, uuid); service.setCharacteristic(this.api.hap.Characteristic.Identifier, identifier) service.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, channel.name) service.setCharacteristic(this.api.hap.Characteristic.IsConfigured, this.api.hap.Characteristic.IsConfigured.CONFIGURED) service.setCharacteristic(this.api.hap.Characteristic.InputSourceType, this.api.hap.Characteristic.InputSourceType.TUNER); tvService.addLinkedService(service); identifier++; } } if(this.config.keys){ for (let key of this.config.keys){ const uuid = this.api.hap.uuid.generate('homebridge:androidtv-' + tvName + '-key-' + key.name); const service = this.tvAccessory.addService(this.api.hap.Service.InputSource, uuid, key.name); this.log.info(tvName, 'Adding key', key.name, uuid); service.setCharacteristic(this.api.hap.Characteristic.Identifier, identifier) service.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, key.name) service.setCharacteristic(this.api.hap.Characteristic.IsConfigured, this.api.hap.Characteristic.IsConfigured.CONFIGURED) service.setCharacteristic(this.api.hap.Characteristic.InputSourceType, this.api.hap.Characteristic.InputSourceType.TUNER); tvService.addLinkedService(service); identifier++; } } if(this.config.applications){ for (let application of this.config.applications){ const uuid = this.api.hap.uuid.generate('homebridge:androidtv-' + tvName + '-application-' + application.name); const service = this.tvAccessory.addService(this.api.hap.Service.InputSource, uuid, application.name); this.log.info(tvName, 'Adding application', application.name, uuid); service.setCharacteristic(this.api.hap.Characteristic.Identifier, identifier) service.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, application.name) service.setCharacteristic(this.api.hap.Characteristic.IsConfigured, this.api.hap.Characteristic.IsConfigured.CONFIGURED) service.setCharacteristic(this.api.hap.Characteristic.InputSourceType, this.api.hap.Characteristic.InputSourceType.APPLICATION); tvService.addLinkedService(service); identifier++; } } tvService.setCharacteristic(this.api.hap.Characteristic.ActiveIdentifier, 0); tvService.getCharacteristic(this.api.hap.Characteristic.ActiveIdentifier) .onSet(async (newValue) => { let channel_length = 0; let key_length = 0; if(this.config.channels){ channel_length = this.config.channels.length; } if(this.config.keys){ key_length = this.config.keys.length; } if (newValue < channel_length) { if(this.config.channels){ let channel = this.config.channels[newValue]; let array = this.splitChannelNumber(channel.number); device.android_remote.sendKey(RemoteKeyCode.KEYCODE_HOME,RemoteDirection.SHORT); await new Promise(resolve => setTimeout(resolve, 500)); device.android_remote.sendKey(RemoteKeyCode.KEYCODE_HOME,RemoteDirection.SHORT); await new Promise(resolve => setTimeout(resolve, 500)); device.android_remote.sendKey(RemoteKeyCode.KEYCODE_DPAD_CENTER,RemoteDirection.SHORT); await new Promise(resolve => setTimeout(resolve, 500)); for (let button of array) { this.log.info(tvName, 'Tap on ' + button + ' ' + this.channelskeys[button]); device.android_remote.sendKey(this.channelskeys[button],RemoteDirection.SHORT); } } } else if (newValue < (channel_length + key_length)) { if(this.config.keys){ let index = newValue - channel_length; let key = this.config.keys[index]; let value = RemoteKeyCode[key.key] device.android_remote.sendKey(value,RemoteDirection.SHORT); this.log.info(tvName, 'Tap on ' + key.key + ' ' + RemoteKeyCode[key.key]); } } else { if(this.config.applications){ let index = newValue - channel_length - key_length; let application = this.config.applications[index]; this.log.info(tvName, "Sending link " + application.link); device.android_remote.sendAppLink(application.link); } } this.log.info(tvName, 'set Active Identifier => setNewValue: ' + newValue); }); this.api.publishExternalAccessories(PLUGIN_NAME, [this.tvAccessory]); } splitChannelNumber(number){ let string_number = String(number); let array = string_number.split(''); return array; } } export { AndroidTV };