commit d8abaf3db6bba42a1a15657f845f06501f195e9b Author: zhongjin Date: Mon Sep 24 00:50:31 2018 +0800 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bff84e --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ +*.class +tasks/ +.idea +.git +node_modules/ +temp.txt +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b7f3442 --- /dev/null +++ b/.npmignore @@ -0,0 +1,10 @@ +Gruntfile.js +tasks +node_modules +.idea +.git +/node_modules +test +.travis.yml +temp.txt + appveyor.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..78f2ced --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +os: + - linux + - osx +language: node_js +node_js: + - '4' + - '6' + - '8' + - '10' +before_script: + - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) + - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi' + - npm -v + - npm install winston@2.3.1 + - 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production' +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..f37fc74 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,160 @@ +// To use this file in WebStorm, right click on the file name in the Project Panel (normally left) and select "Open Grunt Console" + +/** @namespace __dirname */ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +"use strict"; + +function getAppName() { + var parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 1].split('.')[0].toLowerCase(); +} + +module.exports = function (grunt) { + + var srcDir = __dirname + '/'; + var pkg = grunt.file.readJSON('package.json'); + var iopackage = grunt.file.readJSON('io-package.json'); + var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; + var appName = getAppName(); + + // Project configuration. + grunt.initConfig({ + pkg: pkg, + replace: { + core: { + options: { + patterns: [ + { + match: /var version = *'[\.0-9]*';/g, + replacement: "var version = '" + version + "';" + }, + { + match: /"version"\: *"[\.0-9]*",/g, + replacement: '"version": "' + version + '",' + } + ] + }, + files: [ + { + expand: true, + flatten: true, + src: [ + srcDir + 'controller.js', + srcDir + 'package.json', + srcDir + 'io-package.json' + ], + dest: srcDir + } + ] + }, + name: { + options: { + patterns: [ + { + match: /yunkong2/gi, + replacement: appName + } + ] + }, + files: [ + { + expand: true, + flatten: true, + src: [ + srcDir + '*', + srcDir + '.travis.yml' + ], + dest: srcDir + } + ] + } + }, + // Javascript code styler + jscs: require(__dirname + '/tasks/jscs.js'), + // Lint + jshint: require(__dirname + '/tasks/jshint.js'), + http: { + get_hjscs: { + options: { + url: 'https://raw.githubusercontent.com/' + appName + '/' + appName + '.js-controller/master/tasks/jscs.js' + }, + dest: 'tasks/jscs.js' + }, + get_jshint: { + options: { + url: 'https://raw.githubusercontent.com/' + appName + '/' + appName + '.js-controller/master/tasks/jshint.js' + }, + dest: 'tasks/jshint.js' + }, + get_gruntfile: { + options: { + url: 'https://raw.githubusercontent.com/' + appName + '/' + appName + '.build/master/adapters/Gruntfile.js' + }, + dest: 'Gruntfile.js' + }, + get_utilsfile: { + options: { + url: 'https://raw.githubusercontent.com/' + appName + '/' + appName + '.build/master/adapters/utils.js' + }, + dest: 'lib/utils.js' + }, + get_jscsRules: { + options: { + url: 'https://raw.githubusercontent.com/' + appName + '/' + appName + '.js-controller/master/tasks/jscsRules.js' + }, + dest: 'tasks/jscsRules.js' + } + } + }); + + grunt.registerTask('updateReadme', function () { + var readme = grunt.file.read('README.md'); + var pos = readme.indexOf('## Changelog\n'); + if (pos != -1) { + var readmeStart = readme.substring(0, pos + '## Changelog\n'.length); + var readmeEnd = readme.substring(pos + '## Changelog\n'.length); + + if (readme.indexOf(version) == -1) { + var timestamp = new Date(); + var date = timestamp.getFullYear() + '-' + + ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + + ('0' + (timestamp.getDate()).toString(10)).slice(-2); + + var news = ""; + if (iopackage.common.whatsNew) { + for (var i = 0; i < iopackage.common.whatsNew.length; i++) { + if (typeof iopackage.common.whatsNew[i] == 'string') { + news += '* ' + iopackage.common.whatsNew[i] + '\n'; + } else { + news += '* ' + iopackage.common.whatsNew[i].en + '\n'; + } + } + } + + grunt.file.write('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); + } + } + }); + + grunt.loadNpmTasks('grunt-replace'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-jscs'); + grunt.loadNpmTasks('grunt-http'); + + grunt.registerTask('default', [ + 'http', + 'replace:core', + 'updateReadme', + 'jshint', + 'jscs' + ]); + + grunt.registerTask('p', [ + 'replace:core', + 'updateReadme' + ]); + grunt.registerTask('rename', [ + 'replace:name' + ]); +}; \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d5137a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016, instalator + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b486033 --- /dev/null +++ b/README.md @@ -0,0 +1,192 @@ +![Logo](admin/kodi.png) + +[![NPM version](https://img.shields.io/npm/v/yunkong2.kodi.svg)](https://www.npmjs.com/package/yunkong2.kodi) +[![Downloads](https://img.shields.io/npm/dm/yunkong2.kodi.svg)](https://www.npmjs.com/package/yunkong2.kodi) +[![Tests](http://img.shields.io/travis/instalator/yunkong2.kodi/master.svg)](https://travis-ci.org/instalator/yunkong2.kodi) + +[![NPM](https://nodei.co/npm/yunkong2.kodi.png?downloads=true)](https://nodei.co/npm/yunkong2.kodi/) + +# Kodi's JSON-RPC API for yunkong2 +You can find kodi's official documentation of the JSON-RCP API [here](http://kodi.wiki/view/JSON-RPC_API) and the full list of available commands (for protocol version 6) [here](http://kodi.wiki/view/JSON-RPC_API/v6). + +***Note: This adapter requires Node 0.12+ (so 0.10 not supported)*** + +## Конфигурация KODI +Remote control enable. +![Remote control enable.] (admin/remote.jpg) +JSON-RPC API использует **по умолчанию порт 9090**, для того чтобы его изменить необходимо внести изменения в файл [advancedsettings.xml](http://kodi.wiki/view/AdvancedSettings.xml) + +_Note: The advancedsettings.xml file does not exist by default. You have to create it first!_ +```xml + + true + 9999 + +``` +![http enable.](admin/web.jpg) + +## Конфигурация драйвера +В найстройках драйвера указывается IP адрес KODI и порт для JSON-RPC API (по умолчанию 9090). + +## Using +### ShowNotif: +Один важный момент, если используется заголовок сообщения, то он должен всегда находится перед самим текстом сообщения (Внимание;Протечка воды), расположение остальных параметров не критично. + +**Image:** +Уровень сообщения + * 'info' - 0 (default), + * 'warning' - 1, + * 'error' - 2. + +**displaytime:** +Время отображения сообщения в милисекундах, минимум 1500 макс 30000 мс. + +**Пример:** + * 1;Внимание;Протечка воды;15000 + * Внимание;Протечка воды;2;10000 + * Внимание;Протечка воды + * Протечка воды + +Так же сообщения можно отправлять из драйвера javascript: +```js +sendTo("kodi.0", { + message: 'Возможно протечка воды ', //Текст сообщения + title: 'ВНИМАНИЕ!!!', //Заголовок сообщения + image: 'https://raw.githubusercontent.com/instalator/yunkong2.kodi/master/admin/kodi.png', //Ссылка на иконку + delay: 7000 //Время отображения сообщения милисекундах (минимум 1500 макс 30000 мс) +}); +``` +### SwitchPVR: +Переключение PVR IPTV каналов по названию канала в плейлисте. +**Пример:** + ТВ канал - Discovery Science найдет как по полному наименованию так и по discover, + +### Youtube: +Для открытия видео с сайта youtube достаточно записать код видео в данный статус. Начиная с версии 0.1.5 и выше можно вставлять прямую ссылку на видео, а также код или полную ссылку на плейлист. +Например: Для открытия этого [видео](https://www.youtube.com/watch?v=Bvmxr24D4TA), необходимо установить в статус - Bvmxr24D4TA + +### Open: +Сюда записывается ссылка на медиконтент в сети интернет либо путь до локального медиа файла. +После записи значения начнется воспроизведение на проигрователе KODI. + +### Position: +Текущая позиция в плейлисте, так же в этот статус можно записать необходимую позицую и KODI тут же перейдет к воспроизведению этой позиции. + +### Seek: +Текущее значение позиции воспроизведения в процентах от 0 до 100. + +### Repeat: +Повтор воспроизведения, принимает следующие значения: +* off - повтор воспроизведения отключен +* on - повтор воспроизведения текущего трека +* all - повтор всего плейлиста + +### Shuffle: +Перемешивание списка треков в плейлисте для случайного воспроизведения. +Принимает значения true и false + +### Play: +Старт воспроизведения (true, false) + +### Speed: +Скорость воспроизведения. Фиксированные значения -32, -16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16, 32, а также increment и decrement + +### Directory: +Сюда записывается путь до папки или диска, в ответ в этот статус записывается список каталогов указанной папки или диска. + +### ActivateWindow: +Активизирует в проигрывателе окно. Поддерживает следующий список: +``` +"home", "programs", "pictures", "filemanager", "files", "settings", "music", "video", "videos", "tv", "pvr", "pvrguideinfo", "pvrrecordinginfo", "pvrtimersetting", "pvrgroupmanager", "pvrchannelmanager", "pvrchannelmanager", "pvrguidesearch", "pvrchannelscan", "pvrupdateprogress", "pvrosdchannels", "pvrosdguide", "pvrosddirector", "pvrosdcutter", "pvrosdteletext", "systeminfo", "testpattern", "screencalibration", "guicalibration", "picturessettings", "programssettings", "weathersettings", "musicsettings", "systemsettings", "videossettings", "networksettings", "servicesettings", "appearancesettings", "pvrsettings", "tvsettings", "scripts", "videofiles", "videolibrary", "videoplaylist", "loginscreen", "profiles", "skinsettings", "addonbrowser", "yesnodialog", "progressdialog", "virtualkeyboard", "volumebar", "submenu", "favourites", "contextmenu", "infodialog", "numericinput", "gamepadinput", "shutdownmenu", "mutebug", "playercontrols", "seekbar", "musicosd", "addonsettings", "visualisationsettings", "visualisationpresetlist", "osdvideosettings", "osdaudiosettings", "videobookmarks", "filebrowser", "networksetup", "mediasource", "profilesettings", "locksettings", "contentsettings", "songinformation", "smartplaylisteditor", "smartplaylistrule", "busydialog", "pictureinfo", "accesspoints", "fullscreeninfo", "karaokeselector", "karaokelargeselector", "sliderdialog", "addoninformation", "musicplaylist", "musicfiles", "musiclibrary", "musicplaylisteditor", "teletext", "selectdialog", "musicinformation", "okdialog", "movieinformation", "textviewer", "fullscreenvideo", "fullscreenlivetv", "visualisation", "slideshow", "filestackingdialog", "karaoke", "weather", "screensaver", "videoosd", "videomenu", "videotimeseek", "musicoverlay", "videooverlay", "startwindow", "startup", "peripherals", "peripheralsettings", "extendedprogressdialog", "mediafilter". +``` + +### ExecuteAction: +Можно выполнить одно из следующих действий: +``` +"left", "right", "up", "down", "pageup", "pagedown", "select", "highlight", "parentdir", "parentfolder", "back", "previousmenu", "info", "pause", "stop", "skipnext", "skipprevious", "fullscreen", "aspectratio", "stepforward", "stepback", "bigstepforward", "bigstepback", "osd", "showsubtitles", "nextsubtitle", "codecinfo", "nextpicture", "previouspicture", "zoomout", "zoomin", "playlist", "queue", "zoomnormal", "zoomlevel1", "zoomlevel2", "zoomlevel3", "zoomlevel4", "zoomlevel5", "zoomlevel6", "zoomlevel7", "zoomlevel8", "zoomlevel9", "nextcalibration", "resetcalibration", "analogmove", "rotate", "rotateccw", "close", "subtitledelayminus", "subtitledelay", "subtitledelayplus", "audiodelayminus", "audiodelay", "audiodelayplus", "subtitleshiftup", "subtitleshiftdown", "subtitlealign", "audionextlanguage", "verticalshiftup", "verticalshiftdown", "nextresolution", "audiotoggledigital", "number0", "number1", "number2", "number3", "number4", "number5", "number6", "number7", "number8", "number9", "osdleft", "osdright", "osdup", "osddown", "osdselect", "osdvalueplus", "osdvalueminus", "smallstepback", "fastforward", "rewind", "play", "playpause", "delete", "copy", "move", "mplayerosd", "hidesubmenu", "screenshot", "rename", "togglewatched", "scanitem", "reloadkeymaps", "volumeup", "volumedown", "mute", "backspace", "scrollup", "scrolldown", "analogfastforward", "analogrewind", "moveitemup", "moveitemdown", "contextmenu", "shift", "symbols", "cursorleft", "cursorright", "showtime", "analogseekforward", "analogseekback", "showpreset", "presetlist", "nextpreset", "previouspreset", "lockpreset", "randompreset", "increasevisrating", "decreasevisrating", "showvideomenu", "enter", "increaserating", "decreaserating", "togglefullscreen", "nextscene", "previousscene", "nextletter", "prevletter", "jumpsms2", "jumpsms3", "jumpsms4", "jumpsms5", "jumpsms6", "jumpsms7", "jumpsms8", "jumpsms9", "filter", "filterclear", "filtersms2", "filtersms3", "filtersms4", "filtersms5", "filtersms6", "filtersms7", "filtersms8", "filtersms9", "firstpage", "lastpage", "guiprofile", "red", "green", "yellow", "blue", "increasepar", "decreasepar", "volampup", "volampdown", "channelup", "channeldown", "previouschannelgroup", "nextchannelgroup", "leftclick", "rightclick", "middleclick", "doubleclick", "wheelup", "wheeldown", "mousedrag", "mousemove", "noop". + +``` +### System: + - EjectOpticalDrive - Извлекает или закрывает дисковод оптических дисков (если имеется) + - Hibernate - включение спящего режима + - Reboot - перезагрузка системы + - Shutdown - выключает систему + - Suspend - приостанавливает Kodi + +## Changelog + +#### 1.0.0 (2017-11-13) +* (instalator) up to stable + +#### 0.2.4 (2017-10-16) +* (instalator) fix error + +#### 0.2.3 (2017-08-15) +* (instalator) fix error switchPVR +* (instalator) Added description "System" to readme + +#### 0.2.2 (2017-08-14) +* (instalator) added object - System (EjectOpticalDrive, Hibernate, Reboot, Shutdown, Suspend) +* (instalator) fix playlist widget + +#### 0.2.0 (2017-01-07) +* (instalator) added object - state +* (instalator) change repeat to bool + + +#### 0.1.9 (2017-01-05) +* (instalator) change for vis-players + +#### 0.1.8 (2016-12-25) +* (instalator) change open youtube playlist + +#### 0.1.6 (2016-12-23) +* (instalator) added Tests +* (instalator) added open youtube playlist and open youtube link + +#### 0.1.4 (2016-07-05) +* (instalator) fix error for Open +* (instalator) fix method number + +#### 0.1.3 +* (bluefox) fix error milti instance + +#### 0.1.2 (2016-07-05) +* (instalator) change pvr switch - add stop +* (instalator) change dependencies +* (instalator) fix change play/stop state + +#### 0.1.1 (2016-05-30) +* (instalator) change admin setting +* (instalator) Fix error 'playerid' of undefined + +#### 0.1.0 (2016-05-22) +* (instalator) beta + +#### 0.0.6 (2016-05-08) +* (bluefox) fixed crash when the driver turned on the KODI +* (bluefox) make adapter disabled by default, because no IP set +* (instalator) Thumbnail widget +* (instalator) added GetDirectory, GetVideoLibrary +* (instalator) added Scan & Clean Library + +#### 0.0.5 (2016-05-04) +* (instalator) change creating object +* (instalator) added info.connection state + +#### 0.0.4 (2016-05-03) +* (instalator) fix error +* (instalator) added VIS widgets + +#### 0.0.3 (2016-05-01) +* (instalator) fix error +* (instalator) added send message from JS + +#### 0.0.2 (2016-04-24) +* (instalator) remote player +* (instalator) ShowNotification +* (instalator) info playing +* (instalator) GetPVRChannel + +#### 0.0.1 +* (instalator) initial (17.04.2016) diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..2999890 --- /dev/null +++ b/admin/index.html @@ -0,0 +1,69 @@ + + + + + + + + + + +
+ + +
+

