iobroker.emby
Version:
972 lines (866 loc) • 32.6 kB
JavaScript
;
const request = require('request');
const W3CWebSocket = require('websocket').w3cwebsocket;
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
let adapter;
let connection;
let laststates = { };
let timeoutretry;
let timeoutplays = { };
let timeoutstates = { };
let timeoutstarted = { };
function startAdapter(options) {
options = options || {};
Object.assign(options, {
name: 'emby',
stateChange: fStateChange,
unload: fUnload,
ready: function() { main(); },
});
adapter = new utils.Adapter(options);
return adapter;
}
// is called when adapter shuts down - callback has to be called under any circumstances!
function fUnload (callback) {
try {
connection.send('{"MessageType":"SessionsStop", "Data": ""}');
if(connection != undefined) connection.close();
//checkOnline = null;
laststates = { };
Object.keys(timeoutplays).forEach((timeout) => { clearTimeout(timeoutplays[timeout]); });
Object.keys(timeoutstates).forEach((timeout) => { clearTimeout(timeoutstates[timeout]); });
Object.keys(timeoutstarted).forEach((timeout) => { clearTimeout(timeoutstarted[timeout]); });
timeoutplays = { };
timeoutstates = { };
timeoutstarted = { };
adapter.log.info('cleaned everything up...');
clearTimeout(timeoutretry);
callback();
} catch (e) {
callback();
}
}
/*
// is called if a subscribed object changes
adapter.on('objectChange', function (id, obj) {
// Warning, obj can be null if it was deleted
adapter.log.info('objectChange ' + id + ' ' + JSON.stringify(obj));
});
*/
function fStateChange (id, state) {
if (id && state && !state.ack)
{
id = id.substring(adapter.namespace.length + 1);
if(id.indexOf('info.') !== -1 || id.indexOf('playing.') !== -1)
{
adapter.setState(id, state.val, true);
return;
}
const dId = id.substring(0, id.indexOf('.'));
const cmd = id.substring(dId.length + 1);
const headers = {
'Content-Type' : 'application/json'
};
let jsonbody = '';
switch (cmd)
{
case 'command.toggleplay':
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Playing/PlayPause?api_key=' + adapter.config.apikey,
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.play':
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Playing/Unpause?api_key=' + adapter.config.apikey,
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.pause':
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Playing/Pause?api_key=' + adapter.config.apikey,
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.mute':
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/Mute?api_key=' + adapter.config.apikey,
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.unmute':
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/Unmute?api_key=' + adapter.config.apikey,
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.togglemute':
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/ToggleMute?api_key=' + adapter.config.apikey,
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.message':
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Message?api_key=' + adapter.config.apikey,
body: '{"Header":"Test", "Text":"' + state.val + '", "TimeoutMs":"5000" }',
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.dialog':
if(state.val.indexOf('|') !== -1) {
const paras = state.val.split('|');
jsonbody = '{"Header":"' + paras[0] + '", "Text":"' + paras[1] + '" }';
} else {
jsonbody = '{"Header":"ioBroker", "Text":"' + state.val + '" }';
}
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Message?api_key=' + adapter.config.apikey,
body: jsonbody,
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, '', true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.goHome':
request.post('http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/GoHome?api_key=' + adapter.config.apikey,
{ },
function(error, resp, _body) {
if(error)
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.goToSearch':
request.post('http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/GoToSearch?api_key=' + adapter.config.apikey,
{ },
function(error, resp, _body) {
if(error)
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.back':
request.post('http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/Back?api_key=' + adapter.config.apikey,
{ },
function(error, resp, _body) {
if(error)
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
break;
case 'command.volume':
adapter.log.info('http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/SetVolume?api_key=' + adapter.config.apikey);
request.post({
uri: 'http://' + adapter.config.ip + '/Sessions/' + dId + '/Command/SetVolume?api_key=' + adapter.config.apikey,
form: JSON.parse('{ Arguments:{ Volume: ' + state.val + ' } }'),
headers: headers
},
function(error, resp, _body) {
if(!error)
adapter.setState(id, state.val, true);
else
adapter.log.info('Fehler: ' + JSON.stringify(resp));
}
);
adapter.log.info(JSON.stringify(JSON.parse('{ Arguments:{ Volume: ' + state.val + ' } }')));
break;
default:
adapter.log.info('Not supported command: ' + cmd + ' | ' + id);
break;
}
}
}
//adapter.on('message', function (obj) {
// if (typeof obj === 'object' && obj.message) {
// if (obj.command === 'send') {
// if (obj.callback) adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback);
// }
// }
//});
function main() {
adapter.subscribeStates('*');
adapter.setObjectNotExists(adapter.namespace + '.info', {
type: 'channel',
common: {
name: 'Info'
},
native: { }
});
adapter.setObjectNotExists(adapter.namespace + '.info.connection', {
'type': 'state',
'common': {
'name': 'If connected to Emby Server',
'role': 'indicator.connected',
'type': 'boolean',
'read': true,
'write': false,
'def': false
},
'native': {}
});
tryConnect();
}
function tryConnect()
{
try {
if(adapter.config.apikey == '')
{
adapter.log.warn('There is no ApiKey. You can recieve information but cant controle clients.');
}
adapter.log.debug('try to connect to: ' + adapter.config.ip);
const prefix = adapter.config.isSSL ? 'wss://' : 'ws://';
connection = new W3CWebSocket(prefix + adapter.config.ip + '?api_key=' + adapter.config.apikey + '&deviceId=00001'); //8306e66875c54b4c816fed315c3cd2e6
connection.onopen = webOpen;
connection.onerror = webError;
connection.onmessage = webMessage;
} catch(e) {
adapter.setState('info.connection', false, true);
adapter.log.warn('Verbindung konnte nicht hergestellt werden. Nächster Versuch in 1 Minute: "' + e.message + '"');
timeoutretry = setTimeout(tryConnect, 60000);
}
}
function webOpen()
{
if(connection == undefined || connection.readyState !== 1) {
adapter.log.error('Verbindung konnte nicht hergestellt werden. (readyState) Nächster Versuch in 1 Minute.');
if(connection) connection.close();
connection = null;
timeoutretry = setTimeout(tryConnect, 60000);
return;
}
connection.send('{"MessageType":"SessionsStart", "Data": "10000,10000"}');
adapter.log.info('connected with emby (' + adapter.config.ip + ')');
adapter.setState('info.connection', true, true);
}
function webError(error)
{
adapter.setState('info.connection', false, true);
adapter.log.debug('Websocket Error : ' + JSON.stringify(error));
adapter.log.error('WebSocket Error. Try Reconnect in 60s');
timeoutretry = setTimeout(tryConnect, 60000);
}
function webMessage(e)
{
const data = JSON.parse(e.data);
adapter.log.debug(JSON.stringify(data));
for(let i = 0; i < data.Data.length; i++)
{
const d = data.Data[i];
if(adapter.config.deviceIds == '' || ( adapter.config.deviceIds != '' && adapter.config.deviceIds.indexOf(d.Id) == -1))
{
createDevice(d);
if(typeof d.DeviceName !== 'undefined') adapter.setState(d.Id + '.info.deviceName', d.DeviceName, true);
else adapter.setState(d.Id + '.info.deviceName', '', true);
if(typeof d.UserName !== 'undefined') adapter.setState(d.Id + '.info.userName', d.UserName, true);
else adapter.setState(d.Id + '.info.userName', '', true);
if(typeof d.NowPlayingItem !== 'undefined')
{
let endString = '';
if(d.NowPlayingItem.RunTimeTicks != null && d.PlayState.PositionTicks != null) {
const endDate = new Date(Date.now() + ((d.NowPlayingItem.RunTimeTicks - d.PlayState.PositionTicks) / 10000));
endString = endDate.getHours() + ':' + (endDate.getMinutes() < 10 ? '0'+endDate.getMinutes() : endDate.getMinutes()) ;
}
const npi = d.NowPlayingItem;
adapter.setState(d.Id + '.media.endtime', endString, true);
if(typeof npi.Name !== 'undefined') adapter.setState(d.Id + '.media.title', npi.Name, true);
else adapter.setState(d.Id + '.media.title', '', true);
if(typeof npi.Overview !== 'undefined') adapter.setState(d.Id + '.media.description', npi.Overview, true);
else adapter.setState(d.Id + '.media.description', '', true);
if(typeof npi.Type !== 'undefined') adapter.setState(d.Id + '.media.type', npi.Type, true);
else adapter.setState(d.Id + '.media.type', '', true);
if(typeof npi.CommunityRating !== 'undefined') adapter.setState(d.Id + '.media.rating', npi.CommunityRating, true);
else adapter.setState(d.Id + '.media.rating', '', true);
if(typeof npi.ProductionYear !== 'undefined') adapter.setState(d.Id + '.media.year', npi.ProductionYear, true);
else adapter.setState(d.Id + '.media.year', '', true);
if(typeof npi.Taglines !== 'undefined') adapter.setState(d.Id + '.media.tags', npi.Taglines.join(','), true);
else adapter.setState(d.Id + '.media.tags', '', true);
if(typeof npi.Genres !== 'undefined') adapter.setState(d.Id + '.media.genres', npi.Genres.join(','), true);
else adapter.setState(d.Id + '.media.genres', '', true);
if(typeof npi.BackdropImageTags !== 'undefined') adapter.setState(d.Id + '.media.backdropimage', npi.BackdropImageTags.join(','), true);
else adapter.setState(d.Id + '.media.backdropimage', '', true);
if(typeof npi.PrimaryImageAspectRatio != 'undefined') adapter.setState(d.Id + '.media.ratio', npi.PrimaryImageAspectRatio, true);
else adapter.setState(d.Id + '.media.ratio', '', true);
if(typeof npi.OfficialRating != 'undefined') adapter.setState(d.Id + '.media.agerating', npi.OfficialRating, true);
else adapter.setState(d.Id + '.media.agerating', '', true);
if(typeof npi.OriginalTitle != 'undefined') adapter.setState(d.Id + '.media.originaltitle', npi.OriginalTitle, true);
else adapter.setState(d.Id + '.media.originaltitle', '', true);
const prefix = adapter.config.isSSL ? 'https://' : 'http://';
const basePoster = prefix + adapter.config.ip + '/Items/';
switch(npi.Type) {
case 'Episode':
adapter.setState(d.Id + '.posters.main', basePoster + npi.SeriesId + '/Images/Primary', true);
adapter.setState(d.Id + '.posters.season', basePoster + npi.SeasonId + '/Images/Primary', true);
adapter.setState(d.Id + '.posters.episode', basePoster + npi.Id + '/Images/Primary', true);
break;
case 'Movie':
adapter.setState(d.Id + '.posters.main', basePoster + npi.Id + '/Images/Primary', true);
adapter.setState(d.Id + '.posters.season', '', true);
adapter.setState(d.Id + '.posters.episode', '', true);
break;
default:
adapter.setState(d.Id + '.posters.main', '', true);
adapter.setState(d.Id + '.posters.season', '', true);
adapter.setState(d.Id + '.posters.episode', '', true);
break;
}
if(typeof npi.SeasonName !== 'undefined')
{
adapter.setState(d.Id + '.media.seasonName', npi.SeasonName, true);
adapter.setState(d.Id + '.media.seriesName', npi.SeriesName, true);
} else {
adapter.setState(d.Id + '.media.seasonName', '', true);
adapter.setState(d.Id + '.media.seriesName', '', true);
}
//var ispaused;
if(typeof d.PlayState.MediaSourceId !== 'undefined')
{
if(d.PlayState.IsPaused)
changeState(d.Id, 'paused'); //adapter.setState(d.Id + ".media.state", "paused", true);
else
changeState(d.Id, 'playing'); //adapter.setState(d.Id + ".media.state", "playing", true);
} else {
changeState(d.Id, 'paused'); //adapter.setState(d.Id + ".media.state", "paused", true);
}
adapter.setState(d.Id + '.media.isMuted', d.PlayState.IsMuted, true);
} else {
adapter.setState(d.Id + '.media.title', '', true);
adapter.setState(d.Id + '.media.description', '', true);
adapter.setState(d.Id + '.media.seasonName', '', true);
adapter.setState(d.Id + '.media.seriesName', '', true);
adapter.setState(d.Id + '.media.type', 'None', true);
adapter.setState(d.Id + '.media.endtime', '', true);
adapter.setState(d.Id + '.posters.main', '', true);
adapter.setState(d.Id + '.posters.season', '', true);
adapter.setState(d.Id + '.posters.episode', '', true);
changeState(d.Id, 'idle'); //adapter.setState(d.Id + ".media.state", "idle", true);
}
} else {
adapter.log.debug('Device skipped: ' + d.DeviceName);
}
}
}
function changeState(id, state)
{
if(laststates[id] == state)
return;
if(state == 'playing')
{
clearTimeout(timeoutplays[id]);
timeoutstarted[id] = false;
adapter.setState(id + '.media.state', state, true);
} else {
timeoutstates[id] = state;
if(!timeoutstarted[id])
{
timeoutstarted[id] = true;
timeoutplays[id] = setTimeout(function() {
adapter.setState(id + '.media.state', timeoutstates[id], true);
timeoutstarted[id] = false;
}, adapter.config.timeout);
}
}
laststates[id] = state;
}
function createDevice(device)
{
const sid = adapter.namespace + '.' + device.Id;
adapter.setObjectNotExists(sid, {
type: 'device',
common: {
name: device.DeviceName
},
native: { }
});
adapter.setObjectNotExists(sid + '.info', {
type: 'channel',
common: {
name: 'Info'
},
native: { }
});
adapter.setObjectNotExists(sid + '.media', {
type: 'channel',
common: {
name: 'Media Info'
},
native: { }
});
adapter.setObjectNotExists(sid + '.posters', {
type: 'channel',
common: {
name: 'Media Posters'
},
native: { }
});
adapter.setObjectNotExists(sid + '.info.deviceName', {
'type': 'state',
'common': {
'name': 'Name of the Device now playing',
'role': 'info.name',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.info.userName', {
'type': 'state',
'common': {
'name': 'Logged in User',
'role': 'info.name',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.info.supportedCommands', {
'type': 'state',
'common': {
'name': 'List of supported Commands',
'role': 'info.name',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.state', {
'type': 'state',
'common': {
'name': 'State of the Client',
'role': 'media.state',
'type': 'string',
'read': true,
'write': true,
'states': {
'idle': 'Idle',
'paused': 'Paused',
'playing': 'Playing'
}
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.isMuted', {
'type': 'state',
'common': {
'name': 'If Player is muted',
'role': 'media.mute',
'type': 'boolean',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.title', {
'type': 'state',
'common': {
'name': 'Title of file playing',
'role': 'media.title',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.seriesName', {
'type': 'state',
'common': {
'name': 'Name of serie now playing',
'role': 'media.title',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.seasonName', {
'type': 'state',
'common': {
'name': 'Name of season now playing',
'role': 'media.season',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.type', {
'type': 'state',
'common': {
'name': 'Type of file playing',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.description', {
'type': 'state',
'common': {
'name': 'Description of file playing',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.endtime', {
'type': 'state',
'common': {
'name': 'Endtime of playing video/audio',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.rating', {
'type': 'state',
'common': {
'name': 'Community Rating',
'role': 'state',
'type': 'number',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.year', {
'type': 'state',
'common': {
'name': 'Production Year',
'role': 'state',
'type': 'number',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.tags', {
'type': 'state',
'common': {
'name': 'Tags',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.genres', {
'type': 'state',
'common': {
'name': 'Genres',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.backdropimage', {
'type': 'state',
'common': {
'name': 'BackdropImageTags',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.ratio', {
'type': 'state',
'common': {
'name': 'Ratio',
'role': 'state',
'type': 'number',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.agerating', {
'type': 'state',
'common': {
'name': 'Age Rating',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.media.originaltitle', {
'type': 'state',
'common': {
'name': 'Original Title',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.posters.main', {
'type': 'state',
'common': {
'name': 'Main Poster',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.posters.episode', {
'type': 'state',
'common': {
'name': 'Episode Poster',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
adapter.setObjectNotExists(sid + '.posters.season', {
'type': 'state',
'common': {
'name': 'Season Poster',
'role': 'state',
'type': 'string',
'read': true,
'write': false
},
'native': {}
});
if(!device.SupportsRemoteControl)
return;
adapter.setObjectNotExists(sid + '.command', {
type: 'channel',
common: {
name: 'Controll Client'
},
native: { }
});
adapter.setObjectNotExists(sid + '.command.pause', {
'type': 'state',
'common': {
'name': 'Pause',
'role': 'button',
'type': 'button',
'read': false,
'write': true
},
'native': {}
});
adapter.setObjectNotExists(sid + '.command.play', {
'type': 'state',
'common': {
'name': 'Play',
'role': 'button',
'type': 'button',
'read': false,
'write': true
},
'native': {}
});
adapter.setObjectNotExists(sid + '.command.toggleplay', {
'type': 'state',
'common': {
'name': 'Toggle Play',
'role': 'button',
'type': 'button',
'read': false,
'write': true
},
'native': {}
});
adapter.setState(sid + '.info.supportedCommands', JSON.stringify(device.SupportedCommands), true);
for(let i = 0; i < device.SupportedCommands.length; i++)
{
switch(device.SupportedCommands[i])
{
case 'DisplayMessage':
adapter.setObjectNotExists(sid + '.command.message', {
'type': 'state',
'common': {
'name': 'Message',
'role': 'state',
'type': 'string',
'read': false,
'write': true
},
'native': {}
});
adapter.setObjectNotExists(sid + '.command.dialog', {
'type': 'state',
'common': {
'name': 'Message Dialog',
'role': 'state',
'type': 'string',
'read': false,
'write': true
},
'native': {}
});
break;
case 'GoHome':
adapter.setObjectNotExists(sid + '.command.goHome', {
'type': 'state',
'common': {
'name': 'GoHome',
'role': 'button',
'type': 'boolean',
'read': false,
'write': true
},
'native': {}
});
break;
case 'SetVolume':
adapter.setObjectNotExists(sid + '.command.volume', {
'type': 'state',
'common': {
'name': 'Set Volume (causes crash on some devices)',
'role': 'level.volume',
'type': 'number',
'read': true,
'write': true
},
'native': {}
});
break;
case 'GoToSearch':
adapter.setObjectNotExists(sid + '.command.goToSearch', {
'type': 'state',
'common': {
'name': 'Go to search',
'role': 'button',
'type': 'boolean',
'read': false,
'write': true
},
'native': {}
});
break;
case 'Back':
adapter.setObjectNotExists(sid + '.command.back', {
'type': 'state',
'common': {
'name': 'Back',
'role': 'button',
'type': 'boolean',
'read': false,
'write': true
},
'native': {}
});
break;
case 'Mute':
adapter.setObjectNotExists(sid + '.command.mute', {
'type': 'state',
'common': {
'name': 'Mute',
'role': 'button',
'type': 'button',
'read': false,
'write': true
},
'native': {}
});
break;
case 'UnMute':
adapter.setObjectNotExists(sid + '.command.unmute', {
'type': 'state',
'common': {
'name': 'Unmute',
'role': 'button',
'type': 'button',
'read': false,
'write': true
},
'native': {}
});
break;
case 'ToggleMute':
adapter.setObjectNotExists(sid + '.command.togglemute', {
'type': 'state',
'common': {
'name': 'Toggle Mute',
'role': 'button',
'type': 'button',
'read': false,
'write': true
},
'native': {}
});
break;
}
}
}
if (module && module.parent) {
module.exports = startAdapter;
} else {
startAdapter();
}