Kodi adapter settings

+ + + + + + + +
IP:
PortRPC: default:

Kodi webserver settings

PortWeb:
Login:
Password:
+

on save adapter restarts with new config immediately

+
+ + diff --git a/admin/kodi.png b/admin/kodi.png new file mode 100644 index 0000000..45bdb7f Binary files /dev/null and b/admin/kodi.png differ diff --git a/admin/remote.jpg b/admin/remote.jpg new file mode 100644 index 0000000..9f289e1 Binary files /dev/null and b/admin/remote.jpg differ diff --git a/admin/web.jpg b/admin/web.jpg new file mode 100644 index 0000000..c6c67a0 Binary files /dev/null and b/admin/web.jpg differ diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..bb429a7 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,25 @@ +version: 'test-{build}' +environment: + matrix: + - nodejs_version: '4' + - nodejs_version: '6' + - nodejs_version: '8' + - nodejs_version: '10' +platform: + - x86 + - x64 +clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%' +install: + - ps: 'Install-Product node $env:nodejs_version $env:platform' + - ps: '$NpmVersion = (npm -v).Substring(0,1)' + - ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }' + - ps: npm --version + - npm install + - npm install winston@2.3.1 + - 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production' +test_script: + - echo %cd% + - node --version + - npm --version + - npm test +build: 'off' diff --git a/io-package.json b/io-package.json new file mode 100644 index 0000000..ac04070 --- /dev/null +++ b/io-package.json @@ -0,0 +1,1314 @@ +{ + "common": { + "name": "kodi", + "version": "1.0.0", + "news": { + "0.2.4": { + "en": "fix error", + "de": "fix error", + "ru": "fix error" + }, + "0.2.3": { + "en": "fix error switchPVR\nAdded description 'System' to readme", + "de": "fix error switchPVR\nAdded description 'System' to readme", + "ru": "fix error switchPVR\nAdded description 'System' to readme" + }, + "0.2.2": { + "en": "added object - System (EjectOpticalDrive, Hibernate, Reboot, Shutdown, Suspend)\nfix playlist widget", + "de": "added object - System (EjectOpticalDrive, Hibernate, Reboot, Shutdown, Suspend)\nfix playlist widget", + "ru": "added object - System (EjectOpticalDrive, Hibernate, Reboot, Shutdown, Suspend)\nfix playlist widget" + }, + "0.2.0": { + "en": "added object - state\nchange repeat to bool", + "de": "added object - state\nchange repeat to bool", + "ru": "added object - state\nchange repeat to bool" + }, + "0.1.9": { + "en": "change for vis-players", + "de": "change for vis-players", + "ru": "change for vis-players" + }, + "0.1.8": { + "en": "change open youtube playlist", + "de": "change open youtube playlist", + "ru": "change open youtube playlist" + }, + "0.1.6": { + "en": "added open youtube playlist and open youtube link. Added Tests", + "de": "added open youtube playlist and open youtube link. Added Tests", + "ru": "added open youtube playlist and open youtube link. Added Tests" + }, + "0.1.4": { + "en": "fix error for Open, fix method number", + "de": "fix error for Open, fix method number", + "ru": "fix error for Open, fix method number" + }, + "0.1.3": { + "en": "fix error milti instance", + "de": "fix error milti instance", + "ru": "fix error milti instance" + } + }, + "title": "KODI adapter", + "desc": { + "en": "KODI Adapter", + "de": "KODI Adapter", + "ru": "KODI драйвер" + }, + "license": "MIT", + "authors": [ + "instalator" + ], + "platform": "Javascript/Node.js", + "mode": "daemon", + "icon": "kodi.png", + "enabled": false, + "messagebox": true, + "extIcon": "https://raw.githubusercontent.com/instalator/yunkong2.kodi/master/admin/kodi.png", + "keywords": ["yunkong2.kodi", "vis", "GUI", "graphical"], + "readme": "https://github.com/instalator/yunkong2.kodi/blob/master/README.md", + "loglevel": "info", + "type": "multimedia", + "restartAdapters": ["vis"] + }, + "native": { + "ip": "127.0.0.1", + "port": 9090, + "login": "", + "password": "" + }, + "native": { + "ip": "127.0.0.1", + "port": 9090, + "login": "", + "password": "" + }, + "objects": [], + "instanceObjects": [ + { + "_id": "info.connection", + "type": "state", + "common": { + "role": "indicator.connected", + "name": "If connected to KODI", + "type": "boolean", + "read": true, + "write": false, + "def": false + }, + "native": {} + }, + { + "_id": "system.EjectOpticalDrive", + "type": "state", + "common": { + "role": "button", + "name": "Ejects or closes the optical disc drive (if available)", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "system.Hibernate", + "type": "state", + "common": { + "role": "button", + "name": "Puts the system running Kodi into hibernate mode", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "system.Reboot", + "type": "state", + "common": { + "role": "button", + "name": "Reboots the system running Kodi", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "system.Shutdown", + "type": "state", + "common": { + "role": "button", + "name": "Shuts the system running Kodi down", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "system.Suspend", + "type": "state", + "common": { + "role": "button", + "name": "Suspends the system running Kodi", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "state", + "type": "state", + "common": { + "name": "Play, stop, or pause", + "type": "string", + "role": "media.state", + "values": "stop,play,pause", + "read": true, + "write": false, + "def": "stop" + }, + "native": {} + }, + { + "_id": "OnInputRequested", + "type": "state", + "common": { + "name": "OnInputRequested", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "Sources", + "type": "state", + "common": { + "name": "Sources", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "CleanAudioLibrary", + "type": "state", + "common": { + "name": "CleanAudioLibrary", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "CleanVideoLibrary", + "type": "state", + "common": { + "name": "CleanVideoLibrary", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "ScanAudioLibrary", + "type": "state", + "common": { + "name": "ScanAudioLibrary", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "ScanVideoLibrary", + "type": "state", + "common": { + "name": "ScanVideoLibrary", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "Directory", + "type": "state", + "common": { + "role": "media.browser", + "name": "The database browser", + "type": "string", + "read": true, + "write": true, + "def": "" + }, + "native": {} + }, + { + "_id": "VideoLibrary", + "type": "state", + "common": { + "name": "VideoLibrary", + "type": "object", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "playlist", + "type": "state", + "common": { + "name": "playlist", + "type": "string", + "role": "media.playlist", + "read": true, + "write": true + }, + "native": {} + }, + { + "_id": "currentplay", + "type": "state", + "common": { + "name": "currentplay", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "input.Back", + "type": "state", + "common": { + "name": "Back", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.ContextMenu", + "type": "state", + "common": { + "name": "ContextMenu", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.Down", + "type": "state", + "common": { + "name": "Down", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.Home", + "type": "state", + "common": { + "name": "Home", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.Info", + "type": "state", + "common": { + "name": "Info", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.Left", + "type": "state", + "common": { + "name": "Left", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.Right", + "type": "state", + "common": { + "name": "Right", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.Select", + "type": "state", + "common": { + "name": "Select", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.SendText", + "type": "state", + "common": { + "name": "SendText", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.ShowCodec", + "type": "state", + "common": { + "name": "ShowCodec", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.ShowOSD", + "type": "state", + "common": { + "name": "ShowOSD", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "input.Up", + "type": "state", + "common": { + "name": "Up", + "type": "string", + "def": false, + "role": "button" + }, + "native": {} + }, + { + "_id": "setsubtitle", + "type": "state", + "common": { + "name": "setsubtitle", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "zoom", + "type": "state", + "common": { + "name": "zoom", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "ExecuteAction", + "type": "state", + "common": { + "name": "ExecuteAction", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "add", + "type": "state", + "common": { + "role": "media.add", + "name": "The current playlist add", + "type": "string", + "read": true, + "write": true + }, + "native": {} + }, + { + "_id": "ActivateWindow", + "type": "state", + "common": { + "name": "ActivateWindow", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "ShowNotif", + "type": "state", + "common": { + "name": "ShowNotif", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "seek", + "type": "state", + "common": { + "role": "media.seek", + "name": "Controlling playback seek", + "type": "number", + "unit": "%", + "min": 0, + "max": 100, + "read": true, + "write": true + }, + "native": {} + }, + { + "_id": "open", + "type": "state", + "common": { + "name": "open", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "youtube", + "type": "state", + "common": { + "name": "youtube", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "SwitchPVR", + "type": "state", + "common": { + "name": "SwitchPVR", + "type": "string", + "def": false, + "role": "media" + }, + "native": {} + }, + { + "_id": "next", + "type": "state", + "common": { + "role": "button.next", + "name": "Controlling playback next", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "previous", + "type": "state", + "common": { + "role": "button.prev", + "name": "Controlling playback previous", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "stop", + "type": "state", + "common": { + "role": "button.stop", + "name": "Controlling playback stop", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "mute", + "type": "state", + "common": { + "role": "media.mute", + "name": "Player mute", + "type": "boolean", + "read": true, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "playid", + "type": "state", + "common": { + "role": "media.playid", + "name": "Controlling playback playid", + "type": "integer", + "read": true, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "volume", + "type": "state", + "common": { + "name": "volume", + "type": "number", + "role": "level.volume", + "read": true, + "write": false, + "min": 0, + "max": 100 + }, + "native": {} + }, + { + "_id": "pvr.playlist_tv", + "type": "state", + "common": { + "name": "pvr.playlist_tv", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "pvr.playlist_radio", + "type": "state", + "common": { + "name": "pvr.playlist_radio", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "playing_time", + "type": "state", + "common": { + "role": "media.elapsed.text", + "name": "playback duration", + "type": "string", + "read": true, + "write": true, + "def": "" + }, + "native": {} + }, + { + "_id": "playing_time_total", + "type": "state", + "common": { + "role": "media.duration.text", + "name": "playback duration", + "type": "string", + "read": true, + "write": true, + "def": "" + }, + "native": {} + }, + { + "_id": "canseek", + "type": "state", + "common": { + "name": "canseek", + "def": false, + "role": "media", + "type": "boolean" + }, + "native": {} + }, + { + "_id": "repeat", + "type": "state", + "common": { + "name": "repeat", + "type": "boolean", + "role": "media.mode.repeat", + "read": true, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "shuffle", + "type": "state", + "common": { + "name": "shuffle", + "type": "boolean", + "role": "media.mode.shuffle", + "read": true, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "speed", + "type": "state", + "common": { + "name": "speed", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "position", + "type": "state", + "common": { + "name": "Current playing track", + "type": "number", + "role": "media.track", + "read": true, + "write": true + }, + "native": {} + }, + { + "_id": "playlistid", + "type": "state", + "common": { + "name": "playlistid", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "partymode", + "type": "state", + "common": { + "name": "partymode", + "def": false, + "role": "media", + "type": "boolean" + }, + "native": {} + }, + { + "_id": "codec", + "type": "state", + "common": { + "name": "codec", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "samplerate", + "type": "state", + "common": { + "name": "samplerate", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "bitrate", + "type": "state", + "common": { + "name": "bitrate", + "type": "number", + "role": "media.bitrate", + "read": true, + "write": true, + "def": 0 + }, + "native": {} + }, + { + "_id": "type", + "type": "state", + "common": { + "name": "type", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.album", + "type": "state", + "common": { + "name": "Album of current played song", + "type": "string", + "role": "media.album", + "read": true, + "write": false, + "def": "" + }, + "native": {} + }, + { + "_id": "info.artist", + "type": "state", + "common": { + "name": "Artist of current played song", + "type": "string", + "role": "media.artist", + "read": true, + "write": false, + "def": "" + }, + "native": {} + }, + { + "_id": "info.fanart", + "type": "state", + "common": { + "name": "info.fanart", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.file", + "type": "state", + "common": { + "name": "info.file", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.genre", + "type": "state", + "common": { + "name": "Genre", + "type": "string", + "role": "media.genre", + "read": true, + "write": true + }, + "native": {} + }, + { + "_id": "info.label", + "type": "state", + "common": { + "name": "info.label", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.playcount", + "type": "state", + "common": { + "name": "info.playcount", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.rating", + "type": "state", + "common": { + "name": "info.rating", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.thumbnail", + "type": "state", + "common": { + "name": "info.thumbnail", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.title", + "type": "state", + "common": { + "name": "Title of current played song", + "type": "string", + "role": "media.title", + "read": true, + "write": false, + "def": "" + }, + "native": {} + }, + { + "_id": "clear", + "type": "state", + "common": { + "role": "media.clear", + "name": "The current playlist clear", + "type": "string", + "read": false, + "write": true + }, + "native": {} + }, + { + "_id": "info.track", + "type": "state", + "common": { + "name": "info.track", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.type", + "type": "state", + "common": { + "name": "info.type", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.year", + "type": "state", + "common": { + "name": "info.year", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "playlist", + "type": "state", + "common": { + "name": "playlist", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "systeminfo.name", + "type": "state", + "common": { + "name": "systeminfo.name", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "systeminfo.version", + "type": "state", + "common": { + "name": "systeminfo.version", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "systeminfo.system", + "type": "state", + "common": { + "name": "systeminfo.system", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "systeminfo.kernel", + "type": "state", + "common": { + "name": "systeminfo.kernel", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "channels", + "type": "state", + "common": { + "name": "channels", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "language", + "type": "state", + "common": { + "name": "language", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "audiostream", + "type": "state", + "common": { + "name": "audiostream", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.id", + "type": "state", + "common": { + "name": "info.id", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.albumartist", + "type": "state", + "common": { + "name": "info.albumartist", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "play", + "type": "state", + "common": { + "role": "button.play", + "name": "Controlling playback play", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "pause", + "type": "state", + "common": { + "role": "button.pause", + "name": "Controlling playback pause", + "type": "boolean", + "read": false, + "write": true, + "def": false + }, + "native": {} + }, + { + "_id": "info.episode", + "type": "state", + "common": { + "name": "info.episode", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.imdbnumber", + "type": "state", + "common": { + "name": "info.imdbnumber", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.originaltitle", + "type": "state", + "common": { + "name": "info.originaltitle", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.plot", + "type": "state", + "common": { + "name": "info.plot", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.season", + "type": "state", + "common": { + "name": "info.season", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.showtitle", + "type": "state", + "common": { + "name": "info.showtitle", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.audio", + "type": "state", + "common": { + "name": "info.audio", + "def": false, + "role": "media", + "type": "object" + }, + "native": {} + }, + { + "_id": "info.subtitle", + "type": "state", + "common": { + "name": "info.subtitle", + "def": false, + "role": "media", + "type": "object" + }, + "native": {} + }, + { + "_id": "info.video", + "type": "state", + "common": { + "name": "info.video", + "def": false, + "role": "media", + "type": "object" + }, + "native": {} + }, + { + "_id": "info.tagline", + "type": "state", + "common": { + "name": "info.tagline", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.audio_channels", + "type": "state", + "common": { + "name": "info.audio_channels", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.audio_codec", + "type": "state", + "common": { + "name": "info.audio_codec", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.audio_language", + "type": "state", + "common": { + "name": "info.audio_language", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.video_aspect", + "type": "state", + "common": { + "name": "info.video_aspect", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.video_codec", + "type": "state", + "common": { + "name": "info.video_codec", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.video_duration", + "type": "state", + "common": { + "name": "info.video_duration", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.video_height", + "type": "state", + "common": { + "name": "info.video_height", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.video_stereomode", + "type": "state", + "common": { + "name": "info.video_stereomode", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + }, + { + "_id": "info.video_width", + "type": "state", + "common": { + "name": "info.video_width", + "def": false, + "role": "media", + "type": "number" + }, + "native": {} + }, + { + "_id": "info.subtitle_language", + "type": "state", + "common": { + "name": "info.subtitle_language", + "def": false, + "role": "media", + "type": "string" + }, + "native": {} + } + ] +} diff --git a/kodi.js b/kodi.js new file mode 100644 index 0000000..06a8f9b --- /dev/null +++ b/kodi.js @@ -0,0 +1,837 @@ +"use strict"; + +var kodi = require('kodi-ws'); +var utils = require(__dirname + '/lib/utils'); +var adapter = utils.Adapter('kodi'); +//var querystring = require('querystring'); + +var object = {}; + +var connection = null; +var player_id = null; +var player_type = null; +var channel = false; +var canseek = false; +var playlist_id = 0; +var mem = null; +var mem_pos = null; +var mem_time = null; +var timer; + +//TODO Изменить виджеты Коди под новый формат +adapter.on('unload', function (callback){ + try { + adapter.log.info('cleaned everything up...'); + callback(); + } catch (e) { + callback(); + } +}); + +adapter.on('objectChange', function (id, obj){ + // Warning, obj can be null if it was deleted + adapter.log.info('objectChange ' + id + ' ' + JSON.stringify(obj)); +}); + +adapter.on('message', function (obj){ + if (typeof obj === 'object' && obj.message){ + if (obj.command === 'send'){ + adapter.log.debug('send command ' + JSON.stringify(obj)); + var _obj = obj.message; + var param = {'title': '', 'message': '', 'image': 'info', 'displaytime': 5000}; + if (typeof _obj.message !== "object"){ + param.message = _obj.message; + } + param.title = _obj.title || ''; + param.image = _obj.image || 'info'; + param.displaytime = _obj.delay || 5000; + sendCommand('GUI.ShowNotification', param); + if (obj.callback){ + adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback); + } + } + } +}); + +adapter.on('stateChange', function (id, state){ + // adapter.log.error('stateChange ' + id + ' ' + JSON.stringify(state)); + if (id == adapter.namespace + '.playing_time_total' && state.val !== mem_time){ + if (channel){ + mem_time = state.val; + GetCurrentItem(); + } + } + if (id == adapter.namespace + '.currentplay' && state.val !== mem){ + mem = state.val; + GetCurrentItem(); + setTimeout(function (){ + GetPlayList(); + }, 1000); + } + if (id == adapter.namespace + '.position' && state.val !== mem_pos){ + mem_pos = state.val; + GetCurrentItem(); + setTimeout(function (){ + GetPlayList(); + }, 1000); + } + if (state && !state.ack){ + adapter.log.info('stateChange ' + id + ' ' + JSON.stringify(state)); + var param = state.val; + var ids = id.split("."); + var method = ids[ids.length - 2].toString(); + if (isNumeric(method)){ + method = null; + } + ids = ids[ids.length - 1].toString(); + + ConstructorCmd(method, ids, param); + } +}); + +function ConstructorCmd(method, ids, param){ + adapter.log.debug('ConstructorCmd ' + method + ' - ' + ids + ' = ' + param); + if (method === 'input'){ + method = 'Input.' + ids; + param = []; + } else if (method === 'system'){ + method = 'System.' + ids; + param = []; + } else { + switch (ids) { + case "SwitchPVR": + method = null; + SwitchPVR(param, function (res){ + if(player_id){ + sendCommand('Player.Stop', {'playerid': player_id}, function (){ + sendCommand('Player.Open', res); + setTimeout(function (){ + sendCommand('GUI.SetFullscreen', {"fullscreen": true}); + }, 5000); + }); + } else { + sendCommand('Player.Open', res); + setTimeout(function (){ + sendCommand('GUI.SetFullscreen', {"fullscreen": true}); + }, 5000); + } + }); + break; + case "ShowNotif": + ShowNotification(param, function (res){ + method = 'GUI.ShowNotification'; + param = res; + }); + break; + case "zoom": + if (param >= 0 && param <= 10){ + method = 'Player.Zoom'; // + param = {"playerid": player_id, "zoom": parseInt(param)}; + } + break; + case "setsubtitle": + method = 'Player.SetSubtitle'; //"previous", "next", "off", "on + param = {"playerid": player_id, "subtitle": param}; + break; + case "seek": + if (param >= 0 && param <= 100 && canseek){ + method = 'Player.Seek'; //int 0-100 + param = {"playerid": player_id, "value": parseInt(param)} + } + break; + case "volume": + if (param >= 0 && param <= 100){ + method = 'Application.SetVolume'; //int 0-100 + param = parseInt(param); + } + break; + case "mute": + method = 'Application.SetMute'; //bool + param = bool(param); + break; + case "repeat": + method = 'Player.SetRepeat'; //off, on, all + param = bool(param); + if (param){ + param = 'all'; + } else { + param = 'off'; + } + param = {'playerid': player_id, "repeat": param}; + break; + case "shuffle": + method = 'Player.SetShuffle'; //bool + param = {'playerid': player_id, "shuffle": bool(param)}; + break; + case "play": + param = bool(param); + if (param){ + method = 'Input.ExecuteAction'; + param = 'play'; + } else { + method = 'Player.SetSpeed'; + param = {'playerid': player_id, 'speed': 0}; + } + + break; + case "playid": + method = null; + if (player_id !== 'undefined'){ + method = 'Player.GoTo'; + } else { + sendCommand('Input.ExecuteAction', 'play', function (){ //TODO + sendCommand('Player.GoTo', {"playerid": player_id, "to": param}, function (){ + }); + }); + } + param = {"playerid": player_id, "to": param}; + break; + case "next": + method = 'Input.ExecuteAction'; + param = 'skipnext'; + break; + case "previous": + method = 'Input.ExecuteAction'; + param = 'skipprevious'; + break; + case "pause": + method = 'Player.PlayPause'; + param = {'playerid': player_id, "play": "toggle"}; + break; + case "stop": + method = 'Player.Stop'; + param = {'playerid': player_id}; + break; + case "clear": + method = 'Playlist.Clear'; + param = {'playlistid': playlist_id}; + adapter.setState('playlist', {val: '[]', ack: true}); + break; + case "add": + var type; + method = null; + param = param.toString(); + playlist_id = 0; + type = {'playlistid': playlist_id, 'item': {'file': param}}; + if (param.slice(-1) === '\\' || param.slice(-1) === '/'){ + type = {'playlistid': playlist_id, 'item': {'directory': param}}; + } + sendCommand('Playlist.Add', type, function (){ + sendCommand('Player.Open', {'item': {'playlistid': playlist_id, 'position': 0}}, function (){ + sendCommand('GUI.SetFullscreen', {"fullscreen": true}); + }); + }); + break; + case "youtube": + method = null; + if (param){ + if (~param.indexOf('http')){ + /*param = param.replace('&', '?').replace('#', '?'); + param = querystring.parse(param, '?', '=');*/ + param = param.toString().split('='); + if (param.length > 2){ + param = param[param.length - 1]; + param = {'item': {'file': 'plugin://plugin.video.youtube/?path=/root/video&action=play_all&playlist=' + param.toString()}}; + } else if (param.length === 2){ + param = param[1]; + param = {'item': {'file': 'plugin://plugin.video.youtube/?path=/root/video&action=play_video&videoid=' + param.toString()}}; + } + } else { + if (param.toString().length > 12){ + param = {'item': {'file': 'plugin://plugin.video.youtube/?path=/root/video&action=play_all&playlist=' + param.toString()}}; + } else { + param = {'item': {'file': 'plugin://plugin.video.youtube/?path=/root/video&action=play_video&videoid=' + param.toString()}}; + } + } + } + sendCommand('Player.Open', param, function (){ + sendCommand('Input.ExecuteAction', { "action": "select" }, function (){ + sendCommand('Player.Open', {'item': {'playlistid': 1, 'position': 0}}, function (){ + sendCommand('GUI.SetFullscreen', {"fullscreen": true}); + }); + }); + }); + break; + case "ActivateWindow": + method = 'GUI.ActivateWindow'; + param = {"window": param}; + break; + case "ExecuteAction": + method = 'Input.ExecuteAction'; + param = { "action": param }; + break; + case "open": + var type2; + method = null; + param = param.toString(); + playlist_id = 0; + type2 = {'playlistid': playlist_id, 'item': {'file': param}}; + if (param.slice(-1) === '\\'){ + type2 = {'playlistid': playlist_id, 'item': {'directory': param}}; + } + sendCommand('Playlist.Clear', {'playlistid': playlist_id}, function (){ + sendCommand('Playlist.Add', type2, function (){ + sendCommand('Player.Open', {'item': {'playlistid': playlist_id, 'position': 0}}, function (){ + sendCommand('GUI.SetFullscreen', {"fullscreen": true}); + }); + }); + }); + break; + case "speed": + if (~[-32, -16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16, 32].indexOf(parseInt(param))){ + method = 'Player.SetSpeed'; + param = {'playerid': player_id, 'speed': parseInt(param)}; + } else if (param === 'increment' || param === 'decrement'){ + method = 'Player.SetSpeed'; + param = {'playerid': player_id, 'speed': param}; + } + break; + case "Directory": + method = null; + param = param.toString().replace("\\", "\\\\"); + GetDirectory(param); + break; + case "ScanVideoLibrary": + method = 'VideoLibrary.Scan'; + break; + case "ScanAudioLibrary": + method = 'AudioLibrary.Scan'; + break; + case "CleanVideoLibrary": + method = 'VideoLibrary.Clean'; + break; + case "CleanAudioLibrary": + method = 'AudioLibrary.Clean'; + break; + + default: + } + } + //adapter.log.error('stateChange ' + method + ' - ' + ids + ' = ' + JSON.stringify(param)); + sendCommand(method, param); +} + +function sendCommand(method, param, callback){ + if (method){ + getConnection(function (err, _connection){ + if (_connection){ + adapter.log.info('sending in KODI: ' + method + ' - ' + JSON.stringify(param)); + _connection.run(method, param).then(function (result){ + adapter.log.debug('response from KODI: ' + JSON.stringify(result)); + if (callback) callback(); + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }) + } + }); + } else { + adapter.log.debug('It does not specify commands or invalid value!'); + } +} + +adapter.on('ready', function (){ + main(); +}); + +function connect(){ + adapter.setState('info.connection', false, true); + adapter.log.debug('KODI connecting to: ' + adapter.config.ip + ':' + adapter.config.port); + getConnection(function (err, _connection){ + if (_connection){ + GetNameVersion(); + GetPlayerId(); + GetVolume(); + GetChannels(); + GetVideoLibrary(); + setTimeout(function (){ + GetSources(); + }, 10000); + connection_emit(); + } + }); +} +function connection_emit(){ + connection.notification('Player.OnPlay', function(res) { + adapter.setState('state', {val: 'play', ack: true}); + }); + connection.notification('Player.OnPause', function(res) { + adapter.setState('state', {val: 'pause', ack: true}); + }); + connection.notification('Player.OnStop', function(res) { + adapter.setState('state', {val: 'stop', ack: true}); + }); + connection.notification('Input.OnInputRequested', function(res) { + //adapter.log.error('OnInputRequested: ' + JSON.stringify(res)); + //{"data":{"title":"Строка поиска","type":"keyboard","value":""},"sender":"xbmc"} + //adapter.setState('OnInputRequested', {val: true, ack: true}); + }); + connection.notification('Playlist.OnClear', function(res) { + adapter.setState('playlist', {val: '[]', ack: true}); + }); + +} +function GetVolume(){ + if (connection){ + connection.run('Application.GetProperties', {'properties': ['volume', 'muted'] + }).then(function (res){ + adapter.log.debug('GetVolume: ' + JSON.stringify(res)); + adapter.setState('mute', {val: res.muted, ack: true}); + adapter.setState('volume', {val: res.volume, ack: true}); + connection.notification('Application.OnVolumeChanged', function(res) { + adapter.log.debug('OnVolumeChanged: ' + JSON.stringify(res)); + adapter.setState('mute', {val: res.data.muted, ack: true}); + adapter.setState('volume', {val: res.data.volume, ack: true}); + }); + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }) + } +} + +function main(){ + /***/ + //adapter.setState('Directory', false, true); + adapter.subscribeStates('*'); + connect(); +} + +function GetSources(root){ + var obj = { + 'video':[], + 'music':[], + 'pictures':[], + 'files':[], + 'programs':[] + }; + var count = 0; + if (connection){ + Object.keys(obj).forEach(function(key) { + connection.run('Files.GetSources', {"media": key}).then(function (res){ + //adapter.log.debug('GetSources: ' + JSON.stringify(res)); + if (res.limits.total > 0){ + for (var i = 0; i < res.limits.total; i++) { + obj[key][i] = res.sources[i]; + //adapter.log.debug('GetSources: ' + JSON.stringify(obj)); + } + } + count++; + if (count === 5){ + adapter.log.debug('GetSources: ' + JSON.stringify(obj)); + adapter.setState('Sources', {val: JSON.stringify(obj), ack: true}); + filemanager(root, obj); + } + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }) + }); + } +} + +function filemanager(root, obj){ + var browser = {}; + var files = []; + for (var key in obj){ + if (obj.hasOwnProperty(key)){ + if (obj[key].length > 0){ + for (var i = 0; i < obj[key].length; i++) { + var o = {}; + o.file = obj[key][i].file; + o.filetype = 'directory'; + files.push(o); + } + } + } + } + browser.files = files; + adapter.setState('Directory', {val: JSON.stringify(browser), ack: true}); +} +function GetDirectory(path){ + adapter.log.debug('GetDirectory path: ' + JSON.stringify(path)); + if (path !== '/'){ + if (connection){ + connection.run('Files.GetDirectory', { + "directory": path, + "media": "files", + "properties": ["title", "thumbnail", "fanart", "rating", "genre", "artist", "track", "season", "episode", "year", "duration", "album", "showtitle", "playcount", "file", "mimetype", "size", "lastmodified", "resume"], + "sort": {"method": "none", "order": "ascending"} + }).then(function (res){ + adapter.log.debug('GetDirectory: ' + JSON.stringify(res)); + adapter.setState('Directory', {val: JSON.stringify(res), ack: true}); + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }) + } + } else { + GetSources(true); + } +} +function GetVideoLibrary(){ + if (connection){ + connection.run('VideoLibrary.GetMovies', { + "properties": ["genre", "director", "trailer", "tagline", "plot", "plotoutline", "title", "originaltitle", "lastplayed", "runtime", "year", "playcount", "rating", "thumbnail", "file"], + "limits": {"start": 0}, + "sort": {"method": "dateadded", "ignorearticle": true} + }).then(function (res){ + adapter.log.debug('GetVideoLibrary: ' + JSON.stringify(res)); + adapter.setState('VideoLibrary', {val: JSON.stringify(res), ack: true}); + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }) + } +} +function GetPlayList(){ + if (connection){ + connection.run('Playlist.GetItems', { + "playlistid": playlist_id, + "properties": ["title", "thumbnail", "fanart", "rating", "genre", "artist", "track", "season", "episode", "year", "duration", "album", "showtitle", "playcount", "file"]/*,"limits":{"start":0,"end":750}*/ + }).then(function (res){ + var plst = res.items; + adapter.log.debug('GetPlayList: ' + JSON.stringify(plst)); + adapter.setState('playlist', {val: JSON.stringify(plst), ack: true}); + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }) + } +} +function GetCurrentItem(){ + if (connection){ + adapter.getStates('info.*', function (err, obj){ + for (var state in obj) { + if (state !== adapter.namespace + '.info.connection'){ + adapter.setState(state, {val: '', ack: true}); + } + } + connection.run('Player.GetItem', { + "playerid": player_id, + "properties": ["album", "albumartist", "artist", "director", "episode", "fanart", "file", "genre", "plot", "rating", "season", "showtitle", "studio", "imdbnumber", "tagline", "thumbnail", "title", "track", "writer", "year", "streamdetails", "originaltitle", "cast", "playcount"] + }).then(function (res){ + adapter.log.debug('GetCurrentItem: ' + JSON.stringify(res)); + res = res.item; + for (var key in res) { + if (typeof res[key] == 'object'){ + var obj = res[key]; + if (key === 'streamdetails'){ + for (var _key in obj) { + if (obj[_key].length > 0){ + var _obj = obj[_key][0]; + for (var __key in _obj) { + adapter.setState('info.' + _key + '_' + __key, {val: _obj[__key], ack: true}); + //adapter.log.debug('GetPlayList: ' +_key+'_'+__key+' = '+ JSON.stringify(_obj[__key]) +' - '+typeof _obj[__key]); + } + } else { + adapter.setState('info.' + _key, {val: obj[_key], ack: true}); + //adapter.log.debug('GetPlayList: ' +_key+' = '+ JSON.stringify(obj[_key]) +' - '+typeof obj[_key] +' length = '+obj[_key].length); + } + } + } else { + for (var id in obj) { //TODO + adapter.setState('info.' + key, {val: obj[id], ack: true}); + //adapter.log.debug('GetPlayList: ' +_key+'_'+__key+' = '+ JSON.stringify(_obj[__key]) +' - '+typeof _obj[__key]); + } + } + } else { + adapter.setState('info.' + key, {val: res[key], ack: true}); + //adapter.log.debug('GetPlayList: ' +key+' = '+ JSON.stringify(res[0][key]) +' - '+typeof res[0][key]); + } + //adapter.log.debug('GetPlayList: ' +key+' = '+ JSON.stringify(res[0][key]) +' - '+typeof res[0][key]); + } + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }); + }); + } +} +function GetNameVersion(){ + if (connection){ + var batch = connection.batch(); + var GetProperties = batch.Application.GetProperties({"properties": ["name", "version"]}); + var GetInfoBooleans = batch.XBMC.GetInfoBooleans({"booleans": ["System.Platform.Linux", "System.Platform.Linux.RaspberryPi", "System.Platform.Windows", "System.Platform.OSX", "System.Platform.IOS", "System.Platform.Darwin", "System.Platform.ATV2", "System.Platform.Android"]}); + var GetInfoLabels = batch.XBMC.GetInfoLabels({"labels": ["System.KernelVersion", "System.BuildVersion"]}); + batch.send(); + Promise.all([GetProperties, GetInfoBooleans, GetInfoLabels]).then(function (res){ + adapter.log.debug('GetNameVersion: ' + JSON.stringify(res[1])); + if (res[2]['System.KernelVersion'] === 'Ждите…' || res[2]['System.KernelVersion'] === 'Wait…' || res[2]['System.KernelVersion'] === 'Warten…'){ + setTimeout(function (){ + GetNameVersion(); + }, 10000); + } else { + adapter.setState('systeminfo.name', {val: res[0].name, ack: true}); + adapter.setState('systeminfo.version', { + val: res[0].version.major + '.' + res[0].version.minor, + ack: true + }); + for (var key in res[1]) { + if (res[1][key] === true){ + var system = key.split("."); + system = system[system.length - 1]; + adapter.setState('systeminfo.system', {val: system, ack: true}); + } + } + adapter.setState('systeminfo.kernel', {val: res[2]['System.KernelVersion'], ack: true}); + } + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }); + } +} +function GetChannels(){ + if (connection){ + var batch = connection.batch(); + var alltv = batch.PVR.GetChannels({ + "channelgroupid": "alltv", + "properties": ["channel", "channeltype", "hidden", "lastplayed", "locked", "thumbnail", "broadcastnow"] + }); + var allradio = batch.PVR.GetChannels({ + "channelgroupid": "allradio", + "properties": ["channel", "channeltype", "hidden", "lastplayed", "locked", "thumbnail", "broadcastnow"] + }); + batch.send(); + Promise.all([alltv, allradio]).then(function (res){ + if(res){ + adapter.setState('pvr.playlist_tv', {val: JSON.stringify(res[0]), ack: true}); + adapter.setState('pvr.playlist_radio', {val: JSON.stringify(res[1]), ack: true}); + } + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }); + } +} +function GetPlayerProperties(){ + if (connection && player_id !== undefined && player_id !== null){ + var batch = connection.batch(); + var Properties = batch.Player.GetProperties({ + "playerid": player_id, + "properties": ["audiostreams", "canseek", "currentaudiostream", "currentsubtitle", "partymode", "playlistid", "position", "repeat", "shuffled", "speed", "subtitleenabled", "subtitles", "time", "totaltime", "type"] + }); + var InfoLabels = batch.XBMC.GetInfoLabels({"labels": ["MusicPlayer.Codec", "MusicPlayer.SampleRate", "MusicPlayer.BitRate"]}); + var CurrentPlay = batch.Player.GetItem({"playerid": player_id}); + batch.send(); + Promise.all([Properties, InfoLabels, CurrentPlay]).then(function (res){ + //TODO сохранять только изменения + //pre = res[0]; + adapter.log.debug('Response GetPlayerProperties ' + JSON.stringify(res)); + var total = (res[0].totaltime.hours * 3600) + (res[0].totaltime.minutes * 60) + res[0].totaltime.seconds; + var cur = (res[0].time.hours * 3600) + (res[0].time.minutes * 60) + res[0].time.seconds; + playlist_id = res[0].playlistid; + adapter.setState('playing_time', { + val: time(res[0].time.hours, res[0].time.minutes, res[0].time.seconds), + ack: true + }); + adapter.setState('playing_time_total', { + val: time(res[0].totaltime.hours, res[0].totaltime.minutes, res[0].totaltime.seconds), + ack: true + }); + canseek = res[0].canseek; + adapter.setState('seek', {val: parseInt(cur * 100 / total), ack: true}); + adapter.setState('canseek', {val: res[0].canseek, ack: true}); + adapter.setState('repeat', {val: res[0].repeat, ack: true}); + adapter.setState('shuffle', {val: res[0].shuffled, ack: true}); + adapter.setState('speed', {val: res[0].speed, ack: true}); + adapter.setState('position', {val: res[0].position, ack: true}); + adapter.setState('playlistid', {val: res[0].playlistid, ack: true}); + adapter.setState('partymode', {val: res[0].partymode, ack: true}); + if (res[0].audiostreams.length > 0){ + adapter.setState('codec', {val: res[0].audiostreams[0].codec, ack: true}); + adapter.setState('bitrate', {val: res[0].audiostreams[0].bitrate, ack: true}); + adapter.setState('channels', {val: res[0].audiostreams[0].channels, ack: true}); + adapter.setState('language', {val: res[0].audiostreams[0].language, ack: true}); + adapter.setState('audiostream', {val: res[0].audiostreams[0].name, ack: true}); + } else { + adapter.setState('channels', {val: 2, ack: true}); + adapter.setState('audiostream', {val: '', ack: true}); + adapter.setState('language', {val: '', ack: true}); + adapter.setState('codec', {val: res[1]['MusicPlayer.Codec'], ack: true}); + adapter.setState('samplerate', {val: res[1]['MusicPlayer.SampleRate'], ack: true}); + adapter.setState('bitrate', {val: res[1]['MusicPlayer.BitRate'], ack: true}); + } + if (res[2].item.type == 'channel'){ + adapter.setState('type', {val: res[2].item.type, ack: true}); + channel = true; + } else { + adapter.setState('type', {val: res[0].type, ack: true}); + channel = false; + } + if (res[2].item.label.toString().length < 2){ + setTimeout(function (){ + adapter.getState(adapter.namespace + '.info.file', function (err, state){ + state = state.val.substring(state.val.lastIndexOf('/') + 1, state.val.length - 4); + adapter.setState('currentplay', {val: state, ack: true}); + }); + }, 1000); + } else { + adapter.setState('currentplay', {val: res[2].item.label, ack: true}); + } + + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }); + } +} + +function GetPlayerId(){ + clearTimeout(timer); + if (connection){ + connection.run('Player.GetActivePlayers').then(function (res){ + adapter.log.debug('Response GetPlayerId: ' + JSON.stringify(res)); + if (res.length > 0){ + player_id = res[0].playerid; + player_type = res[0].type; + GetPlayerProperties(); + } else { + player_id = null; + player_type = null; + } + timer = setTimeout(function (){ + GetPlayerId(); + }, 2000); + }, function (e){ + ErrProcessing(e); + }).catch(function (e){ + ErrProcessing(e); + }) + } else { + connect(); + } +} + +function getConnection(cb){ + if (connection){ + cb && cb(null, connection); + return; + } + clearTimeout(timer); + kodi(adapter.config.ip, adapter.config.port).then(function (_connection){ + connection = _connection; + _connection.on('error', function (err){ + adapter.log.warn('Error: ' + err); + }).on('close', function (){ + if (connection){ + console.log('Connection closed'); + if (connection.socket) connection.socket.close(); + connection = null; + setTimeout(connect, 5000); + } + }); + adapter.log.info('KODI connected'); + adapter.setState('info.connection', true, true); + GetPlayerId(); + cb && cb(null, connection); + }, function (error){ + adapter.log.debug(error); + adapter.setState('info.connection', false, true); + setTimeout(connect, 5000, cb); + }).catch(function (error){ + if (error.stack){ + adapter.log.error(error.stack); + } else { + adapter.log.error(error); + } + adapter.setState('info.connection', false, true); + setTimeout(connect, 5000, cb); + }); +} +function time(hour, min, sec){ + var time = ''; + hour = (parseInt(hour) < 10 ? '0' : '') + hour; + min = (parseInt(min) < 10 ? '0' : '') + min; + sec = (parseInt(sec) < 10 ? '0' : '') + sec; + if (parseInt(hour) === 0){ + time = min + ':' + sec; + } else { + time = hour + ':' + min + ':' + sec; + } + return time; +} +function SwitchPVR(val, callback){ + adapter.getState(adapter.namespace + '.pvr.playlist_tv', function (err, state){ + if (state){ + var Break = {}; + val = val.toString().toLowerCase(); + var obj = JSON.parse(state.val); + try { + obj.channels.forEach(function (item, i, a){ + var channel = item.label.toString().toLowerCase(); + var pos = channel.indexOf(val); + if (pos === 0){ //TODO + //adapter.log.debug('PVR.GetChannelsIPTV: '+item.channelid); + callback({"item": {"channelid": item.channelid}}); + throw Break; + } + }); + } catch (e) { + if (e !== Break) throw e; + } + } + }); +} +function ShowNotification(param, callback){ + var title = ''; + var message = ''; + var displaytime = 5000; + var img = ['info', 'warning', 'error']; + var image = 'info'; + var c = (';' + param).split(';'); + var flag = false; + c.forEach(function (item, i, arr){ + if (!isNaN(item)){ + var num = parseInt(item); + if (num >= 1500 && num <= 30000){ + displaytime = num; + } + else if (num >= 0 && num <= 2){ + image = img[num]; + } + } + if (isNaN(arr[i]) && isNaN(arr[i + 1]) && flag === false){ + if (arr[i] && arr[i + 1]){ + title = arr[i].toString(); + message = arr[i + 1].toString(); + flag = true; + } + } + }); + if (!flag){ + c.forEach(function (item, i, arr){ + if (isNaN(arr[i]) && arr[i]){ + message = arr[i].toString(); + } + }); + } + callback({'title': title, 'message': message, 'image': image, 'displaytime': displaytime}); +} +function bool(s){ + //s = s.toString(); + if (s === 1 || s === '1' || s === 'true' || s === true){ + return true; + } else { + return false; + } +} +function ErrProcessing(error){ + adapter.log.error(error); + connection = null; + getConnection(); +} +function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..a9b4825 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,83 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +let controllerDir; +let appName; + +/** + * returns application name + * + * The name of the application can be different and this function finds it out. + * + * @returns {string} + */ + function getAppName() { + const parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 2].split('.')[0]; +} + +/** + * looks for js-controller home folder + * + * @param {boolean} isInstall + * @returns {string} + */ +function getControllerDir(isInstall) { + // Find the js-controller location + const possibilities = [ + 'yunkong2.js-controller', + 'yunkong2.js-controller', + ]; + /** @type {string} */ + let controllerPath; + for (const pkg of possibilities) { + try { + const possiblePath = require.resolve(pkg); + if (fs.existsSync(possiblePath)) { + controllerPath = possiblePath; + break; + } + } catch (e) { /* not found */ } + } + if (controllerPath == null) { + if (!isInstall) { + console.log('Cannot find js-controller'); + process.exit(10); + } else { + process.exit(); + } + } + // we found the controller + return path.dirname(controllerPath); +} + +/** + * reads controller base settings + * + * @alias getConfig + * @returns {object} + */ + function getConfig() { + let configPath; + if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', appName + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else { + throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json'); + } +} +appName = getAppName(); +controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1); +const adapter = require(path.join(controllerDir, 'lib/adapter.js')); + +exports.controllerDir = controllerDir; +exports.getConfig = getConfig; +exports.Adapter = adapter; +exports.appName = appName; diff --git a/package.json b/package.json new file mode 100644 index 0000000..970d571 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "yunkong2.kodi", + "version": "1.0.0", + "description": "yunkong2.kodi Adapter", + "author": { + "name": "instalator", + "email": "vvvalt@mail.ru" + }, + "contributors": [ + { + "name": "instalator", + "email": "vvvalt@mail.ru" + } + ], + "homepage": "https://github.com/instalator/yunkong2.kodi", + "license": "MIT", + "keywords": [ + "yunkong2", + "kodi xbmc", + "Smart Home", + "home automation" + ], + "repository": { + "type": "git", + "url": "https://github.com/instalator/yunkong2.kodi" + }, + "dependencies": { + "kodi-ws": "https://github.com/instalator/kodi-ws/tarball/master/" + }, + "devDependencies": { + "grunt": "^1.0.1", + "grunt-replace": "^1.0.1", + "grunt-contrib-jshint": "^1.1.0", + "grunt-jscs": "^3.0.1", + "grunt-http": "^2.2.0", + "grunt-contrib-clean": "^1.1.0", + "grunt-contrib-compress": "^1.4.3", + "grunt-contrib-copy": "^1.0.0", + "grunt-exec": "^3.0.0", + "mocha": "^4.1.0", + "chai": "^4.1.2" + }, + "main": "kodi.js", + "scripts": { + "test": "node node_modules/mocha/bin/mocha --exit" + }, + "bugs": { + "url": "https://github.com/instalator/yunkong2.kodi/issues" + }, + "readmeFilename": "README.md" +} \ No newline at end of file diff --git a/test/lib/setup.js b/test/lib/setup.js new file mode 100644 index 0000000..09cc5f3 --- /dev/null +++ b/test/lib/setup.js @@ -0,0 +1,728 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +// check if tmp directory exists +var fs = require('fs'); +var path = require('path'); +var child_process = require('child_process'); +var rootDir = path.normalize(__dirname + '/../../'); +var pkg = require(rootDir + 'package.json'); +var debug = typeof v8debug === 'object'; +pkg.main = pkg.main || 'main.js'; + +var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/'); +adapterName = adapterName[adapterName.length - 2]; +var adapterStarted = false; + +function getAppName() { + var parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 3].split('.')[0]; +} + +var appName = getAppName().toLowerCase(); + +var objects; +var states; + +var pid = null; + +function copyFileSync(source, target) { + + var targetFile = target; + + //if target is a directory a new file with the same name will be created + if (fs.existsSync(target)) { + if ( fs.lstatSync( target ).isDirectory() ) { + targetFile = path.join(target, path.basename(source)); + } + } + + try { + fs.writeFileSync(targetFile, fs.readFileSync(source)); + } + catch (err) { + console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)"); + } +} + +function copyFolderRecursiveSync(source, target, ignore) { + var files = []; + + var base = path.basename(source); + if (base === adapterName) { + base = pkg.name; + } + //check if folder needs to be created or integrated + var targetFolder = path.join(target, base); + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + //copy + if (fs.lstatSync(source).isDirectory()) { + files = fs.readdirSync(source); + files.forEach(function (file) { + if (ignore && ignore.indexOf(file) !== -1) { + return; + } + + var curSource = path.join(source, file); + var curTarget = path.join(targetFolder, file); + if (fs.lstatSync(curSource).isDirectory()) { + // ignore grunt files + if (file.indexOf('grunt') !== -1) return; + if (file === 'chai') return; + if (file === 'mocha') return; + copyFolderRecursiveSync(curSource, targetFolder, ignore); + } else { + copyFileSync(curSource, curTarget); + } + }); + } +} + +if (!fs.existsSync(rootDir + 'tmp')) { + fs.mkdirSync(rootDir + 'tmp'); +} + +function storeOriginalFiles() { + console.log('Store original files...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) { + objects['system.adapter.admin.0'].common.enabled = false; + } + if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) { + objects['system.adapter.admin.1'].common.enabled = false; + } + + fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects)); + try { + f = fs.readFileSync(dataDir + 'states.json'); + fs.writeFileSync(dataDir + 'states.json.original', f); + } + catch (err) { + console.log('no states.json found - ignore'); + } +} + +function restoreOriginalFiles() { + console.log('restoreOriginalFiles...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json.original'); + fs.writeFileSync(dataDir + 'objects.json', f); + try { + f = fs.readFileSync(dataDir + 'states.json.original'); + fs.writeFileSync(dataDir + 'states.json', f); + } + catch (err) { + console.log('no states.json.original found - ignore'); + } + +} + +function checkIsAdapterInstalled(cb, counter, customName) { + customName = customName || pkg.name.split('.').pop(); + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + console.log('checkIsAdapterInstalled...'); + + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.' + customName + '.0']) { + console.log('checkIsAdapterInstalled: ready!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } else { + console.warn('checkIsAdapterInstalled: still not ready'); + } + } catch (err) { + + } + + if (counter > 20) { + console.error('checkIsAdapterInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsAdapterInstalled: wait...'); + setTimeout(function() { + checkIsAdapterInstalled(cb, counter + 1); + }, 1000); + } +} + +function checkIsControllerInstalled(cb, counter) { + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + console.log('checkIsControllerInstalled...'); + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0']) { + console.log('checkIsControllerInstalled: installed!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } + } catch (err) { + + } + + if (counter > 20) { + console.log('checkIsControllerInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsControllerInstalled: wait...'); + setTimeout(function() { + checkIsControllerInstalled(cb, counter + 1); + }, 1000); + } +} + +function installAdapter(customName, cb) { + if (typeof customName === 'function') { + cb = customName; + customName = null; + } + customName = customName || pkg.name.split('.').pop(); + console.log('Install adapter...'); + var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js'; + // make first install + if (debug) { + child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + } else { + // add controller + var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + + waitForEnd(_pid, function () { + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + }); + } +} + +function waitForEnd(_pid, cb) { + if (!_pid) { + cb(-1, -1); + return; + } + _pid.on('exit', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); + _pid.on('close', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); +} + +function installJsController(cb) { + console.log('installJsController...'); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || + !fs.existsSync(rootDir + 'tmp/' + appName + '-data')) { + // try to detect appName.js-controller in node_modules/appName.js-controller + // travis CI installs js-controller into node_modules + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"'); + // copy all + // stop controller + console.log('Stop controller if running...'); + var _pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js stop', { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // copy all files into + if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp'); + if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules'); + + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){ + console.log('Copy js-controller...'); + copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/'); + } + + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js setup first --console', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + __pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + waitForEnd(__pid, function () { + checkIsControllerInstalled(function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + console.log('Setup finished.'); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }); + } else { + // check if port 9000 is free, else admin adapter will be added to running instance + var client = new require('net').Socket(); + client.connect(9000, '127.0.0.1', function() { + console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.'); + process.exit(0); + }); + + setTimeout(function () { + client.destroy(); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => install from git'); + + child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', { + cwd: rootDir + 'tmp/', + stdio: [0, 1, 2] + }); + } else { + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + child_process.exec('node ' + appName + '.js setup first', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + child_process.fork(appName + '.js', ['setup', 'first'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + } + + // let npm install admin and run setup + checkIsControllerInstalled(function () { + var _pid; + + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }, 1000); + } + } else { + setTimeout(function () { + console.log('installJsController: js-controller installed'); + if (cb) cb(false); + }, 0); + } +} + +function copyAdapterToController() { + console.log('Copy adapter...'); + // Copy adapter to tmp/node_modules/appName.adapter + copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']); + console.log('Adapter copied.'); +} + +function clearControllerLog() { + var dirPath = rootDir + 'tmp/log'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear controller log...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Controller log cleared'); + } catch (err) { + console.error('cannot clear log: ' + err); + } + } +} + +function clearDB() { + var dirPath = rootDir + 'tmp/yunkong2-data/sqlite'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear sqlite DB...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Clear sqlite DB'); + } catch (err) { + console.error('cannot clear DB: ' + err); + } + } +} + +function setupController(cb) { + installJsController(function (isInited) { + clearControllerLog(); + clearDB(); + + if (!isInited) { + restoreOriginalFiles(); + copyAdapterToController(); + } + // read system.config object + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var objs; + try { + objs = fs.readFileSync(dataDir + 'objects.json'); + objs = JSON.parse(objs); + } + catch (e) { + console.log('ERROR reading/parsing system configuration. Ignore'); + objs = {'system.config': {}}; + } + if (!objs || !objs['system.config']) { + objs = {'system.config': {}}; + } + + if (cb) cb(objs['system.config']); + }); +} + +function startAdapter(objects, states, callback) { + if (adapterStarted) { + console.log('Adapter already started ...'); + if (callback) callback(objects, states); + return; + } + adapterStarted = true; + console.log('startAdapter...'); + if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) { + try { + if (debug) { + // start controller + pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + } else { + // start controller + pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + } + } catch (error) { + console.error(JSON.stringify(error)); + } + } else { + console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main); + } + if (callback) callback(objects, states); +} + +function startController(isStartAdapter, onObjectChange, onStateChange, callback) { + if (typeof isStartAdapter === 'function') { + callback = onStateChange; + onStateChange = onObjectChange; + onObjectChange = isStartAdapter; + isStartAdapter = true; + } + + if (onStateChange === undefined) { + callback = onObjectChange; + onObjectChange = undefined; + } + + if (pid) { + console.error('Controller is already started!'); + } else { + console.log('startController...'); + adapterStarted = false; + var isObjectConnected; + var isStatesConnected; + + var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer'); + objects = new Objects({ + connection: { + "type" : "file", + "host" : "127.0.0.1", + "port" : 19001, + "user" : "", + "pass" : "", + "noFileCache": false, + "connectTimeout": 2000 + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.warn(msg); + }, + error: function (msg) { + console.error(msg); + } + }, + connected: function () { + isObjectConnected = true; + if (isStatesConnected) { + console.log('startController: started!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onObjectChange + }); + + // Just open in memory DB itself + var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer'); + states = new States({ + connection: { + type: 'file', + host: '127.0.0.1', + port: 19000, + options: { + auth_pass: null, + retry_max_delay: 15000 + } + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.log(msg); + }, + error: function (msg) { + console.log(msg); + } + }, + connected: function () { + isStatesConnected = true; + if (isObjectConnected) { + console.log('startController: started!!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onStateChange + }); + } +} + +function stopAdapter(cb) { + if (!pid) { + console.error('Controller is not running!'); + if (cb) { + setTimeout(function () { + cb(false); + }, 0); + } + } else { + adapterStarted = false; + pid.on('exit', function (code, signal) { + if (pid) { + console.log('child process terminated due to receipt of signal ' + signal); + if (cb) cb(); + pid = null; + } + }); + + pid.on('close', function (code, signal) { + if (pid) { + if (cb) cb(); + pid = null; + } + }); + + pid.kill('SIGTERM'); + } +} + +function _stopController() { + if (objects) { + objects.destroy(); + objects = null; + } + if (states) { + states.destroy(); + states = null; + } +} + +function stopController(cb) { + var timeout; + if (objects) { + console.log('Set system.adapter.' + pkg.name + '.0'); + objects.setObject('system.adapter.' + pkg.name + '.0', { + common:{ + enabled: false + } + }); + } + + stopAdapter(function () { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + + _stopController(); + + if (cb) { + cb(true); + cb = null; + } + }); + + timeout = setTimeout(function () { + timeout = null; + console.log('child process NOT terminated'); + + _stopController(); + + if (cb) { + cb(false); + cb = null; + } + pid = null; + }, 5000); +} + +// Setup the adapter +function setAdapterConfig(common, native, instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + if (common) objects[id].common = common; + if (native) objects[id].native = native; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects)); +} + +// Read config of the adapter +function getAdapterConfig(instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + return objects[id]; +} + +if (typeof module !== undefined && module.parent) { + module.exports.getAdapterConfig = getAdapterConfig; + module.exports.setAdapterConfig = setAdapterConfig; + module.exports.startController = startController; + module.exports.stopController = stopController; + module.exports.setupController = setupController; + module.exports.stopAdapter = stopAdapter; + module.exports.startAdapter = startAdapter; + module.exports.installAdapter = installAdapter; + module.exports.appName = appName; + module.exports.adapterName = adapterName; + module.exports.adapterStarted = adapterStarted; +} diff --git a/test/testAdapter.js b/test/testAdapter.js new file mode 100644 index 0000000..ae9c289 --- /dev/null +++ b/test/testAdapter.js @@ -0,0 +1,140 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +var expect = require('chai').expect; +var setup = require(__dirname + '/lib/setup'); + +var objects = null; +var states = null; +var onStateChanged = null; +var onObjectChanged = null; +var sendToID = 1; + +var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1); + +function checkConnectionOfAdapter(cb, counter) { + counter = counter || 0; + console.log('Try check #' + counter); + if (counter > 30) { + if (cb) cb('Cannot check connection'); + return; + } + + states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) { + if (err) console.error(err); + if (state && state.val) { + if (cb) cb(); + } else { + setTimeout(function () { + checkConnectionOfAdapter(cb, counter + 1); + }, 1000); + } + }); +} + +function checkValueOfState(id, value, cb, counter) { + counter = counter || 0; + if (counter > 20) { + if (cb) cb('Cannot check value Of State ' + id); + return; + } + + states.getState(id, function (err, state) { + if (err) console.error(err); + if (value === null && !state) { + if (cb) cb(); + } else + if (state && (value === undefined || state.val === value)) { + if (cb) cb(); + } else { + setTimeout(function () { + checkValueOfState(id, value, cb, counter + 1); + }, 500); + } + }); +} + +function sendTo(target, command, message, callback) { + onStateChanged = function (id, state) { + if (id === 'messagebox.system.adapter.test.0') { + callback(state.message); + } + }; + + states.pushMessage('system.adapter.' + target, { + command: command, + message: message, + from: 'system.adapter.test.0', + callback: { + message: message, + id: sendToID++, + ack: false, + time: (new Date()).getTime() + } + }); +} + +describe('Test ' + adapterShortName + ' adapter', function() { + before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) { + this.timeout(600000); // because of first install from npm + + setup.setupController(function () { + var config = setup.getAdapterConfig(); + // enable adapter + config.common.enabled = true; + config.common.loglevel = 'debug'; + + //config.native.dbtype = 'sqlite'; + + setup.setAdapterConfig(config.common, config.native); + + setup.startController(true, function(id, obj) {}, function (id, state) { + if (onStateChanged) onStateChanged(id, state); + }, + function (_objects, _states) { + objects = _objects; + states = _states; + _done(); + }); + }); + }); + +/* + ENABLE THIS WHEN ADAPTER RUNS IN DEAMON MODE TO CHECK THAT IT HAS STARTED SUCCESSFULLY +*/ + it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) { + this.timeout(60000); + checkConnectionOfAdapter(function (res) { + if (res) console.log(res); + expect(res).not.to.be.equal('Cannot check connection'); + objects.setObject('system.adapter.test.0', { + common: { + + }, + type: 'instance' + }, + function () { + states.subscribeMessage('system.adapter.test.0'); + done(); + }); + }); + }); +/**/ + +/* + PUT YOUR OWN TESTS HERE USING + it('Testname', function ( done) { + ... + }); + + You can also use "sendTo" method to send messages to the started adapter +*/ + + after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) { + this.timeout(10000); + + setup.stopController(function (normalTerminated) { + console.log('Adapter normal terminated: ' + normalTerminated); + done(); + }); + }); +}); diff --git a/test/testPackageFiles.js b/test/testPackageFiles.js new file mode 100644 index 0000000..d0759c0 --- /dev/null +++ b/test/testPackageFiles.js @@ -0,0 +1,91 @@ +/* jshint -W097 */ +/* jshint strict:false */ +/* jslint node: true */ +/* jshint expr: true */ +var expect = require('chai').expect; +var fs = require('fs'); + +describe('Test package.json and io-package.json', function() { + it('Test package files', function (done) { + console.log(); + + var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8'); + var ioPackage = JSON.parse(fileContentIOPackage); + + var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8'); + var npmPackage = JSON.parse(fileContentNPMPackage); + + expect(ioPackage).to.be.an('object'); + expect(npmPackage).to.be.an('object'); + + expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; + expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; + + expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); + + if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) { + console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); + console.log(); + } + + expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist; + expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist; + + if (ioPackage.common.name.indexOf('template') !== 0) { + if (Array.isArray(ioPackage.common.authors)) { + expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); + if (ioPackage.common.authors.length === 1) { + expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); + console.log(); + } + expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; + if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') { + console.log('WARNING: titleLang is not existing in io-package.json. Please add'); + console.log(); + } + if ( + ioPackage.common.title.indexOf('yunkong2') !== -1 || + ioPackage.common.title.indexOf('yunkong2') !== -1 || + ioPackage.common.title.indexOf('adapter') !== -1 || + ioPackage.common.title.indexOf('Adapter') !== -1 + ) { + console.log('WARNING: title contains Adapter or yunkong2. It is clear anyway, that it is adapter for yunkong2.'); + console.log(); + } + + if (ioPackage.common.name.indexOf('vis-') !== 0) { + if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) { + console.log('WARNING: Admin3 support is missing! Please add it'); + console.log(); + } + if (ioPackage.common.materialize) { + expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; + } + } + + var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE'); + var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8'); + if (fileContentReadme.indexOf('## Changelog') === -1) { + console.log('Warning: The README.md should have a section ## Changelog'); + console.log(); + } + expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; + if (!licenseFileExists) { + console.log('Warning: The License should also exist as LICENSE file'); + console.log(); + } + if (fileContentReadme.indexOf('## License') === -1) { + console.log('Warning: The README.md should also have a section ## License to be shown in Admin3'); + console.log(); + } + done(); + }); +}); diff --git a/widgets/kodi.html b/widgets/kodi.html new file mode 100644 index 0000000..c992491 --- /dev/null +++ b/widgets/kodi.html @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/widgets/kodi/css/style.css b/widgets/kodi/css/style.css new file mode 100644 index 0000000..b5f0c7a --- /dev/null +++ b/widgets/kodi/css/style.css @@ -0,0 +1,108 @@ +.yunkong2.kodi-class { + font-style: italic; +} + + +.kodicover, .cover{ + width: 100%; + height: 100%; + text-align:center; + background-position: center top; + background-repeat: no-repeat; + background-size: cover; + margin: 0; + padding: 0; +} + +.cover.adef { +background-image: url("../img/defaultplaylist.png") !important; +background-position: center top; +background-repeat: no-repeat; +background-size: cover; +width:100%; +height:100%; +margin: 0; +padding: 0; +} +.cover.vdef { +background-image: url("../img/defaultvideo.png") !important; +background-position: center top; +background-repeat: no-repeat; +background-size: cover; +width:100%; +height:100%; +margin: 0; +padding: 0; +} + + +#progressbar { + width: 100%; + height: 100%; +} + +#kodiplaylist { + height: 100%; + width: 100%; + overflow-x: hidden; +} +#kodiplaylist li:hover, #kodiplaylist li:nth-child(even):hover { background-color:#00bd9b; color:#fff; } +#kodiplaylist li.active{ + background-color:#00bd9b; + color:#fff; +} +#kodiplaylist li img{ + box-shadow: 0 0 5px 0px #FFF, 0 0 0 1px #000, 0 0 0 1px #484848; + border-radius: 5px; +} +#kodiplaylist ul { + margin: 0; + padding-left: 5px; + clear: both; + cursor: pointer; +} +#kodicontrols, .kodiinfo{ + height: 100%; + width: 100%; + padding: 0px; + margin: 0px; +} +#kodicontrols li { + float:left; + display:inline-block; + width:50px; + height: 50px; + text-align:center; +} +.kodiinfo li{ + width: 100%; + height: 100%; + text-align:center; + background-position: center top; + background-repeat: no-repeat; + background-size: cover; +} +.prev { background: url("../img/btn/btn_sprite.png") -48px -48px } +.prev:hover { background: url("../img/btn/btn_sprite.png") -48px -0px;} +.rewind { background: url("../img/btn/btn_sprite.png") -144px 48px;} +.rewind:hover { background: url("../img/btn/btn_sprite.png") -144px -0px;} +.playpause { background: url("../img/btn/btn_sprite.png") -192px -48px } +.playpause:hover { background: url("../img/btn/btn_sprite.png") -192px -0px;} +.playpause.play { background: url("../img/btn/btn_sprite.png") -240px -48px;} +.playpause.play:hover { background: url("../img/btn/btn_sprite.png") -240px -0px} +.stop { background: url("../img/btn/btn_sprite.png") -528px 48px;} +.stop:hover { background: url("../img/btn/btn_sprite.png") -528px -0px;} +.forward { background: url("../img/btn/btn_sprite.png") -96px -48px} +.forward:hover { background: url("../img/btn/btn_sprite.png") -96px -0px} +.next { background: url("../img/btn/btn_sprite.png") -0px -48px} +.next:hover { background: url("../img/btn/btn_sprite.png") -0px -0px} +.repeat { background: url("../img/btn/btn_sprite.png") -480px -48px;} +.repeat:hover { background: url("../img/btn/btn_sprite.png") -480px -0px;} +.repeat.one { background: url("../img/btn/btn_sprite.png") -432px -48px;} +.repeat.one:hover { background: url("../img/btn/btn_sprite.png") -432px -0px;} +.repeat.all { background: url("../img/btn/btn_sprite.png") -384px -48px;} +.repeat.all:hover { background: url("../img/btn/btn_sprite.png") -384px -0px;} +.shuffle { background: url("../img/btn/btn_sprite.png") -336px -48px;} +.shuffle:hover { background: url("../img/btn/btn_sprite.png") -336px -0px} +.shuffle.on { background: url("../img/btn/btn_sprite.png") -288px -48px;} +.shuffle.on:hover { background: url("../img/btn/btn_sprite.png") -288px -0px;} diff --git a/widgets/kodi/img/aspectratio/1.33.png b/widgets/kodi/img/aspectratio/1.33.png new file mode 100644 index 0000000..b9c7488 Binary files /dev/null and b/widgets/kodi/img/aspectratio/1.33.png differ diff --git a/widgets/kodi/img/aspectratio/1.37.png b/widgets/kodi/img/aspectratio/1.37.png new file mode 100644 index 0000000..c0bcb47 Binary files /dev/null and b/widgets/kodi/img/aspectratio/1.37.png differ diff --git a/widgets/kodi/img/aspectratio/1.66.png b/widgets/kodi/img/aspectratio/1.66.png new file mode 100644 index 0000000..0a340e2 Binary files /dev/null and b/widgets/kodi/img/aspectratio/1.66.png differ diff --git a/widgets/kodi/img/aspectratio/1.78.png b/widgets/kodi/img/aspectratio/1.78.png new file mode 100644 index 0000000..2392c15 Binary files /dev/null and b/widgets/kodi/img/aspectratio/1.78.png differ diff --git a/widgets/kodi/img/aspectratio/1.85.png b/widgets/kodi/img/aspectratio/1.85.png new file mode 100644 index 0000000..c12f0c5 Binary files /dev/null and b/widgets/kodi/img/aspectratio/1.85.png differ diff --git a/widgets/kodi/img/aspectratio/2.20.png b/widgets/kodi/img/aspectratio/2.20.png new file mode 100644 index 0000000..5867cc1 Binary files /dev/null and b/widgets/kodi/img/aspectratio/2.20.png differ diff --git a/widgets/kodi/img/aspectratio/2.35.png b/widgets/kodi/img/aspectratio/2.35.png new file mode 100644 index 0000000..104dba7 Binary files /dev/null and b/widgets/kodi/img/aspectratio/2.35.png differ diff --git a/widgets/kodi/img/aspectratio/2.40.png b/widgets/kodi/img/aspectratio/2.40.png new file mode 100644 index 0000000..dbc8d3c Binary files /dev/null and b/widgets/kodi/img/aspectratio/2.40.png differ diff --git a/widgets/kodi/img/aspectratio/2.55.png b/widgets/kodi/img/aspectratio/2.55.png new file mode 100644 index 0000000..b1fc01c Binary files /dev/null and b/widgets/kodi/img/aspectratio/2.55.png differ diff --git a/widgets/kodi/img/aspectratio/2.76.png b/widgets/kodi/img/aspectratio/2.76.png new file mode 100644 index 0000000..4f90ee8 Binary files /dev/null and b/widgets/kodi/img/aspectratio/2.76.png differ diff --git a/widgets/kodi/img/audio/0.png b/widgets/kodi/img/audio/0.png new file mode 100644 index 0000000..be2eb52 Binary files /dev/null and b/widgets/kodi/img/audio/0.png differ diff --git a/widgets/kodi/img/audio/1.png b/widgets/kodi/img/audio/1.png new file mode 100644 index 0000000..b49eb07 Binary files /dev/null and b/widgets/kodi/img/audio/1.png differ diff --git a/widgets/kodi/img/audio/10.png b/widgets/kodi/img/audio/10.png new file mode 100644 index 0000000..6b9793a Binary files /dev/null and b/widgets/kodi/img/audio/10.png differ diff --git a/widgets/kodi/img/audio/2.png b/widgets/kodi/img/audio/2.png new file mode 100644 index 0000000..5a37ffa Binary files /dev/null and b/widgets/kodi/img/audio/2.png differ diff --git a/widgets/kodi/img/audio/3.png b/widgets/kodi/img/audio/3.png new file mode 100644 index 0000000..e85737e Binary files /dev/null and b/widgets/kodi/img/audio/3.png differ diff --git a/widgets/kodi/img/audio/4.png b/widgets/kodi/img/audio/4.png new file mode 100644 index 0000000..f1e2b7d Binary files /dev/null and b/widgets/kodi/img/audio/4.png differ diff --git a/widgets/kodi/img/audio/5.png b/widgets/kodi/img/audio/5.png new file mode 100644 index 0000000..78b50b0 Binary files /dev/null and b/widgets/kodi/img/audio/5.png differ diff --git a/widgets/kodi/img/audio/6.png b/widgets/kodi/img/audio/6.png new file mode 100644 index 0000000..96e5c6f Binary files /dev/null and b/widgets/kodi/img/audio/6.png differ diff --git a/widgets/kodi/img/audio/7.png b/widgets/kodi/img/audio/7.png new file mode 100644 index 0000000..be47cbc Binary files /dev/null and b/widgets/kodi/img/audio/7.png differ diff --git a/widgets/kodi/img/audio/8.png b/widgets/kodi/img/audio/8.png new file mode 100644 index 0000000..e9f625f Binary files /dev/null and b/widgets/kodi/img/audio/8.png differ diff --git a/widgets/kodi/img/audio/aac.png b/widgets/kodi/img/audio/aac.png new file mode 100644 index 0000000..f63bceb Binary files /dev/null and b/widgets/kodi/img/audio/aac.png differ diff --git a/widgets/kodi/img/audio/ac3.png b/widgets/kodi/img/audio/ac3.png new file mode 100644 index 0000000..cb74714 Binary files /dev/null and b/widgets/kodi/img/audio/ac3.png differ diff --git a/widgets/kodi/img/audio/aif.png b/widgets/kodi/img/audio/aif.png new file mode 100644 index 0000000..1a9acb4 Binary files /dev/null and b/widgets/kodi/img/audio/aif.png differ diff --git a/widgets/kodi/img/audio/aifc.png b/widgets/kodi/img/audio/aifc.png new file mode 100644 index 0000000..0e8cf6b Binary files /dev/null and b/widgets/kodi/img/audio/aifc.png differ diff --git a/widgets/kodi/img/audio/aiff.png b/widgets/kodi/img/audio/aiff.png new file mode 100644 index 0000000..1a9acb4 Binary files /dev/null and b/widgets/kodi/img/audio/aiff.png differ diff --git a/widgets/kodi/img/audio/alac.png b/widgets/kodi/img/audio/alac.png new file mode 100644 index 0000000..fe7381a Binary files /dev/null and b/widgets/kodi/img/audio/alac.png differ diff --git a/widgets/kodi/img/audio/ape.png b/widgets/kodi/img/audio/ape.png new file mode 100644 index 0000000..22e9533 Binary files /dev/null and b/widgets/kodi/img/audio/ape.png differ diff --git a/widgets/kodi/img/audio/avc.png b/widgets/kodi/img/audio/avc.png new file mode 100644 index 0000000..494b392 Binary files /dev/null and b/widgets/kodi/img/audio/avc.png differ diff --git a/widgets/kodi/img/audio/cdda.png b/widgets/kodi/img/audio/cdda.png new file mode 100644 index 0000000..c00f8ce Binary files /dev/null and b/widgets/kodi/img/audio/cdda.png differ diff --git a/widgets/kodi/img/audio/dca.png b/widgets/kodi/img/audio/dca.png new file mode 100644 index 0000000..816e53a Binary files /dev/null and b/widgets/kodi/img/audio/dca.png differ diff --git a/widgets/kodi/img/audio/dts.png b/widgets/kodi/img/audio/dts.png new file mode 100644 index 0000000..816e53a Binary files /dev/null and b/widgets/kodi/img/audio/dts.png differ diff --git a/widgets/kodi/img/audio/dtshd_hra.png b/widgets/kodi/img/audio/dtshd_hra.png new file mode 100644 index 0000000..2d51dd9 Binary files /dev/null and b/widgets/kodi/img/audio/dtshd_hra.png differ diff --git a/widgets/kodi/img/audio/dtshd_ma.png b/widgets/kodi/img/audio/dtshd_ma.png new file mode 100644 index 0000000..33793f4 Binary files /dev/null and b/widgets/kodi/img/audio/dtshd_ma.png differ diff --git a/widgets/kodi/img/audio/eac3.png b/widgets/kodi/img/audio/eac3.png new file mode 100644 index 0000000..94886ff Binary files /dev/null and b/widgets/kodi/img/audio/eac3.png differ diff --git a/widgets/kodi/img/audio/flac.png b/widgets/kodi/img/audio/flac.png new file mode 100644 index 0000000..d0118f1 Binary files /dev/null and b/widgets/kodi/img/audio/flac.png differ diff --git a/widgets/kodi/img/audio/mp1.png b/widgets/kodi/img/audio/mp1.png new file mode 100644 index 0000000..3ac54a1 Binary files /dev/null and b/widgets/kodi/img/audio/mp1.png differ diff --git a/widgets/kodi/img/audio/mp2.png b/widgets/kodi/img/audio/mp2.png new file mode 100644 index 0000000..98d510b Binary files /dev/null and b/widgets/kodi/img/audio/mp2.png differ diff --git a/widgets/kodi/img/audio/mp3.png b/widgets/kodi/img/audio/mp3.png new file mode 100644 index 0000000..519b0c1 Binary files /dev/null and b/widgets/kodi/img/audio/mp3.png differ diff --git a/widgets/kodi/img/audio/ogg.png b/widgets/kodi/img/audio/ogg.png new file mode 100644 index 0000000..ed30800 Binary files /dev/null and b/widgets/kodi/img/audio/ogg.png differ diff --git a/widgets/kodi/img/audio/pcm_bluray.png b/widgets/kodi/img/audio/pcm_bluray.png new file mode 100644 index 0000000..ada4c29 Binary files /dev/null and b/widgets/kodi/img/audio/pcm_bluray.png differ diff --git a/widgets/kodi/img/audio/truehd.png b/widgets/kodi/img/audio/truehd.png new file mode 100644 index 0000000..f3e5db0 Binary files /dev/null and b/widgets/kodi/img/audio/truehd.png differ diff --git a/widgets/kodi/img/audio/vorbis.png b/widgets/kodi/img/audio/vorbis.png new file mode 100644 index 0000000..ed30800 Binary files /dev/null and b/widgets/kodi/img/audio/vorbis.png differ diff --git a/widgets/kodi/img/audio/wav.png b/widgets/kodi/img/audio/wav.png new file mode 100644 index 0000000..bee2097 Binary files /dev/null and b/widgets/kodi/img/audio/wav.png differ diff --git a/widgets/kodi/img/audio/wavpack.png b/widgets/kodi/img/audio/wavpack.png new file mode 100644 index 0000000..2e739b7 Binary files /dev/null and b/widgets/kodi/img/audio/wavpack.png differ diff --git a/widgets/kodi/img/audio/wma.png b/widgets/kodi/img/audio/wma.png new file mode 100644 index 0000000..2e50ce4 Binary files /dev/null and b/widgets/kodi/img/audio/wma.png differ diff --git a/widgets/kodi/img/audio/wmapro.png b/widgets/kodi/img/audio/wmapro.png new file mode 100644 index 0000000..2a1620d Binary files /dev/null and b/widgets/kodi/img/audio/wmapro.png differ diff --git a/widgets/kodi/img/audio/wmav2.png b/widgets/kodi/img/audio/wmav2.png new file mode 100644 index 0000000..2e50ce4 Binary files /dev/null and b/widgets/kodi/img/audio/wmav2.png differ diff --git a/widgets/kodi/img/audio_icons.png b/widgets/kodi/img/audio_icons.png new file mode 100644 index 0000000..13923b7 Binary files /dev/null and b/widgets/kodi/img/audio_icons.png differ diff --git a/widgets/kodi/img/bg-track.png b/widgets/kodi/img/bg-track.png new file mode 100644 index 0000000..f103809 Binary files /dev/null and b/widgets/kodi/img/bg-track.png differ diff --git a/widgets/kodi/img/btn/btn_sprite.png b/widgets/kodi/img/btn/btn_sprite.png new file mode 100644 index 0000000..78c7507 Binary files /dev/null and b/widgets/kodi/img/btn/btn_sprite.png differ diff --git a/widgets/kodi/img/defaultplaylist.png b/widgets/kodi/img/defaultplaylist.png new file mode 100644 index 0000000..c74a693 Binary files /dev/null and b/widgets/kodi/img/defaultplaylist.png differ diff --git a/widgets/kodi/img/defaultvideo.png b/widgets/kodi/img/defaultvideo.png new file mode 100644 index 0000000..bb6cc16 Binary files /dev/null and b/widgets/kodi/img/defaultvideo.png differ diff --git a/widgets/kodi/img/grey/next.png b/widgets/kodi/img/grey/next.png new file mode 100644 index 0000000..dc2ff9e Binary files /dev/null and b/widgets/kodi/img/grey/next.png differ diff --git a/widgets/kodi/img/grey/pause.png b/widgets/kodi/img/grey/pause.png new file mode 100644 index 0000000..ff98d5c Binary files /dev/null and b/widgets/kodi/img/grey/pause.png differ diff --git a/widgets/kodi/img/grey/play.png b/widgets/kodi/img/grey/play.png new file mode 100644 index 0000000..889593d Binary files /dev/null and b/widgets/kodi/img/grey/play.png differ diff --git a/widgets/kodi/img/grey/playlist.png b/widgets/kodi/img/grey/playlist.png new file mode 100644 index 0000000..55aad86 Binary files /dev/null and b/widgets/kodi/img/grey/playlist.png differ diff --git a/widgets/kodi/img/grey/previous.png b/widgets/kodi/img/grey/previous.png new file mode 100644 index 0000000..b505cc7 Binary files /dev/null and b/widgets/kodi/img/grey/previous.png differ diff --git a/widgets/kodi/img/grey/replay-all.png b/widgets/kodi/img/grey/replay-all.png new file mode 100644 index 0000000..c5ad2fb Binary files /dev/null and b/widgets/kodi/img/grey/replay-all.png differ diff --git a/widgets/kodi/img/grey/replay.png b/widgets/kodi/img/grey/replay.png new file mode 100644 index 0000000..c32a092 Binary files /dev/null and b/widgets/kodi/img/grey/replay.png differ diff --git a/widgets/kodi/img/grey/volume-decrease.png b/widgets/kodi/img/grey/volume-decrease.png new file mode 100644 index 0000000..8840ade Binary files /dev/null and b/widgets/kodi/img/grey/volume-decrease.png differ diff --git a/widgets/kodi/img/grey/volume-increase.png b/widgets/kodi/img/grey/volume-increase.png new file mode 100644 index 0000000..4d3b172 Binary files /dev/null and b/widgets/kodi/img/grey/volume-increase.png differ diff --git a/widgets/kodi/img/handle.png b/widgets/kodi/img/handle.png new file mode 100644 index 0000000..7b28631 Binary files /dev/null and b/widgets/kodi/img/handle.png differ diff --git a/widgets/kodi/img/test.png b/widgets/kodi/img/test.png new file mode 100644 index 0000000..883c891 Binary files /dev/null and b/widgets/kodi/img/test.png differ diff --git a/widgets/kodi/img/video/1080.png b/widgets/kodi/img/video/1080.png new file mode 100644 index 0000000..2fa619e Binary files /dev/null and b/widgets/kodi/img/video/1080.png differ diff --git a/widgets/kodi/img/video/480.png b/widgets/kodi/img/video/480.png new file mode 100644 index 0000000..943e5cf Binary files /dev/null and b/widgets/kodi/img/video/480.png differ diff --git a/widgets/kodi/img/video/4k.png b/widgets/kodi/img/video/4k.png new file mode 100644 index 0000000..96c498f Binary files /dev/null and b/widgets/kodi/img/video/4k.png differ diff --git a/widgets/kodi/img/video/540.png b/widgets/kodi/img/video/540.png new file mode 100644 index 0000000..943e5cf Binary files /dev/null and b/widgets/kodi/img/video/540.png differ diff --git a/widgets/kodi/img/video/576.png b/widgets/kodi/img/video/576.png new file mode 100644 index 0000000..943e5cf Binary files /dev/null and b/widgets/kodi/img/video/576.png differ diff --git a/widgets/kodi/img/video/720.png b/widgets/kodi/img/video/720.png new file mode 100644 index 0000000..134cadb Binary files /dev/null and b/widgets/kodi/img/video/720.png differ diff --git a/widgets/kodi/img/video/avc1.png b/widgets/kodi/img/video/avc1.png new file mode 100644 index 0000000..4a55b63 Binary files /dev/null and b/widgets/kodi/img/video/avc1.png differ diff --git a/widgets/kodi/img/video/bluray.png b/widgets/kodi/img/video/bluray.png new file mode 100644 index 0000000..00045b1 Binary files /dev/null and b/widgets/kodi/img/video/bluray.png differ diff --git a/widgets/kodi/img/video/divx.png b/widgets/kodi/img/video/divx.png new file mode 100644 index 0000000..35c547e Binary files /dev/null and b/widgets/kodi/img/video/divx.png differ diff --git a/widgets/kodi/img/video/dvd.png b/widgets/kodi/img/video/dvd.png new file mode 100644 index 0000000..0610086 Binary files /dev/null and b/widgets/kodi/img/video/dvd.png differ diff --git a/widgets/kodi/img/video/flv.png b/widgets/kodi/img/video/flv.png new file mode 100644 index 0000000..8917dde Binary files /dev/null and b/widgets/kodi/img/video/flv.png differ diff --git a/widgets/kodi/img/video/h264.png b/widgets/kodi/img/video/h264.png new file mode 100644 index 0000000..f356b42 Binary files /dev/null and b/widgets/kodi/img/video/h264.png differ diff --git a/widgets/kodi/img/video/hddvd.png b/widgets/kodi/img/video/hddvd.png new file mode 100644 index 0000000..585090f Binary files /dev/null and b/widgets/kodi/img/video/hddvd.png differ diff --git a/widgets/kodi/img/video/hdmv.png b/widgets/kodi/img/video/hdmv.png new file mode 100644 index 0000000..00045b1 Binary files /dev/null and b/widgets/kodi/img/video/hdmv.png differ diff --git a/widgets/kodi/img/video/mpeg1video.png b/widgets/kodi/img/video/mpeg1video.png new file mode 100644 index 0000000..eba4c72 Binary files /dev/null and b/widgets/kodi/img/video/mpeg1video.png differ diff --git a/widgets/kodi/img/video/mpeg2video.png b/widgets/kodi/img/video/mpeg2video.png new file mode 100644 index 0000000..e0bb51f Binary files /dev/null and b/widgets/kodi/img/video/mpeg2video.png differ diff --git a/widgets/kodi/img/video/tv.png b/widgets/kodi/img/video/tv.png new file mode 100644 index 0000000..1f5f929 Binary files /dev/null and b/widgets/kodi/img/video/tv.png differ diff --git a/widgets/kodi/img/video/vc-1.png b/widgets/kodi/img/video/vc-1.png new file mode 100644 index 0000000..c50819b Binary files /dev/null and b/widgets/kodi/img/video/vc-1.png differ diff --git a/widgets/kodi/img/video/vhs.png b/widgets/kodi/img/video/vhs.png new file mode 100644 index 0000000..6198e1c Binary files /dev/null and b/widgets/kodi/img/video/vhs.png differ diff --git a/widgets/kodi/img/video/vp8.png b/widgets/kodi/img/video/vp8.png new file mode 100644 index 0000000..856abc3 Binary files /dev/null and b/widgets/kodi/img/video/vp8.png differ diff --git a/widgets/kodi/img/video/wvc1.png b/widgets/kodi/img/video/wvc1.png new file mode 100644 index 0000000..c50819b Binary files /dev/null and b/widgets/kodi/img/video/wvc1.png differ diff --git a/widgets/kodi/img/video/xvid.png b/widgets/kodi/img/video/xvid.png new file mode 100644 index 0000000..8d86fd5 Binary files /dev/null and b/widgets/kodi/img/video/xvid.png differ diff --git a/widgets/kodi/img/volume.png b/widgets/kodi/img/volume.png new file mode 100644 index 0000000..002b52f Binary files /dev/null and b/widgets/kodi/img/volume.png differ diff --git a/widgets/kodi/img/w_icon/aspect.png b/widgets/kodi/img/w_icon/aspect.png new file mode 100644 index 0000000..ff72603 Binary files /dev/null and b/widgets/kodi/img/w_icon/aspect.png differ diff --git a/widgets/kodi/img/w_icon/button.png b/widgets/kodi/img/w_icon/button.png new file mode 100644 index 0000000..4463ecc Binary files /dev/null and b/widgets/kodi/img/w_icon/button.png differ diff --git a/widgets/kodi/img/w_icon/channel.png b/widgets/kodi/img/w_icon/channel.png new file mode 100644 index 0000000..06fde51 Binary files /dev/null and b/widgets/kodi/img/w_icon/channel.png differ diff --git a/widgets/kodi/img/w_icon/codec.png b/widgets/kodi/img/w_icon/codec.png new file mode 100644 index 0000000..4f69eb6 Binary files /dev/null and b/widgets/kodi/img/w_icon/codec.png differ diff --git a/widgets/kodi/img/w_icon/playlist.png b/widgets/kodi/img/w_icon/playlist.png new file mode 100644 index 0000000..97987ca Binary files /dev/null and b/widgets/kodi/img/w_icon/playlist.png differ diff --git a/widgets/kodi/img/w_icon/progress.png b/widgets/kodi/img/w_icon/progress.png new file mode 100644 index 0000000..66acb1e Binary files /dev/null and b/widgets/kodi/img/w_icon/progress.png differ diff --git a/widgets/kodi/img/w_icon/resolution.png b/widgets/kodi/img/w_icon/resolution.png new file mode 100644 index 0000000..2d99a26 Binary files /dev/null and b/widgets/kodi/img/w_icon/resolution.png differ diff --git a/widgets/kodi/img/w_icon/video_codec.png b/widgets/kodi/img/w_icon/video_codec.png new file mode 100644 index 0000000..436d4b1 Binary files /dev/null and b/widgets/kodi/img/w_icon/video_codec.png differ diff --git a/widgets/kodi/js/kodi.js b/widgets/kodi/js/kodi.js new file mode 100644 index 0000000..e277802 --- /dev/null +++ b/widgets/kodi/js/kodi.js @@ -0,0 +1,432 @@ +/* + kodi Widget-Set + Copyright 10.2015-2016 instalator +*/ +"use strict"; + +if (vis.editMode) { + $.extend(true, systemDictionary, { + "myColor": {"en": "myColor", "de": "mainColor", "ru": "Мой цвет"}, + "myColor_tooltip": { + "en": "Description of\x0AmyColor", + "de": "Beschreibung von\x0AmyColor", + "ru": "Описание\x0AmyColor" + }, + "oid_playlist": {"en": "playlist", "de": "playlist", "ru": "playlist"}, + "oid_pvrplaylist": {"en": "PVR playlist", "de": "PVR playlist", "ru": "PVR playlist"}, + "oid_position": {"en": "position", "de": "position", "ru": "position"}, + "oid_codec": {"en": "codec", "de": "codec", "ru": "codec"}, + "oid_aspect": {"en": "video_aspect", "de": "video_aspect", "ru": "video_aspect"}, + "oid_channel": {"en": "channels", "de": "channels", "ru": "channels"}, + "oid_videocodec": {"en": "video_codec", "de": "video_codec", "ru": "video_codec"}, + "oid_play": {"en": "play", "de": "play", "ru": "play"}, + "oid_speed": {"en": "speed", "de": "speed", "ru": "speed"}, + "oid_prev": {"en": "previous", "de": "previous", "ru": "previous"}, + "oid_next": {"en": "next", "de": "next", "ru": "next"}, + "oid_stop": {"en": "stop", "de": "stop", "ru": "stop"}, + "oid_mute": {"en": "mute", "de": "mute", "ru": "mute"}, + "oid_rpt": {"en": "repeat", "de": "repeat", "ru": "repeat"}, + "oid_shf": {"en": "shuffle", "de": "shuffle", "ru": "shuffle"}, + "oid_seek": {"en": "seek", "de": "seek", "ru": "seek"}, + "oid_server": { + "en": "IP адрес и порт веб сервера коди. Например: 127.0.0.1:80", + "de": "IP адрес и порт веб сервера коди. Например: 127.0.0.1:80", + "ru": "IP адрес и порт веб сервера коди. Например: 127.0.0.1:80"}, + "oid_resolut": {"en": "video_height", "de": "video_height", "ru": "video_height"} + }); +} + +$.extend(true, systemDictionary, { + "Instance": {"en": "Instance", "de": "Instanz", "ru": "Инстанция"} +}); + +vis.binds.kodi = { + version: "0.2.2", + showVersion: function () { + if (vis.binds.kodi.version) { + console.log('Version kodi: ' + vis.binds.kodi.version); + vis.binds.kodi.version = null; + } + }, + states: { + oid_seek: {val: 0, selector: '', objName: 'oid_seek'}, + oid_curtimetotal: {val: undefined, selector: '', objName: 'curtimetotal'} + }, +/***********************************************************************/ +Thumbnail: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + var type = null; + // if nothing found => wait + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.Thumbnail(widgetID, view, data, style); + }, 100); + } + function Thumb(cover){ + var url_thumb; + if (vis.editMode) { + $div.find('li').removeClass().addClass("cover adef").css('backgroundImage', 'url()'); + } else { + if (cover && cover !== 'image://DefaultAlbumCover.png/'){ + url_thumb = 'http://' + data.oid_server + '/image/' + encodeURI(cover); + $div.find('li').removeClass().addClass("cover").css('backgroundImage', 'url('+url_thumb+')'); + } else { + if (type == 'video'){ + $div.find('li').removeClass().addClass("cover vdef").css('backgroundImage', 'url()'); + } else { + $div.find('li').removeClass().addClass("cover adef").css('backgroundImage', 'url()'); + } + } + } + } + // subscribe on updates of value + if (data.oid_thumbnail) { + vis.states.bind(data.oid_thumbnail + '.val', function (e, newVal, oldVal) { + Thumb(newVal); + }); + } + if (data.oid_type) { + vis.states.bind(data.oid_type + '.val', function (e, newVal, oldVal) { + type = newVal; + Thumb(); + }); + } + Thumb(vis.states[data.oid_thumbnail + '.val']); + }, +/************************************************************************/ +Progress: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.Progress(widgetID, view, data, style); + }, 100); + } + + $(function(){ + $div.progressbar({ + value: 0 + }); + }); + + $div.on('click', function(e){ + var maxWidth = $(this).css("width").slice(0, -2); + //var left = $(this).css("left").slice(0, -2); + var clickPos = e.pageX - this.offsetLeft; + var percentage = clickPos / maxWidth * 100; + vis.setValue(data.oid_seek, percentage); + $div.progressbar("option","value", percentage); + }); + + // subscribe on updates of value + if (data.oid_seek) { + vis.states.bind(data.oid_seek + '.val', function (e, newVal, oldVal) { + $div.progressbar("value", newVal); + }); + } + }, +/**************************************************************************/ +Button: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.Button(widgetID, view, data, style); + }, 100); + } + + $("#kodicontrols .prev").on('click',function(){ + vis.setValue(data.oid_prev, ''); + }); + $("#kodicontrols .rewind").on('click',function(){ + var seek = vis.states[data.oid_seek + '.val']; + vis.setValue(data.oid_speed, 'decrement'); + }); + $("#kodicontrols .playpause").on('click',function(){ + var speed = vis.states[data.oid_speed + '.val']; + if (speed === 1){ + vis.setValue(data.oid_speed, 0); + } else { + vis.setValue(data.oid_speed, 1); + } + }); + $("#kodicontrols .stop").on('click',function(){ + vis.setValue(data.oid_stop, ''); + }); + $("#kodicontrols .forward").on('click',function(){ + var seek = vis.states[data.oid_seek + '.val']; + vis.setValue(data.oid_speed, 'increment'); + }); + $("#kodicontrols .next").on('click',function(){ + vis.setValue(data.oid_next, ''); + }); + $("#kodicontrols .repeat").on('click',function(){ + var rpt = vis.states[data.oid_rpt + '.val']; + if (rpt === 'off'){ + vis.setValue(data.oid_rpt, 'one'); + } else if (rpt === 'one'){ + vis.setValue(data.oid_rpt, 'all'); + } else if (rpt === 'all'){ + vis.setValue(data.oid_rpt, 'off'); + } + }); + $("#kodicontrols .shuffle").on('click',function(){ + var shf = vis.states[data.oid_shf + '.val']; + if (shf === false){ + vis.setValue(data.oid_shf, true); + } else { + vis.setValue(data.oid_shf, false); + } + }); + + + // subscribe on updates of value + if (data.oid_speed) { + vis.states.bind(data.oid_speed + '.val', function (e, newVal, oldVal) { + var sp = $("#kodicontrols > .playpause"); + if (newVal !== 1){ + sp.removeClass().addClass('playpause play'); + } else { + sp.removeClass().addClass('playpause'); + } + }); + } + if (data.oid_rpt) { + vis.states.bind(data.oid_rpt + '.val', function (e, newVal, oldVal) { + var r = $("#kodicontrols > .repeat"); + if (newVal == 'off'){ + r.removeClass(); + r.addClass("repeat off"); + } else if (newVal == 'one'){ + r.removeClass(); + r.addClass("repeat one"); + } else if (newVal == 'all'){ + r.removeClass(); + r.addClass("repeat all"); + } + }); + } + if (data.oid_shf) { + vis.states.bind(data.oid_shf + '.val', function (e, newVal, oldVal) { + var s = $("#kodicontrols > .shuffle"); + if (newVal === true || newVal === 'true'){ + s.removeClass('off').addClass('on'); + } else { + s.removeClass('on').addClass('off'); + } + }); + } + }, +/**************************************************************************/ +Playlist: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + // if nothing found => wait + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.Playlist(widgetID, view, data, style); + }, 100); + } + var playlist; + function SetPlaylist(val){ + playlist = JSON.parse(val); + var _playlist = {}; + if (playlist){ + _playlist = playlist; + $div.find("#playListContainer").empty(); + _playlist.forEach(function(item, i, arr) { + $div.find("#playListContainer").append("
  • "+(i+1)+' - '+_playlist[i].label+"
  • "); + }); + $div.find("#playListContainer .item"+(parseInt(vis.states[data.oid_position + '.val'])+1)).addClass("active"); + $div.find('#playListContainer').on('click', "li", function(){ + var n=$(this).index(); + vis.setValue(data.oid_position, n); + }); + } else if (playlist.channels){ + _playlist = playlist.channels; + $div.find("#playListContainer").empty(); + _playlist.forEach(function(item, i, arr) { + var url = 'http://'+data.oid_server+'/image/' + encodeURI(_playlist[i].thumbnail); + $div.find("#playListContainer").append("
  • "+_playlist[i].label+"
  • "); + }); + $div.find("#playListContainer .item"+(parseInt(vis.states[data.oid_position + '.val'])+1)).addClass("active"); + + $div.find('#playListContainer').on('click', "li", function(){ + var n=$(this).index()+1; + vis.setValue(data.oid_position, n); + }); + } + } + // subscribe on updates of value + if (data.oid_playlist) { + vis.states.bind(data.oid_playlist + '.val', function (e, newVal, oldVal) { + SetPlaylist(newVal); + }); + } + if (data.oid_position) { + vis.states.bind(data.oid_position + '.val', function (e, newVal, oldVal) { + $div.find("#playListContainer li").removeClass("active"); + newVal = parseInt(newVal); + if (playlist.items){ + newVal++; + } + $div.find("#playListContainer .item"+ newVal).addClass("active"); + }); + } + if ($div.length){ + SetPlaylist(vis.states[data.oid_playlist + '.val']); + } + + }, +/************************************************************************/ +CodecInfo: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + // if nothing found => wait + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.CodecInfo(widgetID, view, data, style); + }, 100); + } + function SetCodecInfo(val){ + if (val){ + $('.kodiinfo > .codec').css('backgroundImage', 'url(./widgets/kodi/img/audio/'+val+'.png)'); + } + } + // subscribe on updates of value + if (data.oid_codec) { + vis.states.bind(data.oid_codec + '.val', function (e, newVal, oldVal) { + SetCodecInfo(newVal); + }); + } + if (vis.editMode) { + SetCodecInfo('dtshd_ma'); + } else { + SetCodecInfo(vis.states[data.oid_codec + '.val']); + } + }, +/************************************************************************/ +AspectInfo: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + // if nothing found => wait + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.AspectInfo(widgetID, view, data, style); + }, 100); + } + function SetAspectInfo(val){ + if (val){ + val = parseFloat(val).toFixed(2); + $('.kodiinfo > .aspect').css('backgroundImage', 'url(./widgets/kodi/img/aspectratio/'+val+'.png)'); + } + } + // subscribe on updates of value + if (data.oid_aspect) { + vis.states.bind(data.oid_aspect + '.val', function (e, newVal, oldVal) { + SetAspectInfo(newVal); + }); + } + if (vis.editMode) { + SetAspectInfo('1.78'); + } else { + SetAspectInfo(vis.states[data.oid_aspect + '.val']); + } + }, +/************************************************************************/ +ResolutInfo: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + // if nothing found => wait + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.ResolutInfo(widgetID, view, data, style); + }, 100); + } + function SetResolutInfo(val){ + if (val){ + $('.kodiinfo > .resolut').css('backgroundImage', 'url(./widgets/kodi/img/video/'+val+'.png)'); + } + } + // subscribe on updates of value + if (data.oid_resolut) { + vis.states.bind(data.oid_resolut + '.val', function (e, newVal, oldVal) { + SetResolutInfo(newVal); + }); + } + if (vis.editMode) { + SetResolutInfo('1080'); + } else { + SetResolutInfo(vis.states[data.oid_resolut + '.val']); + } + + }, +/************************************************************************/ +ChannelInfo: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + // if nothing found => wait + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.ChannelInfo(widgetID, view, data, style); + }, 100); + } + function SetChannelInfo(val){ + if (val){ + $('.kodiinfo > .channel').css('backgroundImage', 'url(./widgets/kodi/img/audio/'+val+'.png)'); + } + } + // subscribe on updates of value + if (data.oid_channel) { + vis.states.bind(data.oid_channel + '.val', function (e, newVal, oldVal) { + SetChannelInfo(newVal); + }); + } + if (vis.editMode) { + SetChannelInfo('6'); + } else { + SetChannelInfo(vis.states[data.oid_channel + '.val']); + } + }, +/************************************************************************/ +VideoCodec: function (widgetID, view, data, style) { + var $div = $('#' + widgetID); + // if nothing found => wait + if (!$div.length) { + return setTimeout(function () { + vis.binds.kodi.VideoCodec(widgetID, view, data, style); + }, 100); + } + function SetVideoCodecInfo(val){ + if (val){ + $('.kodiinfo > .videocodec').css('backgroundImage', 'url(./widgets/kodi/img/video/'+val+'.png)'); + } + } + // subscribe on updates of value + if (data.oid_videocodec) { + vis.states.bind(data.oid_videocodec + '.val', function (e, newVal, oldVal) { + SetVideoCodecInfo(newVal); + }); + } + if (vis.editMode) { + SetVideoCodecInfo('vhs'); + } else { + SetVideoCodecInfo(vis.states[data.oid_videocodec + '.val']); + } + } +/***********************************************************************/ + +}; + /*if (vis.editMode) { + vis.binds.kodi.changeOid = function (widgetID, view, newId, attr, isCss) { + //console.log('---------: ' + widgetID +' - '+view+' - '+newId+' - '+attr+' - '+isCss); + newId = newId ? newId.substring(0, newId.length - attr.length + 'oid_'.length) : ''; + var changed = []; + for (var s in vis.binds.kodi.states) { + if (s === 'oid_curtime' || !vis.binds.kodi.states[s].objName) continue; + if (vis.objects[newId + vis.binds.kodi.states[s].objName]) { + changed.push(s); + vis.views[view].widgets[widgetID].data[s] = newId + vis.binds.kodi.states[s].objName; + vis.widgets[widgetID].data[s] = newId + vis.binds.kodi.states[s].objName; + } + } + return changed; + }; +}*/ + +vis.binds.kodi.showVersion(); diff --git a/www/README.md b/www/README.md new file mode 100644 index 0000000..aba6cb9 --- /dev/null +++ b/www/README.md @@ -0,0 +1,2 @@ +if you put files in this directory they will be uploaded to DB on adapter install/upgrade. +they can then be accessed via yunkong2 WEB adapter http://<yunkong2IP>:8082/<adapter-name>/... \ No newline at end of file diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..a7ed613 --- /dev/null +++ b/www/index.html @@ -0,0 +1,4 @@ +

    template adapter

    +

    + +

    \ No newline at end of file diff --git a/www/kodi.png b/www/kodi.png new file mode 100644 index 0000000..ced8614 Binary files /dev/null and b/www/kodi.png differ