diff --git a/.gitignore b/.gitignore index 9dc6f35..ad93533 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ debian/libkysdk-system/ debian/libkysdk-system-dev/ debian/libkysdk-systime/ debian/libkysdk-systime-dev/ +debian/libkysdk-processmanage/ debian/libkysdk*.substvars debian/libkysdk*.debhelper debian/.debhelper diff --git a/debian/changelog b/debian/changelog index d4dc5e1..2434b8c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +libkysdk-system (2.0.0.0-ok7) yangtze; urgency=medium + + *Bug号: + *需求号: + *其他修改:合并主线2.0.0.0-0k0.1~2.0.0.0-0k0.14版本到openkylin + + -- szm-min Mon, 03 Apr 2023 10:46:43 +0800 + libkysdk-system (2.0.0.0-ok6) yangtze; urgency=medium *Bug号: diff --git a/debian/control b/debian/control index 857941b..d2feb1e 100644 --- a/debian/control +++ b/debian/control @@ -22,7 +22,10 @@ Build-Depends: debhelper-compat (= 12), libtesseract-dev, libcups2-dev, libcurl4-openssl-dev, - libxrandr-dev + libxrandr-dev, + libqt5dbus5, + libqt5webchannel5-dev, + libqt5websockets5-dev Standards-Version: 4.4.1 Homepage: http://gitlab2.kylin.com/kysdk/kysdk-system @@ -102,6 +105,7 @@ Depends: ${shlibs:Depends}, systemd, libglib2.0-0, libkysdk-timer(>=1.2.0), + libkysdk-config(>=1.2.0), libkysdk-systemcommon Multi-Arch: same Description: 系统时间相关操作库 @@ -113,7 +117,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, libkysdk-systime, libglib2.0-dev, - libkysdk-timer-dev(>=1.2.0) + libkysdk-timer-dev(>=1.2.0), + libkysdk-config-dev(>=1.2.0) Multi-Arch: foreign Description: 系统时间相关操作库 - 开发库 @@ -228,6 +233,7 @@ Architecture: any Section: utils Depends: ${shlibs:Depends}, ${misc:Depends}, + libkysdk-log(>=1.2.0), libkysdk-systemcommon Multi-Arch: same Description: 运行时信息获取库 @@ -237,6 +243,8 @@ Architecture: all Section: utils Depends: ${shlibs:Depends}, ${misc:Depends}, + libkysdk-log-dev(>=1.2.0), + libkysdk-utils-dev(>=1.2.0), libkysdk-proc Multi-Arch: foreign Description: 运行时信息获取库 - 开发库 @@ -343,4 +351,72 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, libkysdk-realtime Multi-Arch: foreign -Description: 运行时信息库 - 开发库 \ No newline at end of file +Description: 运行时信息库 - 开发库 + +Package: libkysdk-system-java +Architecture: any +Section: utils +Depends: ${shlibs:Depends}, + ${misc:Depends}, + libkysdk-system-dbus +Multi-Arch: same +Description: 多语言支持 - java + +Package: libkysdk-system-python +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, python3-dbus, libkysdk-system-dbus +Multi-Arch: same +Description: 多语言支持 - python + +Package: libkysdk-system-dbus +Architecture: any +Section: utils +Depends: ${shlibs:Depends}, + ${misc:Depends}, + libqt5dbus5, + libkysdk-disk, + libkysdk-hardware, + libkysdk-location, + libkysdk-net, + libkysdk-package, + libkysdk-proc, + libkysdk-realtime, + libkysdk-sysinfo +Multi-Arch: same +Description: sdk dbus + +Package: libkysdk-system-javascript-http +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends},libqt5widgets5,libqt5gui5,libqt5dbus5,libqt5core5a,libkysdk-system-dbus +Multi-Arch: same +Description:多语言支持 - javascript + +Package: libkysdk-system-javascript-websocket +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends},libqt5webchannel5,libqt5websockets5,libkysdk-system-dbus +Multi-Arch: same +Description: 多语言支持 - javascript + +Package: libkysdk-logrotate +Architecture: any +Section: utils +Depends: ${shlibs:Depends}, + ${misc:Depends}, + libkysdk-systemcommon, + libqt5core5a, + libqt5dbus5, + libkysdk-log, + logrotate +Multi-Arch: same +Description: 日志转储服务 + +Package: libkysdk-processmanage +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + libdbus-1-3, + libkysdk-log, + libkysdk-config, + libkysdk-utils +Description: 进程管理 + diff --git a/debian/libkysdk-location.postinst b/debian/libkysdk-location.postinst new file mode 100755 index 0000000..df98a32 --- /dev/null +++ b/debian/libkysdk-location.postinst @@ -0,0 +1,4 @@ +#!/bin/bash + +systemctl enable dbus_location.service +systemctl restart dbus_location.service \ No newline at end of file diff --git a/debian/libkysdk-logrotate.install b/debian/libkysdk-logrotate.install new file mode 100644 index 0000000..778fe0b --- /dev/null +++ b/debian/libkysdk-logrotate.install @@ -0,0 +1,4 @@ +bin/kysdk-logrotate usr/bin/ +src/logrotate/com.kysdk.base.conf etc/dbus-1/system.d/ +src/logrotate/kysdk-logrotate.service lib/systemd/system/ +src/logrotate/logrotate.cron usr/lib/kysdk/kysdk-system/ \ No newline at end of file diff --git a/debian/libkysdk-logrotate.postinst b/debian/libkysdk-logrotate.postinst new file mode 100644 index 0000000..af366e9 --- /dev/null +++ b/debian/libkysdk-logrotate.postinst @@ -0,0 +1,5 @@ +#!/bin/bash + +systemctl enable kysdk-logrotate.service +systemctl restart kysdk-logrotate.service +crontab /usr/lib/kysdk/kysdk-system/logrotate.cron \ No newline at end of file diff --git a/debian/libkysdk-processmanage.install b/debian/libkysdk-processmanage.install new file mode 100644 index 0000000..4c85ecf --- /dev/null +++ b/debian/libkysdk-processmanage.install @@ -0,0 +1,4 @@ +development-files/kysdk-processmanage.pc usr/share/pkgconfig/ +src/processmanage/processStateManage.service lib/systemd/system +src/processmanage/com.kylin.processStateManage.conf etc/dbus-1/system.d +bin/processStateManage usr/bin/ diff --git a/debian/libkysdk-system-dbus.install b/debian/libkysdk-system-dbus.install new file mode 100644 index 0000000..30b0fd2 --- /dev/null +++ b/debian/libkysdk-system-dbus.install @@ -0,0 +1,6 @@ +bin/kySdkDbus usr/bin/ +bin/kySdkDbus-session usr/bin/ +src/kysdkdbus/com.kylin.kysdk.service.conf etc/dbus-1/system.d/ +src/kysdkdbus/kysdk-dbus.service lib/systemd/system/ +src/kysdkdbus/kysdk-dbus-session.desktop etc/xdg/autostart/ + diff --git a/debian/libkysdk-system-dbus.postinst b/debian/libkysdk-system-dbus.postinst new file mode 100644 index 0000000..b9c85a3 --- /dev/null +++ b/debian/libkysdk-system-dbus.postinst @@ -0,0 +1,5 @@ +#!/bin/bash + +systemctl daemon-reload +systemctl enable kysdk-dbus.service + diff --git a/debian/libkysdk-system-java.install b/debian/libkysdk-system-java.install new file mode 100644 index 0000000..232eb7d --- /dev/null +++ b/debian/libkysdk-system-java.install @@ -0,0 +1 @@ +src/kysdk-java/kysdk-java-ext/kysdk-java/target/kysdk-java-1.0.0.jar usr/share/java \ No newline at end of file diff --git a/debian/libkysdk-system-javascript-http.install b/debian/libkysdk-system-javascript-http.install new file mode 100644 index 0000000..f8c234a --- /dev/null +++ b/debian/libkysdk-system-javascript-http.install @@ -0,0 +1,4 @@ +src/libkysdk-system-javascript-http/httpServer usr/bin/ +src/libkysdk-system-javascript-http/conf/kysdk.conf etc/kysdk/ +src/libkysdk-system-javascript-http/conf/kysdkHttpServer.desktop etc/xdg/autostart/ +src/libkysdk-system-javascript-http/html/ usr/lib/kysdk-dbus-http/html/ \ No newline at end of file diff --git a/debian/libkysdk-system-javascript-websocket.install b/debian/libkysdk-system-javascript-websocket.install new file mode 100644 index 0000000..222ba1b --- /dev/null +++ b/debian/libkysdk-system-javascript-websocket.install @@ -0,0 +1,3 @@ +src/libkysdk-system-javascript-websocket/kysdk-dbus-websocket usr/bin/ +src/libkysdk-system-javascript-websocket/kysdk-dbus-websocket.desktop etc/xdg/autostart/ +src/libkysdk-system-javascript-websocket/html/ usr/lib/kysdk-dbus-websocket/html/ \ No newline at end of file diff --git a/debian/libkysdk-system-python.install b/debian/libkysdk-system-python.install new file mode 100644 index 0000000..6024589 --- /dev/null +++ b/debian/libkysdk-system-python.install @@ -0,0 +1 @@ +src/python/kysdk.py usr/lib/python3/dist-packages/ \ No newline at end of file diff --git a/debian/libkysdk-systime-dev.install b/debian/libkysdk-systime-dev.install new file mode 100644 index 0000000..952ed4c --- /dev/null +++ b/debian/libkysdk-systime-dev.install @@ -0,0 +1,2 @@ +usr/include/kysdk/kysdk-system/libkydate.h +development-files/kysdk-systime.pc usr/share/pkgconfig/ \ No newline at end of file diff --git a/debian/libkysdk-systime.install b/debian/libkysdk-systime.install index 94da381..27f4857 100644 --- a/debian/libkysdk-systime.install +++ b/debian/libkysdk-systime.install @@ -1,3 +1,10 @@ -bin/* usr/bin/ +bin/systime usr/bin/ src/systemtime/com.kylin.kysdk.TimeServer.conf etc/dbus-1/system.d/ src/systemtime/kysdk-systime.service lib/systemd/system/ +src/systemtime/kysdk-systime-date.desktop etc/xdg/autostart/ +bin/sdk-date usr/bin/ +usr/lib/kysdk/kysdk-system/libkydate.so* +src/systemtime/dateform.conf /etc/kydate/ +po/zh_CN.mo usr/share/locale/zh_CN/LC_MESSAGES/ +po/bo_CN.mo usr/share/locale/bo_CN/LC_MESSAGES/ + diff --git a/debian/libkysdk-systime.postinst b/debian/libkysdk-systime.postinst index 05567c8..6a80d70 100644 --- a/debian/libkysdk-systime.postinst +++ b/debian/libkysdk-systime.postinst @@ -8,3 +8,5 @@ else systemctl restart kysdk-systime.service fi +mv /usr/share/locale/zh_CN/LC_MESSAGES/zh_CN.mo /usr/share/locale/zh_CN/LC_MESSAGES/kysdk-date.mo +mv /usr/share/locale/bo_CN/LC_MESSAGES/bo_CN.mo /usr/share/locale/bo_CN/LC_MESSAGES/kysdk-date.mo diff --git a/development-files/kysdk-processmanage.pc b/development-files/kysdk-processmanage.pc new file mode 100644 index 0000000..fb3fa80 --- /dev/null +++ b/development-files/kysdk-processmanage.pc @@ -0,0 +1,4 @@ +Name: process-state-manage +Description: manage process oom_score_adj and oom_score_adj_child +Requires: kysdk-log kysdk-config libsystemd dbus-1 dbus-glib-1 +Version: 2.0.0 diff --git a/development-files/kysdk-systime.pc b/development-files/kysdk-systime.pc new file mode 100644 index 0000000..a2087d5 --- /dev/null +++ b/development-files/kysdk-systime.pc @@ -0,0 +1,6 @@ +Name: libkysdk-systime +Description: kysdk system layer date component +Requires: kysdk-config glib-2.0 +Version: 2.0.0 +Libs: -L/usr/lib/kysdk/kysdk-system/ -lkydate -Wl,-rpath=/usr/lib/kysdk/kysdk-system/ +Cflags: -I/usr/include/kysdk/kysdk-system/ \ No newline at end of file diff --git a/po/bo_CN.mo b/po/bo_CN.mo new file mode 100644 index 0000000..ab9fe9d Binary files /dev/null and b/po/bo_CN.mo differ diff --git a/po/bo_CN.po b/po/bo_CN.po new file mode 100644 index 0000000..73eb938 --- /dev/null +++ b/po/bo_CN.po @@ -0,0 +1,266 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-08-16 14:41+0800\n" +"PO-Revision-Date: 2022-08-17 03:47+0000\n" +"Last-Translator: shaozhimin \n" +"Language-Team: Tibetan (China) \n" +"Language: bo_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.12.1-dev\n" + +#: src/systemtime/libkydate.c:109 src/systemtime/libkydate.c:1162 +#: src/systemtime/libkydate.c:1435 src/systemtime/date.c:104 +msgid "%Y_year%m_mon%d_day" +msgstr "%Yལོ།%mཟླ།%dཉིན།" + +#: src/systemtime/libkydate.c:126 src/systemtime/libkydate.c:603 +#: src/systemtime/libkydate.c:696 src/systemtime/libkydate.c:1174 +#: src/systemtime/libkydate.c:1447 src/systemtime/date.c:82 +#: src/systemtime/libkydate.c:1494 +#, c-format +msgid "%d_year%d_mon%d_day" +msgstr "%dལོ།%dཟླ།%dཉིན།" + +#: src/systemtime/libkydate.c:445 +#, c-format +msgid "**year**mon**day" +msgstr "**ལོ།**ཟླ།**ཉིན།" + +#: src/systemtime/libkydate.c:456 +#, c-format +msgid "*year*mon*day" +msgstr "*ལོ།*ཟླ།*ཉིན།" + +#: src/systemtime/libkydate.c:486 src/systemtime/libkydate.c:499 +#: src/systemtime/date.c:115 src/systemtime/date.c:124 +#, c-format +msgid "24-hour clock" +msgstr "ཆུ་ཚོད་24ལམ་ལུགས་" + +#: src/systemtime/libkydate.c:507 src/systemtime/date.c:131 +#, c-format +msgid "12-hour clock" +msgstr "ཆུ་ཚོད་12ལམ་ལུགས་" + +#: src/systemtime/libkydate.c:687 +#, c-format +msgid "%d_year0%d_mon0%d_day" +msgstr "%dལོ།0%dཟླ།0%dཉིན།" + +#: src/systemtime/libkydate.c:689 +#, c-format +msgid "%d_year0%d_mon%d_day" +msgstr "%dལོ།0%dཟླ།%dཉིན།" + +#: src/systemtime/libkydate.c:694 +#, c-format +msgid "%d_year%d_mon0%d_day" +msgstr "%dལོ།%dཟླ།0%dཉིན།" + +#: src/systemtime/libkydate.c:778 src/systemtime/libkydate.c:820 +#, c-format +msgid "pm%d:0%d" +msgstr "ཕྱི་དྲོ།%d:0%d" + +#: src/systemtime/libkydate.c:781 src/systemtime/libkydate.c:823 +#, c-format +msgid "pm%d:0%d:0%d" +msgstr "ཕྱི་དྲོ།%d:0%d:0%d" + +#: src/systemtime/libkydate.c:784 src/systemtime/libkydate.c:825 +#, c-format +msgid "pm%d:0%d:%d" +msgstr "ཕྱི་དྲོ།%d:0%d:%d" + +#: src/systemtime/libkydate.c:787 src/systemtime/libkydate.c:828 +#, c-format +msgid "pm%d:%d" +msgstr "ཕྱི་དྲོ།%d:%d" + +#: src/systemtime/libkydate.c:790 src/systemtime/libkydate.c:831 +#, c-format +msgid "pm%d:%d:0%d" +msgstr "ཕྱི་དྲོ།%d:%d:0%d" + +#: src/systemtime/libkydate.c:793 src/systemtime/libkydate.c:833 +#, c-format +msgid "pm%d:%d:%d" +msgstr "ཕྱི་དྲོ།%d:%d:%d" + +#: src/systemtime/libkydate.c:801 +#, c-format +msgid "pm0%d:0%d" +msgstr "ཕྱི་དྲོ།0%d:0%d" + +#: src/systemtime/libkydate.c:804 +#, c-format +msgid "pm0%d:0%d:0%d" +msgstr "ཕྱི་དྲོ།0%d:0%d:0%d" + +#: src/systemtime/libkydate.c:806 +#, c-format +msgid "pm0%d:0%d:%d" +msgstr "ཕྱི་དྲོ།0%d:0%d:%d" + +#: src/systemtime/libkydate.c:809 +#, c-format +msgid "pm0%d:%d" +msgstr "ཕྱི་དྲོ།0%d:%d" + +#: src/systemtime/libkydate.c:812 +#, c-format +msgid "pm0%d:%d:0%d" +msgstr "ཕྱི་དྲོ།0%d:%d:0%d" + +#: src/systemtime/libkydate.c:814 +#, c-format +msgid "pm0%d:%d:%d" +msgstr "ཕྱི་དྲོ།0%d:%d:%d" + +#: src/systemtime/libkydate.c:843 +#, c-format +msgid "am0%d:0%d" +msgstr "སྔ་དྲོ།0%d:0%d" + +#: src/systemtime/libkydate.c:846 +#, c-format +msgid "am0%d:0%d:0%d" +msgstr "སྔ་དྲོ།0%d:0%d:0%d" + +#: src/systemtime/libkydate.c:848 +#, c-format +msgid "am0%d:0%d:%d" +msgstr "སྔ་དྲོ།0%d:0%d:%d" + +#: src/systemtime/libkydate.c:851 +#, c-format +msgid "am0%d:%d" +msgstr "སྔ་དྲོ།0%d:%d" + +#: src/systemtime/libkydate.c:854 +#, c-format +msgid "am0%d:%d:0%d" +msgstr "སྔ་དྲོ།0%d:%d:0%d" + +#: src/systemtime/libkydate.c:856 +#, c-format +msgid "am0%d:%d:%d" +msgstr "སྔ་དྲོ།0%d:%d:%d" + +#: src/systemtime/libkydate.c:862 +#, c-format +msgid "am%d:0%d" +msgstr "སྔ་དྲོ།%d:0%d" + +#: src/systemtime/libkydate.c:865 +#, c-format +msgid "am%d:0%d:0%d" +msgstr "སྔ་དྲོ།%d:0%d:0%d" + +#: src/systemtime/libkydate.c:867 +#, c-format +msgid "am%d:0%d:%d" +msgstr "སྔ་དྲོ།%d:0%d:%d" + +#: src/systemtime/libkydate.c:870 +#, c-format +msgid "am%d:%d" +msgstr "སྔ་དྲོ།%d:%d" + +#: src/systemtime/libkydate.c:873 +#, c-format +msgid "am%d:%d:0%d" +msgstr "སྔ་དྲོ།%d:%d:0%d" + +#: src/systemtime/libkydate.c:875 +#, c-format +msgid "am%d:%d:%d" +msgstr "སྔ་དྲོ།%d:%d:%d" + +#: src/systemtime/libkydate.c:1084 src/systemtime/libkydate.c:1386 +msgid "pm%I:%M" +msgstr "ཕྱི་དྲོ།%I:%M" + +#: src/systemtime/libkydate.c:1086 src/systemtime/libkydate.c:1388 +msgid "am%I:%M" +msgstr "སྔ་དྲོ།%I:%M" + +#: src/systemtime/libkydate.c:1214 +msgid "_Sun" +msgstr "གཟའ་ཉི་མ།" + +#: src/systemtime/libkydate.c:1217 +msgid "_Mon" +msgstr "གཟའ་ཟླ་བ།" + +#: src/systemtime/libkydate.c:1220 +msgid "_Tue" +msgstr "གཟའ་མིག་དམར།" + +#: src/systemtime/libkydate.c:1223 +msgid "_Wed" +msgstr "གཟའ་ལྷག་པ།" + +#: src/systemtime/libkydate.c:1226 +msgid "_Thu" +msgstr "གཟའ་ཕུར་བུ།" + +#: src/systemtime/libkydate.c:1229 +msgid "_Fri" +msgstr "གཟའ་པ་སངས།" + +#: src/systemtime/libkydate.c:1232 +msgid "_Sat" +msgstr "གཟའ་སྤེན་པ།" + +#: src/systemtime/libkydate.c:1263 +msgid "_Sunday" +msgstr "གཟའ་ཉི་མ།" + +#: src/systemtime/libkydate.c:1266 +msgid "_Monday" +msgstr "གཟའ་ཟླ་བ།" + +#: src/systemtime/libkydate.c:1269 +msgid "_Tuesday" +msgstr "གཟའ་མིག་དམར།" + +#: src/systemtime/libkydate.c:1272 +msgid "_Wednesday" +msgstr "གཟའ་ལྷག་པ།" + +#: src/systemtime/libkydate.c:1275 +msgid "_Thursday" +msgstr "གཟའ་ཕུར་བུ།" + +#: src/systemtime/libkydate.c:1278 +msgid "_Friday" +msgstr "གཟའ་པ་སངས།" + +#: src/systemtime/libkydate.c:1281 +msgid "_Saturday" +msgstr "གཟའ་སྤེན་པ།" + +#: src/systemtime/libkydate.c:1330 +msgid "pm%I:%M:%S" +msgstr "ཕྱི་དྲོ།%I:%M:%S" + +#: src/systemtime/libkydate.c:1332 +msgid "am%I:%M:%S" +msgstr "སྔ་དྲོ།%I:%M:%S" + +#: src/systemtime/libkydate.c:1483 +#, c-format +msgid "%s_year%s_mon%s_day" +msgstr "%sལོ།%sཟླ།%sཉིན།" diff --git a/po/zh_CN.mo b/po/zh_CN.mo new file mode 100644 index 0000000..98f363a Binary files /dev/null and b/po/zh_CN.mo differ diff --git a/po/zh_CN.po b/po/zh_CN.po new file mode 100644 index 0000000..e963e0e --- /dev/null +++ b/po/zh_CN.po @@ -0,0 +1,265 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-08-16 14:41+0800\n" +"PO-Revision-Date: 2022-08-19 09:56+0800\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.3\n" +"Last-Translator: \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Language: zh_CN\n" + +#: src/systemtime/libkydate.c:109 src/systemtime/libkydate.c:1162 +#: src/systemtime/libkydate.c:1435 src/systemtime/date.c:104 +msgid "%Y_year%m_mon%d_day" +msgstr "%Y年%m月%d日" + +#: src/systemtime/libkydate.c:126 src/systemtime/libkydate.c:603 +#: src/systemtime/libkydate.c:696 src/systemtime/libkydate.c:1174 +#: src/systemtime/libkydate.c:1447 src/systemtime/date.c:82 +#: src/systemtime/libkydate.c:1494 +#, c-format +msgid "%d_year%d_mon%d_day" +msgstr "%d年%d月%d日" + +#: src/systemtime/libkydate.c:445 +#, c-format +msgid "**year**mon**day" +msgstr "**年**月**日" + +#: src/systemtime/libkydate.c:456 +#, c-format +msgid "*year*mon*day" +msgstr "*年*月*日" + +#: src/systemtime/libkydate.c:486 src/systemtime/libkydate.c:499 +#: src/systemtime/date.c:115 src/systemtime/date.c:124 +#, c-format +msgid "24-hour clock" +msgstr "24小时制" + +#: src/systemtime/libkydate.c:507 src/systemtime/date.c:131 +#, c-format +msgid "12-hour clock" +msgstr "12小时制" + +#: src/systemtime/libkydate.c:687 +#, c-format +msgid "%d_year0%d_mon0%d_day" +msgstr "%d年0%d月0%d日" + +#: src/systemtime/libkydate.c:689 +#, c-format +msgid "%d_year0%d_mon%d_day" +msgstr "%d年0%d月%d日" + +#: src/systemtime/libkydate.c:694 +#, c-format +msgid "%d_year%d_mon0%d_day" +msgstr "%d年%d月0%d日" + +#: src/systemtime/libkydate.c:778 src/systemtime/libkydate.c:820 +#, c-format +msgid "pm%d:0%d" +msgstr "下午%d:0%d" + +#: src/systemtime/libkydate.c:781 src/systemtime/libkydate.c:823 +#, c-format +msgid "pm%d:0%d:0%d" +msgstr "下午%d:0%d:0%d" + +#: src/systemtime/libkydate.c:784 src/systemtime/libkydate.c:825 +#, c-format +msgid "pm%d:0%d:%d" +msgstr "下午%d:0%d:%d" + +#: src/systemtime/libkydate.c:787 src/systemtime/libkydate.c:828 +#, c-format +msgid "pm%d:%d" +msgstr "下午%d:%d" + +#: src/systemtime/libkydate.c:790 src/systemtime/libkydate.c:831 +#, c-format +msgid "pm%d:%d:0%d" +msgstr "下午%d:%d:0%d" + +#: src/systemtime/libkydate.c:793 src/systemtime/libkydate.c:833 +#, c-format +msgid "pm%d:%d:%d" +msgstr "下午%d:%d:%d" + +#: src/systemtime/libkydate.c:801 +#, c-format +msgid "pm0%d:0%d" +msgstr "下午0%d:0%d" + +#: src/systemtime/libkydate.c:804 +#, c-format +msgid "pm0%d:0%d:0%d" +msgstr "下午0%d:0%d:0%d" + +#: src/systemtime/libkydate.c:806 +#, c-format +msgid "pm0%d:0%d:%d" +msgstr "下午0%d:0%d:%d" + +#: src/systemtime/libkydate.c:809 +#, c-format +msgid "pm0%d:%d" +msgstr "下午0%d:%d" + +#: src/systemtime/libkydate.c:812 +#, c-format +msgid "pm0%d:%d:0%d" +msgstr "下午0%d:%d:0%d" + +#: src/systemtime/libkydate.c:814 +#, c-format +msgid "pm0%d:%d:%d" +msgstr "下午0%d:%d:%d" + +#: src/systemtime/libkydate.c:843 +#, c-format +msgid "am0%d:0%d" +msgstr "上午0%d:0%d" + +#: src/systemtime/libkydate.c:846 +#, c-format +msgid "am0%d:0%d:0%d" +msgstr "上午0%d:0%d:0%d" + +#: src/systemtime/libkydate.c:848 +#, c-format +msgid "am0%d:0%d:%d" +msgstr "上午0%d:0%d:%d" + +#: src/systemtime/libkydate.c:851 +#, c-format +msgid "am0%d:%d" +msgstr "上午0%d:%d" + +#: src/systemtime/libkydate.c:854 +#, c-format +msgid "am0%d:%d:0%d" +msgstr "上午0%d:%d:0%d" + +#: src/systemtime/libkydate.c:856 +#, c-format +msgid "am0%d:%d:%d" +msgstr "上午0%d:%d:%d" + +#: src/systemtime/libkydate.c:862 +#, c-format +msgid "am%d:0%d" +msgstr "上午%d0%d" + +#: src/systemtime/libkydate.c:865 +#, c-format +msgid "am%d:0%d:0%d" +msgstr "上午%d:0%d:0%d" + +#: src/systemtime/libkydate.c:867 +#, c-format +msgid "am%d:0%d:%d" +msgstr "上午%d:0%d:%d" + +#: src/systemtime/libkydate.c:870 +#, c-format +msgid "am%d:%d" +msgstr "上午%d:%d" + +#: src/systemtime/libkydate.c:873 +#, c-format +msgid "am%d:%d:0%d" +msgstr "上午%d:%d:0%d" + +#: src/systemtime/libkydate.c:875 +#, c-format +msgid "am%d:%d:%d" +msgstr "上午%d:%d:%d" + +#: src/systemtime/libkydate.c:1084 src/systemtime/libkydate.c:1386 +msgid "pm%I:%M" +msgstr "下午%I:%M" + +#: src/systemtime/libkydate.c:1086 src/systemtime/libkydate.c:1388 +msgid "am%I:%M" +msgstr "上午%I:%M" + +#: src/systemtime/libkydate.c:1214 +msgid "_Sun" +msgstr "周日" + +#: src/systemtime/libkydate.c:1217 +msgid "_Mon" +msgstr "周一" + +#: src/systemtime/libkydate.c:1220 +msgid "_Tue" +msgstr "周二" + +#: src/systemtime/libkydate.c:1223 +msgid "_Wed" +msgstr "周三" + +#: src/systemtime/libkydate.c:1226 +msgid "_Thu" +msgstr "周四" + +#: src/systemtime/libkydate.c:1229 +msgid "_Fri" +msgstr "周五" + +#: src/systemtime/libkydate.c:1232 +msgid "_Sat" +msgstr "周六" + +#: src/systemtime/libkydate.c:1263 +msgid "_Sunday" +msgstr "星期日" + +#: src/systemtime/libkydate.c:1266 +msgid "_Monday" +msgstr "星期一" + +#: src/systemtime/libkydate.c:1269 +msgid "_Tuesday" +msgstr "星期二" + +#: src/systemtime/libkydate.c:1272 +msgid "_Wednesday" +msgstr "星期三" + +#: src/systemtime/libkydate.c:1275 +msgid "_Thursday" +msgstr "星期四" + +#: src/systemtime/libkydate.c:1278 +msgid "_Friday" +msgstr "星期五" + +#: src/systemtime/libkydate.c:1281 +msgid "_Saturday" +msgstr "星期六" + +#: src/systemtime/libkydate.c:1330 +msgid "pm%I:%M:%S" +msgstr "下午%I:%M:%S" + +#: src/systemtime/libkydate.c:1332 +msgid "am%I:%M:%S" +msgstr "上午%I:%M:%S" + +#: src/systemtime/libkydate.c:1483 +#, c-format +msgid "%s_year%s_mon%s_day" +msgstr "%s年%s月%s日" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84dcc86..3054fba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,10 @@ + +# add_custom_target(PREBUILDER ALL) +# add_custom_command(TARGET PREBUILDER +# PRE_BUILD +# COMMAND sh ${PROJECT_SOURCE_DIR}/src/realtime/smartctl/configure +# WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/realtime/smartctl) + add_subdirectory(disk) add_subdirectory(systeminfo) add_subdirectory(systemtime) @@ -8,5 +15,10 @@ add_subdirectory(filesystem) add_subdirectory(powermanagement) add_subdirectory(kdkocr) add_subdirectory(location) +add_subdirectory(logrotate) add_subdirectory(net) -add_subdirectory(realtime) \ No newline at end of file +add_subdirectory(realtime) +add_subdirectory(kysdkdbus) +add_subdirectory(libkysdk-system-javascript-http) +add_subdirectory(libkysdk-system-javascript-websocket) +add_subdirectory(processmanage) diff --git a/src/disk/libkydiskinfo.c b/src/disk/libkydiskinfo.c index 6dfdba4..31cd2b8 100644 --- a/src/disk/libkydiskinfo.c +++ b/src/disk/libkydiskinfo.c @@ -115,7 +115,7 @@ static int get_disk_space(int fd, kdk_diskinfo *di) if (get_disk_size(fd, &bytes)) { klog_err("磁盘容量获取失败\n"); - printf("Unable to get disk size.\n"); + klog_err("Unable to get disk size.\n"); return -1; } di->sectors_num = (bytes >> 9); @@ -244,18 +244,23 @@ static int get_disk_type(kdk_diskinfo *di) // 获取接口类型 // 获取磁盘类型 - if (isdigit(di->name[strlen(di->name)])) // 分区盘 - { - di->disk_type = DISK_TYPE_NONE; - goto out; - } - else - { + // if (isdigit(di->name[strlen(di->name)])) // 分区盘 + // { + // di->disk_type = DISK_TYPE_NONE; + // goto out; + // } + // else + // { #ifdef __linux__ const char *p_name = di->name; p_name += strlastof(p_name, '/'); char syspath[PATH_MAX]; sprintf(syspath, "/sys/block/%s/queue/rotational", p_name); + if(0 != access(syspath,0)) + { + di->disk_type = DISK_TYPE_NONE; + goto out; + } int tfd = open(syspath, O_RDONLY); if (! tfd) { @@ -273,13 +278,14 @@ static int get_disk_type(kdk_diskinfo *di) if (di->disk_type == DISK_TYPE_SSD) goto out; - } + // } // 获取转速信息(固态除外) out: + close(tfd); return 0; } @@ -420,7 +426,7 @@ kdk_diskinfo *kdk_get_diskinfo(const char *diskname) serial_val = udev_device_get_sysattr_value(dev,"serial"); // klog_debug("Dev DeviceDevPath: %s\n", udev_device_get_syspath(dev)); // klog_debug("Dev DeviceSerial: %s\n", serial_val); - res->serial = strdup(serial_val); + res->serial = strdup(serial_val ? serial_val : "None"); } else { @@ -442,7 +448,7 @@ kdk_diskinfo *kdk_get_diskinfo(const char *diskname) // klog_debug("Dev_P DeviceDevPath: %s\n", udev_device_get_syspath(serial)); } // klog_debug("Dev_P DeviceSerial: %s\n", serial_val); - res->serial = strdup(serial_val); + res->serial = strdup(serial_val ? serial_val : "None"); res->model = strdup(udev_device_get_sysattr_value(dev_P,"model")); } else if (udev_device_get_sysattr_value(dev_PP,"model")) @@ -459,7 +465,7 @@ kdk_diskinfo *kdk_get_diskinfo(const char *diskname) // klog_debug("DeviceDevPath: %s\n", udev_device_get_syspath(serial)); } // klog_debug("DeviceSerial: %s\n", serial_val); - res->serial = strdup(serial_val); + res->serial = strdup(serial_val ? serial_val : "None"); res->model = strdup(udev_device_get_sysattr_value(dev_PP,"model")); } else{ @@ -479,6 +485,7 @@ kdk_diskinfo *kdk_get_diskinfo(const char *diskname) char buf[10] = {0}; read(tfd, buf, FWREV_SIZE); strcpy(res->fwrev, buf); + close(tfd); } } @@ -517,4 +524,4 @@ void kdk_free_diskinfo(kdk_diskinfo *disk) SAFE_FREE(disk->mount_path); SAFE_FREE(disk->fwrev); SAFE_FREE(disk); -} \ No newline at end of file +} diff --git a/src/hardware/CMakeLists.txt b/src/hardware/CMakeLists.txt index 46fe4d6..c38215a 100644 --- a/src/hardware/CMakeLists.txt +++ b/src/hardware/CMakeLists.txt @@ -1,5 +1,8 @@ aux_source_directory(. SOURCESCODE) include_directories(.) +include(FindPkgConfig) +pkg_check_modules(GLIB REQUIRED glib-2.0) +include_directories(${GLIB_INCLUDE_DIRS}) add_library(kyhw SHARED ${SOURCESCODE}) set_target_properties(kyhw PROPERTIES VERSION 2.0.0 SOVERSION 1) add_executable(kync-test test/kync-test.c) @@ -10,7 +13,7 @@ add_executable(kyusb-test test/kyusb-test.c) add_executable(kybios-test test/kybios-test.c) add_executable(kyboard-test test/kyboard-test.c) # 额外链接的库在这一行 -target_link_libraries(kyhw kylog kyconf pthread systemd cups curl udev) +target_link_libraries(kyhw kylog kyconf pthread systemd cups curl udev ${GLIB_LIBRARIES}) target_link_libraries(kyprinterprint-test kyhw) target_link_libraries(kyprinterlist-test kyhw) target_link_libraries(kync-test kyhw) @@ -38,4 +41,4 @@ install(FILES libkyboard.h DESTINATION include/kysdk/kysdk-system) install(FILES libkyusb.h - DESTINATION include/kysdk/kysdk-system) \ No newline at end of file + DESTINATION include/kysdk/kysdk-system) diff --git a/src/hardware/libkyboard.c b/src/hardware/libkyboard.c index 39738fe..62d855d 100644 --- a/src/hardware/libkyboard.c +++ b/src/hardware/libkyboard.c @@ -5,11 +5,11 @@ const char *kdk_board_get_name() { - char *name = (char *)calloc(32,sizeof(char)); + char *name = (char *)calloc(64,sizeof(char)); FILE *fd = fopen("/sys/class/dmi/id/board_name","r"); if(fd) { - fgets(name,32,fd); + fgets(name,64,fd); fclose(fd); } return name; @@ -17,11 +17,11 @@ const char *kdk_board_get_name() const char *kdk_board_get_date() { - char *date = (char *)calloc(32,sizeof(char)); + char *date = (char *)calloc(64,sizeof(char)); FILE *fd = fopen("/sys/class/dmi/id/bios_date","r"); if(fd) { - fgets(date,32,fd); + fgets(date,64,fd); fclose(fd); } return date; @@ -29,11 +29,11 @@ const char *kdk_board_get_date() const char *kdk_board_get_serial() { - char *serial = (char *)calloc(32,sizeof(char)); + char *serial = (char *)calloc(64,sizeof(char)); FILE *fd = fopen("/sys/class/dmi/id/board_serial","r"); if(fd) { - fgets(serial,32,fd); + fgets(serial,64,fd); fclose(fd); } return serial; @@ -41,11 +41,11 @@ const char *kdk_board_get_serial() const char *kdk_board_get_vendor() { - char *vendor = (char *)calloc(32,sizeof(char)); + char *vendor = (char *)calloc(64,sizeof(char)); FILE *fd = fopen("/sys/class/dmi/id/board_vendor","r"); if(fd) { - fgets(vendor,32,fd); + fgets(vendor,64,fd); fclose(fd); } return vendor; @@ -54,4 +54,4 @@ const char *kdk_board_get_vendor() void kdk_board_free(char* info) { free(info); -} \ No newline at end of file +} diff --git a/src/hardware/libkycpu.c b/src/hardware/libkycpu.c index f69b4da..5043c48 100644 --- a/src/hardware/libkycpu.c +++ b/src/hardware/libkycpu.c @@ -365,9 +365,12 @@ static void _get_cpu_info() } fclose(fp); - if(strstr(cpuinf->model, "Loongson")) + if(cpuinf->model) { - cpuinf->vendor = strdup("loongson"); + if(strstr(cpuinf->model, "Loongson")) + { + cpuinf->vendor = strdup("loongson"); + } } if (cpuinf->flags) @@ -471,4 +474,4 @@ const char* kdk_cpu_get_virt() unsigned int kdk_cpu_get_process() { GET_VALUE(cpu_process, 0); -} \ No newline at end of file +} diff --git a/src/hardware/libkync.c b/src/hardware/libkync.c index ed3e8ee..0ce42b1 100644 --- a/src/hardware/libkync.c +++ b/src/hardware/libkync.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include enum cardspec{ NCSPEC_ALL, @@ -42,6 +44,140 @@ enum cardcfg{ #define SIOCGIWNAME 0x8B01 #define PCIID_PATH "/usr/share/misc/pci.ids" +static GKeyFile * +configure_open_file(const gchar *filename) +{ + GError *err = NULL; + GKeyFile *keyfile; + keyfile = g_key_file_new(); + g_key_file_set_list_separator(keyfile, ','); + if (!g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &err)) + { + g_error_free(err); + g_key_file_free(keyfile); + return NULL; + } + return keyfile; +} + +static void +configure_read_string(GKeyFile *keyfile, GString *ret, gchar *sec, gchar *key, gchar *default_val) +{ + GError *err = NULL; + gchar *val = g_key_file_get_string(keyfile, + sec, + key, + &err); + if (err) + { + g_string_assign(ret, default_val); + g_error_free(err); + return; + } + g_string_assign(ret, val); + g_free(val); +} + +static char ** +get_ip_list(enum cardcfg type,const char *nc) +{ + int count = 0; + char curAddr[64] = "\0"; + char **result = NULL; + DIR *dir = NULL; + struct dirent *file; + char *path = "/etc/NetworkManager/system-connections/"; + char *value = NULL; + if (type == NCCFG_IPv4) + { + value = "ipv4"; + if (kdk_nc_get_private_ipv4(nc)) + strcpy(curAddr, kdk_nc_get_private_ipv4(nc)); + } + else if(type == NCCFG_IPv6) + { + value = "ipv6"; + if (kdk_nc_get_private_ipv6(nc)) + strcpy(curAddr, kdk_nc_get_private_ipv6(nc)); + } + else + value = NULL; + + if ((dir = opendir(path)) == NULL) + { + klog_err("opendir %s failed!", dir); + return NULL; + } + + //网卡当前地址加入返回列表 + if (curAddr && strcmp(curAddr,"")) + { + result = (char **)calloc(++count + 1, sizeof(char *)); + if(!result) + goto err_out; + result[count - 1] = (char *)calloc(strlen(curAddr) + 1, sizeof(char)); + if(!result[count - 1]) + goto err_out; + strncpy(result[count - 1], curAddr, strlen(curAddr)); + } + + while (file = readdir(dir)) + { + // 判断是否为文件 + if (file->d_type != 8) + continue; + // 为文件加上相对路径 + char fileName[512] = "\0"; + strncpy(fileName, path, strlen(path)); + strcat(fileName, file->d_name); + + GKeyFile *keyFile = configure_open_file(fileName); + if (NULL == keyFile) + { + klog_err("load config file %s failed\n", fileName); + continue; + } + + GString *val = g_string_new(""); + configure_read_string(keyFile, val, "connection", "interface-name", "null"); + + if (0 == strcmp(val->str, nc)) + { + configure_read_string(keyFile, val, value, "address1", "null"); + if (0 != strcmp(val->str, "null")) + { + int i = -1; + while(val->str[++i] != 0x2f); // 0x2f是'/'的ascii码值 + val->str[i] = '\0'; + if (0 != strcmp(curAddr, val->str)) + { + result = (char **)realloc(result, (++count + 1) * sizeof(char *)); + if(!result) + goto err_out; + result[count - 1] = (char *)calloc(i + 1, sizeof(char)); + if(!result[count - 1]) + goto err_out; + strncpy(result[count - 1], val->str, i); + result[count] = NULL; + } + } + } + g_string_free(val,TRUE); + g_key_file_free(keyFile); + } + closedir(dir); + return result; +err_out: + while (result && count) + { + free(result[count - 1]); + result[count - 1] = NULL; + } + free(result); + closedir(dir); + return NULL; +} + static enum cardstat _get_card_stat(const char *nc) { int sfd = socket(AF_INET, SOCK_DGRAM, 0); @@ -170,6 +306,7 @@ char **_get_nc_cfg(const char *nc, enum cardcfg cfg) res[0] = malloc(sizeof(char) * NC_MAC_SIZE); if (!res[0]) { + free(res); klog_err("内存申请失败:%s\n", strerror(errno)); close(sfd); return NULL; @@ -199,6 +336,7 @@ char **_get_nc_cfg(const char *nc, enum cardcfg cfg) res[0] = malloc(sizeof(char) * NC_IPv4_SIZE); if (!res[0]) { + free(res); klog_err("内存申请失败:%s\n", strerror(errno)); close(sfd); return NULL; @@ -233,6 +371,7 @@ char **_get_nc_cfg(const char *nc, enum cardcfg cfg) res[0] = malloc(sizeof(char) * INET6_ADDRSTRLEN); if (!res[0]) { + free(res); klog_err("内存申请失败:%s\n", strerror(errno)); close(sfd); freeifaddrs(ifap); @@ -303,6 +442,13 @@ char* kdk_nc_get_private_ipv4(const char *nc) return ipv4; } +char **kdk_nc_get_ipv4(const char *nc) +{ + if(!nc) + return NULL; + return get_ip_list(NCCFG_IPv4, nc); +} + char* kdk_nc_get_private_ipv6(const char *nc) { if (!nc) @@ -316,17 +462,89 @@ char* kdk_nc_get_private_ipv6(const char *nc) return ipv6; } +char **kdk_nc_get_ipv6(const char *nc) +{ + if (!nc) + return NULL; + return get_ip_list(NCCFG_IPv6, nc); +} + int kdk_nc_is_wireless(const char *nc) { if (!nc) return -1; - char buffer[2 * IFNAMSIZ + 1]; - int fd = socket(PF_INET, SOCK_DGRAM, 0); - if (fd < 0) + int i = 0, ret = -1; + char **list = kdk_nc_get_cardlist(); + if(!list) + goto out; + while(list[i]) + { + if(0 == strcmp(nc, list[i++])) + { + char buffer[2 * IFNAMSIZ + 1]; + int fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -1; + memset(buffer, 0, sizeof(buffer)); + strncpy(buffer, nc, sizeof(buffer)); + ret = ioctl(fd, SIOCGIWNAME, &buffer) == 0 ? 1 : 0; + close(fd); + break; + } + } +out: + kdk_nc_freeall(list); + return ret; +} + +//根据mac地址获取从hwinfo --usb中获取指定网卡的vendor和device +static int __hwinfo_usb(const char * mac,char * vendor, char *product) +{ + FILE * fp = popen("hwinfo --usb","r"); + if(!fp) return -1; - memset(buffer, 0, sizeof(buffer)); - strncpy(buffer, nc, sizeof(buffer)); - return ioctl(fd, SIOCGIWNAME, &buffer) == 0 ? 1 : 0; + char line[1024] = "\0"; + char ven[256] = "\0"; + char pro[256] = "\0"; + int flags = 0; + while(fgets(line,1024,fp)) + { + int index = 0; + if (flags && (0 < strlen(ven)) && (0 < strlen(pro))) + { + break; + } + + if (strstr(line, "HW Address") && strstr(line, mac)) + { + flags = 1; + } + + if(strstr(line, "Vendor:")) + { + // 返回数据为Vendor: usb 0xxxxx "Linux Foundation",取最后双引号中的内容 + strcpy(ven, &line[22]); + } + + if (strstr(line, "Device:")) + { + // 返回数据为Device: usb 0xxxxx "2.0 root hub",取最后双引号中的内容 + strcpy(pro, &line[22]); + } + + if(0 < sscanf(line,"%d:%*s",&index)) + { + memset(ven,0,256); + memset(pro,0,256); + } + } + if(flags) + { + strncpy(vendor, ven, strlen(ven) - 2); + strncpy(product, pro, strlen(pro) - 2); + } + fclose(fp); + return 0; } int kdk_nc_get_vendor_and_product(const char *nc, char *vendor, char *product) @@ -338,6 +556,27 @@ int kdk_nc_get_vendor_and_product(const char *nc, char *vendor, char *product) if (0 != access(filename, F_OK)) return -1; //非物理网卡,可能是逻辑网卡如 lo: 本地环回 FILE *fp = NULL; + + + //usb网卡通过hwinfo获取 + char ncType[64] = "\0"; + sprintf(ncType, "%s/%s", filename, "uevent"); + if(NULL != (fp = fopen(ncType,"r"))) + { + char text[1024] = "\0"; + if (0 > fread(text, sizeof(char), 1024, fp)) + { + klog_err("open %s failed", ncType); + return -1; + } + if (strstr(text, "usb_interface")) + { + char *mac = kdk_nc_get_phymac(nc); + int ret = __hwinfo_usb(mac,vendor,product); + free(mac); + return ret; + } + } char vendorId[64] = "0"; char productId[64] = "0"; strcpy(vendorId, filename); @@ -355,10 +594,14 @@ int kdk_nc_get_vendor_and_product(const char *nc, char *vendor, char *product) fclose(fp); } - long u[4]; // 0,1存储网卡的厂家和设备ID,2,3存储pci.ids文件读到的ID - memset(u, 0, sizeof(u)); - sscanf(vendorId, "%lx", &u[0]); + long u[4] = {0,1,2,3}; // 0,1存储网卡的厂家和设备ID,2,3存储pci.ids文件读到的ID + // memset(u, 0, sizeof(u)); + int count = sscanf(vendorId, "%lx", &u[0]); + if(count < 1) + return -1; sscanf(productId, "%lx", &u[1]); + if (count < 1) + return -1; char line[512] = "0"; if (NULL != (fp = fopen(PCIID_PATH, "r"))) @@ -392,6 +635,7 @@ int kdk_nc_get_vendor_and_product(const char *nc, char *vendor, char *product) } } } + fclose(fp); } return 0; } @@ -407,4 +651,4 @@ inline void kdk_nc_freeall(char **list) index ++; } free(list); -} \ No newline at end of file +} diff --git a/src/hardware/libkync.h b/src/hardware/libkync.h index 6a3950c..8c70531 100644 --- a/src/hardware/libkync.h +++ b/src/hardware/libkync.h @@ -72,6 +72,14 @@ extern char** kdk_nc_get_ipv4(const char* nc); */ extern char* kdk_nc_get_private_ipv6(const char *nc); +/** + * @brief 获取指定网卡的所有IPv6地址 + * + * @param nc 网卡名称,如eno1 + * @return char** IPv6地址列表,以NULL表示结尾,由alloc生成,需要被kdk_nc_freeall回收;若获取出错,返回NULL; + */ +extern char **kdk_nc_get_ipv6(const char *nc); + /** * @brief 获取指定网卡的有线/无线类型 * @@ -90,14 +98,6 @@ extern int kdk_nc_is_wireless(const char *nc); */ extern int kdk_nc_get_vendor_and_product(const char *nc, char *vendor, char *product); -/** - * @brief 获取指定网卡的所有IPv6地址 - * - * @param nc 网卡名称,如eno1 - * @return char** IPv6地址列表,以NULL表示结尾,由alloc生成,需要被kdk_nc_freeall回收;若获取出错,返回NULL; - */ -extern char** kdk_nc_get_ipv6(const char *nc); - /** * @brief 用于回收字符串列表 * @@ -112,4 +112,4 @@ extern inline void kdk_nc_freeall(char **ptr); #endif /** * @} - */ \ No newline at end of file + */ diff --git a/src/hardware/libkyprinter.c b/src/hardware/libkyprinter.c index 4ce1105..062f5a6 100644 --- a/src/hardware/libkyprinter.c +++ b/src/hardware/libkyprinter.c @@ -13,6 +13,7 @@ #include #include #include +#include #define _IPP_PRIVATE_STRUCTURES 1 @@ -127,7 +128,7 @@ void kdk_printer_set_options(int number_up, num_options = cupsAddOption("sides", sides, num_options, &cups_options); } -int kdk_printer_print_local_file(const char *printername, char *filepath) +int kdk_printer_print_local_file(const char *printername, const char *filepath) { printf("[%s] Start\n", __FUNCTION__); printf("[%s] printer_name: %s, file_path: %s\n", __FUNCTION__, @@ -151,7 +152,7 @@ int kdk_printer_cancel_all_jobs(const char *printername) int ret = cupsCancelJob(printername, CUPS_JOBID_ALL); // 1=success 0=fail if (ret == 0) { - printf(LOG_ERR, "%s ,job_id = CUPS_JOBID_ALL, find err = %s ", __FUNCTION__, cupsLastErrorString()); + printf("%s ,job_id = CUPS_JOBID_ALL, find err = %s ", __FUNCTION__, cupsLastErrorString()); return -1; } return 0; @@ -381,9 +382,11 @@ size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) return written; } //获取url内的文件名 -void kdk_printer_get_filename(const char *url, char *filename) +char* kdk_printer_get_filename(const char *url) { printf("[%s] Start\n", __FUNCTION__); + char *filename = malloc(strlen(url) + 1); + memset(filename, 0, sizeof(url)); int len = strlen(url); int i; for (i = (len - 1); i >= 0; i--) @@ -394,10 +397,10 @@ void kdk_printer_get_filename(const char *url, char *filename) } } strcpy(filename, &url[i + 1]); - return; + return filename; } //检查url是否存在 -bool check_url(char *url) +int check_url(char *url) { printf("[%s] Start\n", __FUNCTION__); // 获得一个CURL会话,进行网络操作 @@ -423,11 +426,11 @@ bool check_url(char *url) // 根据代码判断网址是否有效 int retcode = 0; curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &retcode); - bool res = false; + int res = URL_UNAVAILABLE; // 如果HTTP反应代码为200,表示网址有效 if (200 == retcode) { - res = true; + res = 0; } // 执行会话的清理工作 curl_easy_cleanup(handle); @@ -436,22 +439,36 @@ bool check_url(char *url) } else // 无法验证 { - return false; + return CAN_NOT_CHECK_URL; } } -//下载url到文件 +// 下载url到文件 int download_file(char *url, const char *savefile) { printf("[%s] Start\n", __FUNCTION__); CURL *curl; - FILE *fp; + FILE *fp = NULL; CURLcode res; printf("[%s] url = %s \n", __FUNCTION__, url); printf("[%s] savefile = %s \n", __FUNCTION__, savefile); + //检查保存路径是否可用 + fp = fopen(savefile, "wb"); + printf("[%s] fp = %d \n", __FUNCTION__, fp); + if (!fp) + { + switch (errno) + { + case EACCES: // you don't have permission + return PATH_NO_PERMISSION; + case ENOENT: // the file doesn't exists + return PATH_NOT_EXIST; + default: + return PATH_ERROR; + } + } curl = curl_easy_init(); if (curl) { - fp = fopen(savefile, "wb"); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); @@ -462,8 +479,8 @@ int download_file(char *url, const char *savefile) res = curl_easy_perform(curl); /* always cleanup */ curl_easy_cleanup(curl); - fclose(fp); } + fclose(fp); return res; } @@ -472,17 +489,19 @@ int kdk_printer_print_download_remote_file(const char *url, const char *filepath printf("[%s] Start\n", __FUNCTION__); int res = -1; - bool available = check_url(url); - if (available) + int available = check_url(url); + if (available == 0) { res = download_file(url, filepath); + printf("res = %d.\n", res); + return res; } else { printf("%s seems to be unavailable.\n", url); + return available; } - printf("res = %d.\n", res); - return res; + } int kdk_printer_get_job_status(const char *printername, int jobid) @@ -492,8 +511,9 @@ int kdk_printer_get_job_status(const char *printername, int jobid) int i; int number_jobs; int ret; - - number_jobs = cupsGetJobs(&jobs, printername, 1, CUPS_WHICHJOBS_ALL); + // cupsGetJobs函数的第三个参数应该是0而不是1 + // 因为1表示只获取正在进行中的任务,而0表示获取所有任务,包括已完成的和已停止的 + number_jobs = cupsGetJobs(&jobs, printername, 0, CUPS_WHICHJOBS_ALL); ipp_jstate_t state = 0; for (i = 0; i < number_jobs; i++) @@ -508,4 +528,5 @@ int kdk_printer_get_job_status(const char *printername, int jobid) cupsFreeJobs(number_jobs, jobs); return state; -} \ No newline at end of file +} + diff --git a/src/hardware/libkyprinter.h b/src/hardware/libkyprinter.h index 024fcf1..7e3fc1b 100644 --- a/src/hardware/libkyprinter.h +++ b/src/hardware/libkyprinter.h @@ -1,5 +1,5 @@ -#ifndef KDK_SYSTEM_HW_NWCARD_H__ -#define KDK_SYSTEM_HW_NWCARD_H__ +#ifndef LIBKYPRINTER_H +#define LIBKYPRINTER_H //打印机状态 #define CUPS_PRINTER_STATE_BUSY 6 //"Busy" #define CUPS_PRINTER_STATE_STOPPED 4 //"Stop" @@ -15,6 +15,13 @@ #define IPP_JOB_ABORTED 8 //由于错误跳过该打印任务 #define IPP_JOB_COMPLETED 9 //正常完成 +//下载远程相关状态 +#define URL_UNAVAILABLE -5 +#define CAN_NOT_CHECK_URL -6 +#define PATH_NOT_EXIST -7 +#define PATH_NO_PERMISSION -8 +#define PATH_ERROR -9 + //下载状态 // typedef enum { // CURLE_OK = 0, @@ -207,7 +214,7 @@ extern int kdk_printer_print_download_remote_file(const char *url, const char *f * 然后把a.log改名叫a.pdf,这个时候a.pdf就变成了格式不对的文件,打印不出来了 * */ -extern int kdk_printer_print_local_file(const char *printername, char *filepath); +extern int kdk_printer_print_local_file(const char *printername, const char *filepath); /** * @brief 清除某个打印机的所有打印队列 @@ -241,12 +248,11 @@ extern int kdk_printer_get_job_status(const char *printername, int jobid); /** * @brief 获取url内的文件名 * - * @param url - * @param filename 解析出来的文件名 + * @param url * - * @return 无 + * @return 解析出来的函数名 */ -extern void kdk_printer_get_filename(const char *url, char *filename); +extern char* kdk_printer_get_filename(const char *url); #ifdef __cplusplus } @@ -255,4 +261,4 @@ extern void kdk_printer_get_filename(const char *url, char *filename); #endif /** * @} - */ \ No newline at end of file + */ diff --git a/src/hardware/libkyusb.c b/src/hardware/libkyusb.c index c709585..aae0e97 100644 --- a/src/hardware/libkyusb.c +++ b/src/hardware/libkyusb.c @@ -2,6 +2,7 @@ #include #include #include +#include "libkylog.h" /// @brief /// @return usb device info @@ -20,7 +21,7 @@ pDriverInfoList kdk_usb_get_list() udev = udev_new(); if (!udev) { - printf("Can't create udev"); + klog_err("Can't create udev"); return list; } @@ -83,15 +84,15 @@ pDriverInfoList kdk_usb_get_list() //申请节点内存 if(NULL == list) { - list = (pDriverInfoList)calloc(1, sizeof(DriverInfoList)); - curnode = list; + curnode = (pDriverInfoList)calloc(1, sizeof(DriverInfoList)); + list = curnode; } else curnode = (pDriverInfoList)calloc(1, sizeof(DriverInfoList)); if (NULL == curnode) { - printf("Request Memory For List Failed"); + klog_err("Request Memory For List Failed"); kdk_usb_free(list); return NULL; } @@ -99,18 +100,18 @@ pDriverInfoList kdk_usb_get_list() curnode->data = (pDriverInfo)calloc(1, sizeof(DriverInfo)); if (NULL == curnode->data) { - printf("Request Memory For Data Failed"); + klog_err("Request Memory For Data Failed"); kdk_usb_free(list); return NULL; } - sprintf(curnode->data->name, "%s", udev_device_get_sysattr_value(dev, "product")); - sprintf(curnode->data->type, "%s", udev_device_get_sysattr_value(interface, "bInterfaceClass")); - sprintf(curnode->data->pid, "%s", udev_device_get_sysattr_value(dev, "idProduct")); - sprintf(curnode->data->vid, "%s", udev_device_get_sysattr_value(dev, "idVendor")); - sprintf(curnode->data->serialNo, "%s", udev_device_get_sysattr_value(dev, "serial")); + sprintf(curnode->data->name, "%s", udev_device_get_sysattr_value(dev, "product") ? udev_device_get_sysattr_value(dev, "product"): "null"); + sprintf(curnode->data->type, "%s", udev_device_get_sysattr_value(interface, "bInterfaceClass") ? udev_device_get_sysattr_value(interface, "bInterfaceClass") : "null"); + sprintf(curnode->data->pid, "%s", udev_device_get_sysattr_value(dev, "idProduct") ? udev_device_get_sysattr_value(dev, "idProduct") : "null"); + sprintf(curnode->data->vid, "%s", udev_device_get_sysattr_value(dev, "idVendor") ? udev_device_get_sysattr_value(dev, "idVendor") : "null"); + sprintf(curnode->data->serialNo, "%s", udev_device_get_sysattr_value(dev, "serial") ? udev_device_get_sysattr_value(dev, "serial") : "null"); sprintf(curnode->data->devNode ,"%s", udev_device_get_devnode(dev)); - // sprintf(curnode->data->vendor, "%s", udev_device_get_sysattr_value(dev, "manufacturer")); + // sprintf(curnode->data->vendor, "%s", udev_device_get_sysattr_value(dev, "manufacturer")); if (NULL != prevnode) prevnode->next = curnode; @@ -136,4 +137,4 @@ void kdk_usb_free(pDriverInfoList list) free(tmp->data); free(tmp); } -} \ No newline at end of file +} diff --git a/src/hardware/test/kybios-test.c b/src/hardware/test/kybios-test.c index 122381e..f8a2ad1 100644 --- a/src/hardware/test/kybios-test.c +++ b/src/hardware/test/kybios-test.c @@ -2,7 +2,11 @@ #include "../libkybios.h" int main() { - printf("vendor: %s",kdk_bios_get_vendor()); - printf("version: %s",kdk_bios_get_version()); + char *vendor = kdk_bios_get_vendor(); + char *version = kdk_bios_get_version(); + printf("vendor: %s", vendor); + printf("version: %s",version); + free(vendor); + free(version); return 0; -} \ No newline at end of file +} diff --git a/src/hardware/test/kyboard-test.c b/src/hardware/test/kyboard-test.c index e905318..bfb5bf3 100644 --- a/src/hardware/test/kyboard-test.c +++ b/src/hardware/test/kyboard-test.c @@ -3,9 +3,18 @@ int main() { - printf("name : %s", kdk_board_get_name()); - printf("vendor : %s", kdk_board_get_vendor()); - printf("date : %s", kdk_board_get_date()); - printf("serial : %s", kdk_board_get_serial()); + + char *name = kdk_board_get_name(); + char *vendor = kdk_board_get_vendor(); + char *date = kdk_board_get_date(); + char *serial = kdk_board_get_serial(); + printf("name : %s", name); + printf("vendor : %s", vendor); + printf("date : %s", date); + printf("serial : %s", serial); + free(name); + free(vendor); + free(date); + free(serial); return 0; -} \ No newline at end of file +} diff --git a/src/hardware/test/kync-test.c b/src/hardware/test/kync-test.c index eba8659..f47e147 100644 --- a/src/hardware/test/kync-test.c +++ b/src/hardware/test/kync-test.c @@ -12,11 +12,35 @@ int main() char *mac = kdk_nc_get_phymac(cards[index]); char *ipv4 = kdk_nc_get_private_ipv4(cards[index]); char *ipv6 = kdk_nc_get_private_ipv6(cards[index]); - printf("Card %zd: %s\tStatus: %s\tMac: %s\tIPv4: %s\tIPv6: %s\n", index + 1, cards[index], kdk_nc_is_up(cards[index]) == 1 ? "Up" : "Down", mac, ipv4,ipv6); + char vendor[256] = "\0", product[256] = "\0"; + kdk_nc_get_vendor_and_product(cards[index], vendor, product); + printf("Card %zd: %s\tStatus: %s\tMac: %s\tIPv4: %s\tIPv6: %s\t Vendor: %s\t Product: %s\t Type: %s\n", + index + 1, cards[index], kdk_nc_is_up(cards[index]) == 1 ? "Up" : "Down", + mac, ipv4, ipv6, vendor, product, + kdk_nc_is_wireless(cards[index]) ? "wireless" : "ethernet"); + + char **list4 = kdk_nc_get_ipv4(cards[index]); + int i = 0; + printf("AllIpV4: "); + while (list4 && list4[i]) + { + printf("%s\t", list4[i++]); + } + printf("\n"); + char **list6 = kdk_nc_get_ipv6(cards[index]); + printf("AllIpV6: "); + i = 0; + while (list6 && list6[i]) + { + printf("%s\t", list6[i++]); + } + printf("\n"); free(mac); free(ipv4); free(ipv6); - index ++; + free(list4); + free(list6); + index++; } kdk_nc_freeall(cards); char **upcards = kdk_nc_get_upcards(); @@ -25,8 +49,8 @@ int main() while (upcards[index]) { printf("Card %zd: %s\n", index + 1, upcards[index]); - index ++; + index++; } kdk_nc_freeall(upcards); return 0; -} \ No newline at end of file +} diff --git a/src/hardware/test/kyprinterlist-test.c b/src/hardware/test/kyprinterlist-test.c index 12659d1..e2b13fc 100644 --- a/src/hardware/test/kyprinterlist-test.c +++ b/src/hardware/test/kyprinterlist-test.c @@ -16,7 +16,7 @@ int main() kdk_nc_freeall(printers); //获取可用打印机列表 index = 0; - char **available_printers = kdk_printer_get_list(); + char **available_printers = kdk_printer_get_available_list(); while (available_printers[index]) { int status = kdk_printer_get_status(available_printers[index]); @@ -26,4 +26,4 @@ int main() kdk_nc_freeall(available_printers); return 0; -} \ No newline at end of file +} diff --git a/src/hardware/test/kyprinterprint-test.c b/src/hardware/test/kyprinterprint-test.c index 3f94d17..fcadae0 100644 --- a/src/hardware/test/kyprinterprint-test.c +++ b/src/hardware/test/kyprinterprint-test.c @@ -17,9 +17,7 @@ int main() //获取url对应的文件名 char *url = "http://www.cztouch.com/upfiles/soft/testpdf.pdf"; int res = -1; - char *filename = (char *)malloc(sizeof(url)); //申请一个缓冲区,存放接收到的文件内容 - memset(filename, 0, sizeof(url)); - kdk_printer_get_filename(url, filename); //从完整路径名中解析出文件名称,例如:/home/test/abc.txt,解析完成后为abc.txt + char *filename = kdk_printer_get_filename(url); //从完整路径名中解析出文件名称,例如:/home/test/abc.txt,解析完成后为abc.txt printf("filename = %s\n", filename); //设置打印参数 @@ -45,7 +43,8 @@ int main() } index++; + free(filename); } kdk_nc_freeall(printers); return 0; -} \ No newline at end of file +} diff --git a/src/kysdk-java/kysdk-java-ext/.gitattributes b/src/kysdk-java/kysdk-java-ext/.gitattributes new file mode 100644 index 0000000..3c200cd --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/.gitattributes @@ -0,0 +1 @@ +*.sql linguist-language=java diff --git a/src/kysdk-java/kysdk-java-ext/.gitignore b/src/kysdk-java/kysdk-java-ext/.gitignore new file mode 100644 index 0000000..598a77a --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/.gitignore @@ -0,0 +1,46 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml + +### JRebel ### +rebel.xml diff --git a/src/kysdk-java/kysdk-java-ext/README.md b/src/kysdk-java/kysdk-java-ext/README.md new file mode 100644 index 0000000..f0dd20d --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/README.md @@ -0,0 +1,80 @@ +### 目录结构 +```shell +. +├── event-listener +│ ├── pom.xml +│ ├── src +│ │ ├── main +│ │ │ ├── java +│ │ │ │ ├── cn +│ │ │ │ │ └── kylin +│ │ │ │ │ └── dbus +│ │ │ │ │ ├── client +│ │ │ │ │ │ └── DBusClient.java dbus连接器 +│ │ │ │ │ ├── enums +│ │ │ │ │ │ └── EventTypeEnum.java 事件枚举 +│ │ │ │ │ ├── event +│ │ │ │ │ │ ├── PresenceEvent.java +│ │ │ │ │ │ ├── ScreenSaverEvent.java +│ │ │ │ │ │ ├── UserNameEvent.java +│ │ │ │ │ │ └── UserSessionEvent.java +│ │ │ │ │ └── listener +│ │ │ │ │ ├── DBusListener.java 监听器 +│ │ │ │ │ ├── DBusListenerSupport.java 监听器扩展 +│ │ │ │ │ ├── EventResult.java 监听器返回值 +│ │ │ │ │ ├── PresnceStatusChangedListener.java 键盘监听器 +│ │ │ │ │ ├── ScreenSaverLockListener.java 锁屏监听器 +│ │ │ │ │ ├── ScreenSaverUnLockListener.java 解屏监听器 +│ │ │ │ │ ├── UserSessionNewListener.java 登录监听器 +│ │ │ │ │ └── UserSessionRemoveListener.java 注销监听器 +│ │ │ │ ├── com +│ │ │ │ │ └── kylin +│ │ │ │ │ └── SessionManager +│ │ │ │ │ └── User.java +│ │ │ │ └── org +│ │ │ │ ├── freedesktop +│ │ │ │ │ └── login1 +│ │ │ │ │ └── Manager.java +│ │ │ │ ├── gnome +│ │ │ │ │ └── SessionManager +│ │ │ │ │ └── Presence.java +│ │ │ │ └── ukui +│ │ │ │ └── ScreenSaver.java +│ │ │ └── resources +│ └── test +│ └── java +│ └── cn +│ └── kylin +│ └── dbus +│ └── EventTest.java +│ └── target +├── package +│ ├── build.sh +│ └── settings.xml +├── pom.xml +└── README.md + +``` + + +### 编译 +```shell +cd package +./build.sh +``` + +### 使用 +```java + //执行dubus操作 + new PresenceEvent().StatusChanged(); + new ScreenSaverEvent().Lock(); + new UserSessionEvent().SessionNew(); + //监听事件 + DBusListenerSupport listenerSupport = new DBusListenerSupport(); + EventResult lock= listenerSupport.triggerEvent(EventTypeEnum.LOCK); + EventResult unlock= listenerSupport.triggerEvent(EventTypeEnum.UNLOCK); + EventResult changed= listenerSupport.triggerEvent(EventTypeEnum.KEY_STATUS_CHANGED); +``` + +### 注意事项 +> 所有继承DBusInterface的接口实现,必须跟dbus的路径和方法名一致,大小写强校验 diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/pom.xml b/src/kysdk-java/kysdk-java-ext/kysdk-java/pom.xml new file mode 100644 index 0000000..e3f3428 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/pom.xml @@ -0,0 +1,61 @@ + + + + kylin.kysdk.java + kysdk-java-ext + 1.0.0 + + 4.0.0 + dbus + kysdk-java + 1.0.0 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 6 + 6 + + + + + + + + com.github.hypfvieh + dbus-java + 3.3.2 + + + com.github.hypfvieh + libmatthew + 0.8.3 + + + com.github.jnr + jffi + 1.3.9 + + + org.slf4j + slf4j-simple + 1.7.36 + + + org.projectlombok + lombok + 1.18.24 + provided + + + org.junit.jupiter + junit-jupiter + 5.9.0 + test + + + diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/bios.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/bios.java new file mode 100644 index 0000000..e38c69b --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/bios.java @@ -0,0 +1,9 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import java.util.List; + +public interface bios extends DBusInterface { + public String getBiosVendor(); + public String getBiosVersion(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/cpuinfo.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/cpuinfo.java new file mode 100644 index 0000000..4e22c4c --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/cpuinfo.java @@ -0,0 +1,20 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.types.UInt32; + +public interface cpuinfo extends DBusInterface { + public String getCpuArch(); + + public String getCpuVendor(); + + public String getCpuModel(); + + public String getCpuFreqMHz(); + + public UInt32 getCpuCorenums(); + + public String getCpuVirt(); + + public UInt32 getCpuProcess(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/disk.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/disk.java new file mode 100644 index 0000000..4eae7b6 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/disk.java @@ -0,0 +1,19 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.types.UInt32; +import org.freedesktop.dbus.types.UInt64; +import java.util.List; + +public interface disk extends DBusInterface { + public List getDiskList(); + public UInt32 getDiskSectorSize(String name); + public float getDiskTotalSizeMiB(String name); + public String getDiskModel(String name); + public String getDiskSerial(String name); + public UInt32 getDiskPartitionNums(String name); + public String getDiskType(String name); + public String getDiskVersion(String name); + public UInt64 getDiskSectorNum(String name); + +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/gps.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/gps.java new file mode 100644 index 0000000..d75b9c9 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/gps.java @@ -0,0 +1,7 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface gps extends DBusInterface { + public String getGPSInfo(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/mainboard.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/mainboard.java new file mode 100644 index 0000000..26d319a --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/mainboard.java @@ -0,0 +1,16 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface mainboard extends DBusInterface{ + + //输出:主板型号 + public String getMainboardName(); + //输出:发布日期 + public String getMainboardDate(); + //输出:主板序列号 + public String getMainboardSerial(); + //输出:主板厂商 + public String getMainboardVendor(); + +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/net.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/net.java new file mode 100644 index 0000000..2107c1a --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/net.java @@ -0,0 +1,11 @@ +package com.kylin.kysdk; + +import java.util.List; +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface net extends DBusInterface { + public int getPortState(int port); + public List getMultiplePortStat(int begin,int end); + public List getGatewayInfo(); + public List getFirewallState(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/netcard.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/netcard.java new file mode 100644 index 0000000..05bd7f8 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/netcard.java @@ -0,0 +1,18 @@ +package com.kylin.kysdk; + +import java.util.List; +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface netcard extends DBusInterface { + + public List getNetCardName(); + public int getNetCardUp(String nc); + public List getNetCardUpcards(); + public String getNetCardPhymac(String nc); + public String getNetCardPrivateIPv4(String nc); + public List getNetCardIPv4(String nc); + public String getNetCardPrivateIPv6(String nc); + public int getNetCardType(String nc); + public List getNetCardProduct(String nc); + public List getNetCardIPv6(String nc); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/packageinfo.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/packageinfo.java new file mode 100644 index 0000000..100267d --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/packageinfo.java @@ -0,0 +1,12 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import java.util.List; + +public interface packageinfo extends DBusInterface { + public List getPackageList(); + + public String getPackageVersion(String name); + + public int getPackageInstalled(String name,String version); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/peripheralsenum.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/peripheralsenum.java new file mode 100644 index 0000000..483ceaf --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/peripheralsenum.java @@ -0,0 +1,8 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import java.util.List; + +public interface peripheralsenum extends DBusInterface { + public List getAllUsbInfo(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/print.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/print.java new file mode 100644 index 0000000..1977c6f --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/print.java @@ -0,0 +1,25 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import java.util.List; + +public interface print extends DBusInterface { + public List getPrinterList(); + + public List getPrinterAvailableList(); + + public void setPrinterOptions(int page,String paperType,String cups,String sided); + + public int getPrinterPrintLocalFile(String printer,String file); + + public int getPrinterCancelAllJobs(String printer); + + public int getPrinterStatus(String printer); + + public String getPrinterFilename(String url); + + public int getPrinterJobStatus(String job, int id); + + public int getPrinterDownloadRemoteFile(String remoteFile,String localFile); + +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/process.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/process.java new file mode 100644 index 0000000..59b6e5f --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/process.java @@ -0,0 +1,42 @@ +package com.kylin.kysdk; + +import java.util.List; +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface process extends DBusInterface { + //输入:进程号 + //输出:CPU瞬时使用率 + public double getProcInfoCpuUsage(int proc); + //输入:进程号 + //输出:IO使用率 + public double getProcInfoIoUsage(int proc); + //输入:进程号 + //输出:内存占用率 + public double getProcInfoMemUsage(int proc); + //输入:进程号 + //输出:进程状态 + public String getProcInfoStatus(int proc); + //输入:进程号 + //输出:进程使用的端口号 + public int getProcInfoPort(int proc); + //输入:进程号 + //输出:进程的启动时间 + public String getProcInfoStartTime(int proc); + //输入:进程号 + //输出:进程的运行时间 + public String getProcInfoRunningTime(int proc); + //输入:进程号 + //输出:CPU时间 + public String getProcInfoCpuTime(int proc); + //输入:进程号 + //输出:cmd + public String getProcInfoCmd(int proc); + //输入:进程号 + //输出:属主 + public String getProcInfoUser(int proc); + //输入:进程名 + //输出:某进程所有信息 + public List getProcInfo(String proc); + public List getAllProcInfo(); + +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/resolution.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/resolution.java new file mode 100644 index 0000000..50b558f --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/resolution.java @@ -0,0 +1,8 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import java.util.List; + +public interface resolution extends DBusInterface{ + public List getSysLegalResolution(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/resource.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/resource.java new file mode 100644 index 0000000..bd7682a --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/resource.java @@ -0,0 +1,29 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface resource extends DBusInterface { + public double getMemTotalKiB(); + + public double getMemUsagePercent(); + + public double getMemUsageKiB(); + + public double getMemAvailableKiB(); + + public double getMemFreeKiB(); + + public double getMemVirtAllocKiB(); + + public double getMemSwapTotalKiB(); + + public double getMemSwapUsagePercent(); + + public double getMemSwapUsageKiB(); + + public double getMemSwapFreeKiB(); + + public double getCpuCurrentUsage(); + + public String getUpTime(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/runinfo.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/runinfo.java new file mode 100644 index 0000000..1abbdff --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/runinfo.java @@ -0,0 +1,10 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; + +public interface runinfo extends DBusInterface { + public double getNetSpeed(String nc); + public int getDiskRate(String disk); + public double getCpuTemperature(); + public double getDiskTemperature(String name); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/sysinfo.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/sysinfo.java new file mode 100644 index 0000000..0623c90 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/com/kylin/kysdk/sysinfo.java @@ -0,0 +1,39 @@ +package com.kylin.kysdk; + +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.types.UInt32; + +import java.util.List; + +public interface sysinfo extends DBusInterface { + + public String getSystemArchitecture(); + + public String getSystemName(); + + public String getSystemVersion(boolean verbose); + + public int getSystemActivationStatus(); + + public String getSystemSerialNumber(); + + public String getSystemKernelVersion(); + + public String getSystemEffectUser(); + + public String getSystemProjectName(); + + public String getSystemProjectSubName(); + + public UInt32 getSystemProductFeatures(); + + public String getSystemHostVirtType(); + + public String getSystemHostCloudPlatform(); + + public String getSystemOSVersion(); + + public String getSystemUpdateVersion(); + + public boolean getSystemIsZYJ(); +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/client/DBusClient.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/client/DBusClient.java new file mode 100644 index 0000000..e5e6801 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/client/DBusClient.java @@ -0,0 +1,15 @@ +package kylin.kysdk.java.client; + +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +/*** + * dbus连接器 + * @author lihao3 + */ +public class DBusClient { + public static DBusConnection getConn(DBusConnection.DBusBusType dBusBusType) throws DBusException { + DBusConnection conn = DBusConnection.getConnection(dBusBusType); + return conn; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/BiosMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/BiosMethod.java new file mode 100644 index 0000000..66df573 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/BiosMethod.java @@ -0,0 +1,52 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.bios; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import java.util.List; + +@Slf4j +public class BiosMethod implements bios { + + @Override + public String getBiosVendor(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + bios obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/bios", bios.class); + return obj.getBiosVendor(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getBiosVersion(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + bios obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/bios", bios.class); + return obj.getBiosVersion(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/CpuMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/CpuMethod.java new file mode 100644 index 0000000..281725b --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/CpuMethod.java @@ -0,0 +1,120 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.types.UInt32; +import com.kylin.kysdk.cpuinfo; + +@Slf4j +public class CpuMethod implements cpuinfo{ + @Override + public String getCpuArch(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + cpuinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/cpuinfo", cpuinfo.class); + return obj.getCpuArch(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getCpuVendor(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + cpuinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/cpuinfo", cpuinfo.class); + return obj.getCpuVendor(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getCpuModel(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + cpuinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/cpuinfo", cpuinfo.class); + return obj.getCpuModel(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getCpuFreqMHz(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + cpuinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/cpuinfo", cpuinfo.class); + return obj.getCpuFreqMHz(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public UInt32 getCpuCorenums(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + cpuinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/cpuinfo", cpuinfo.class); + return obj.getCpuCorenums(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getCpuVirt(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + cpuinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/cpuinfo", cpuinfo.class); + return obj.getCpuVirt(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public UInt32 getCpuProcess(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + cpuinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/cpuinfo", cpuinfo.class); + return obj.getCpuProcess(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/DiskMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/DiskMethod.java new file mode 100644 index 0000000..e5dfbd8 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/DiskMethod.java @@ -0,0 +1,153 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.disk; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.types.UInt32; +import org.freedesktop.dbus.types.UInt64; + +import java.util.List; + +@Slf4j +public class DiskMethod implements disk { + @Override + public List getDiskList() { + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskList(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public UInt32 getDiskSectorSize(String name) { + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskSectorSize(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return new UInt32(0); + } + @Override + public float getDiskTotalSizeMiB(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskTotalSizeMiB(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1; + } + @Override + public String getDiskModel(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskModel(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getDiskSerial(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskSerial(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public UInt32 getDiskPartitionNums(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskPartitionNums(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return new UInt32(0); + } + @Override + public String getDiskType(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskType(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getDiskVersion(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskVersion(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public UInt64 getDiskSectorNum(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + disk obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/disk", disk.class); + return obj.getDiskSectorNum(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return new UInt64(0); + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/GpsMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/GpsMethod.java new file mode 100644 index 0000000..1749b9b --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/GpsMethod.java @@ -0,0 +1,36 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.gps; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class GpsMethod implements gps { + + @Override + public String getGPSInfo(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + gps obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/gps", gps.class); + return obj.getGPSInfo(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/MainBoardMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/MainBoardMethod.java new file mode 100644 index 0000000..d43f35d --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/MainBoardMethod.java @@ -0,0 +1,80 @@ +package kylin.kysdk.java.method; +import com.kylin.kysdk.mainboard; +import kylin.kysdk.java.client.DBusClient; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import lombok.extern.slf4j.Slf4j;; + +@Slf4j +public class MainBoardMethod implements mainboard { + + @Override + public String getMainboardName(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + mainboard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/mainboard", mainboard.class); + return obj.getMainboardName(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getMainboardDate(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + mainboard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/mainboard", mainboard.class); + return obj.getMainboardDate(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getMainboardSerial(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + mainboard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/mainboard", mainboard.class); + return obj.getMainboardSerial(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getMainboardVendor(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + mainboard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/mainboard", mainboard.class); + return obj.getMainboardVendor(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/NetCardMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/NetCardMethod.java new file mode 100644 index 0000000..8b3218f --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/NetCardMethod.java @@ -0,0 +1,166 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.netcard; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +@Slf4j +public class NetCardMethod implements netcard { + + @Override + public List getNetCardName(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardName(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public int getNetCardUp(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardUp(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return 0; + } + @Override + public List getNetCardUpcards(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardUpcards(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getNetCardPhymac(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardPhymac(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getNetCardPrivateIPv4(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardPrivateIPv4(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public List getNetCardIPv4(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardIPv4(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getNetCardPrivateIPv6(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardPrivateIPv6(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public List getNetCardIPv6(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardIPv6(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public int getNetCardType(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardType(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1; + } + + @Override + public List getNetCardProduct(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + netcard obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/netcard", netcard.class); + return obj.getNetCardProduct(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/NetMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/NetMethod.java new file mode 100644 index 0000000..0941a23 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/NetMethod.java @@ -0,0 +1,80 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.net; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import java.util.List; + +@Slf4j +public class NetMethod implements net { + + @Override + public int getPortState(int port){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + net obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/net", net.class); + return obj.getPortState(port); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1; + } + @Override + public List getMultiplePortStat(int begin,int end){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + net obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/net", net.class); + return obj.getMultiplePortStat(begin,end); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public List getGatewayInfo(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + net obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/net", net.class); + return obj.getGatewayInfo(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public List getFirewallState(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + net obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/net", net.class); + return obj.getFirewallState(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PackageInfoMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PackageInfoMethod.java new file mode 100644 index 0000000..abe8890 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PackageInfoMethod.java @@ -0,0 +1,65 @@ +package kylin.kysdk.java.method; +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.packageinfo; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +@Slf4j +public class PackageInfoMethod implements packageinfo{ + @Override + public List getPackageList(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + packageinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/packageinfo", packageinfo.class); + return obj.getPackageList(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getPackageVersion(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + packageinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/packageinfo", packageinfo.class); + return obj.getPackageVersion(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public int getPackageInstalled(String name,String version){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + packageinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/packageinfo", packageinfo.class); + return obj.getPackageInstalled(name,version); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} + diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PeripheralsenumMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PeripheralsenumMethod.java new file mode 100644 index 0000000..a18388c --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PeripheralsenumMethod.java @@ -0,0 +1,38 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.peripheralsenum; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +@Slf4j +public class PeripheralsenumMethod implements peripheralsenum { + + @Override + public List getAllUsbInfo(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + peripheralsenum obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/peripheralsenum", peripheralsenum.class); + return obj.getAllUsbInfo(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PrintMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PrintMethod.java new file mode 100644 index 0000000..41c0883 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/PrintMethod.java @@ -0,0 +1,150 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.print; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +@Slf4j +public class PrintMethod implements print { + @Override + public List getPrinterList(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterList(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public List getPrinterAvailableList(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterAvailableList(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public void setPrinterOptions(int page,String paperType,String cups,String sided){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + obj.setPrinterOptions(page,paperType,cups,sided); + return; + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return; + } + @Override + public int getPrinterPrintLocalFile(String printer,String file){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterPrintLocalFile(printer, file); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return 0; + } + @Override + public int getPrinterCancelAllJobs(String printer){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterCancelAllJobs(printer); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return 0; + } + @Override + public int getPrinterStatus(String printer){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterStatus(printer); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return 0; + } + @Override + public String getPrinterFilename(String url){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterFilename(url); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public int getPrinterJobStatus(String job, int id){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterJobStatus(job,id); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return 0; + } + @Override + public int getPrinterDownloadRemoteFile(String remoteFile,String localFile){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + print obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/print", print.class); + return obj.getPrinterDownloadRemoteFile(remoteFile, localFile); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return 0; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/ProcessMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/ProcessMethod.java new file mode 100644 index 0000000..3d2a224 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/ProcessMethod.java @@ -0,0 +1,200 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.process; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import java.util.List; + +@Slf4j +public class ProcessMethod implements process { + + @Override + public double getProcInfoCpuUsage(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoCpuUsage(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + + @Override + public double getProcInfoIoUsage(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoIoUsage(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + + @Override + public double getProcInfoMemUsage(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoMemUsage(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public String getProcInfoStatus(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoStatus(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public int getProcInfoPort(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoPort(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1; + } + + @Override + public String getProcInfoStartTime(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoStartTime(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getProcInfoRunningTime(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoRunningTime(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getProcInfoCpuTime(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoCpuTime(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getProcInfoCmd(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoCpuTime(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public String getProcInfoUser(int proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfoUser(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public List getProcInfo(String proc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getProcInfo(proc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public List getAllProcInfo(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + process obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/process", process.class); + return obj.getAllProcInfo(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/ResourceMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/ResourceMethod.java new file mode 100644 index 0000000..cbf65b7 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/ResourceMethod.java @@ -0,0 +1,193 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import com.kylin.kysdk.resource; + +@Slf4j +public class ResourceMethod implements resource{ + @Override + public double getMemTotalKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemTotalKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemUsagePercent(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemUsagePercent(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemUsageKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemUsageKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemAvailableKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemAvailableKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemFreeKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemFreeKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemVirtAllocKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemVirtAllocKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemSwapTotalKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemSwapTotalKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemSwapUsagePercent(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemSwapUsagePercent(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public double getMemSwapUsageKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemSwapUsageKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + + @Override + public double getMemSwapFreeKiB(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getMemSwapFreeKiB(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + + @Override + public double getCpuCurrentUsage(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getCpuCurrentUsage(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + + } + + @Override + public String getUpTime(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + resource obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resource", resource.class); + return obj.getUpTime(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/RunInfoMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/RunInfoMethod.java new file mode 100644 index 0000000..f8132a1 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/RunInfoMethod.java @@ -0,0 +1,78 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import com.kylin.kysdk.runinfo; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +@Slf4j +public class RunInfoMethod implements runinfo { + @Override + public double getNetSpeed(String nc){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + runinfo runinfo = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/runinfo", com.kylin.kysdk.runinfo.class); + return runinfo.getNetSpeed(nc); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + @Override + public int getDiskRate(String disk){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + runinfo runinfo = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/runinfo", com.kylin.kysdk.runinfo.class); + return runinfo.getDiskRate(disk); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1; + } + @Override + public double getCpuTemperature(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + runinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/runinfo", runinfo.class); + return obj.getCpuTemperature(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + + @Override + public double getDiskTemperature(String name){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + runinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/runinfo", runinfo.class); + return obj.getDiskTemperature(name); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return -1.0; + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/SysInfoMethod.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/SysInfoMethod.java new file mode 100644 index 0000000..22fd3e8 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/main/java/kylin/kysdk/java/method/SysInfoMethod.java @@ -0,0 +1,267 @@ +package kylin.kysdk.java.method; + +import kylin.kysdk.java.client.DBusClient; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import com.kylin.kysdk.sysinfo; +import com.kylin.kysdk.resolution; +import org.freedesktop.dbus.types.UInt32; + +import java.util.List; + +@Slf4j +public class SysInfoMethod implements sysinfo{ + @Override + public String getSystemArchitecture(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemArchitecture(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemName(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemName(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemVersion(boolean verbose){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemVersion(verbose); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public int getSystemActivationStatus(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemActivationStatus(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return 0; + } + @Override + public String getSystemSerialNumber(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemSerialNumber(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemKernelVersion(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemKernelVersion(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemEffectUser(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemEffectUser(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemProjectName(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemProjectName(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemProjectSubName(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemProjectSubName(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public UInt32 getSystemProductFeatures(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemProductFeatures(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemHostVirtType(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemHostVirtType(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemHostCloudPlatform(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemHostCloudPlatform(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemOSVersion(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemOSVersion(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public String getSystemUpdateVersion(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemUpdateVersion(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public boolean getSystemIsZYJ(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SYSTEM); + sysinfo obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/sysinfo", sysinfo.class); + return obj.getSystemIsZYJ(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return false; + } + + public List getSysLegalResolution(){ + ResolutionMethod obj = new ResolutionMethod(); + return obj.getSysLegalResolution(); + } + + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } + + public class ResolutionMethod implements resolution{ + @Override + public List getSysLegalResolution(){ + DBusConnection conn = null; + try { + conn = DBusClient.getConn(DBusConnection.DBusBusType.SESSION); + resolution obj = conn.getRemoteObject("com.kylin.kysdk.service", + "/com/kylin/kysdk/resolution", resolution.class); + + return obj.getSysLegalResolution(); + } catch (DBusException e) { + if (conn != null) conn.disconnect(); + log.error("DBusException GetEUser :",e); + } + return null; + } + @Override + public boolean isRemote() { + return false; + } + + @Override + public String getObjectPath() { + return null; + } + } +} diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/src/test/java/kylin/kysdk/java/EventTest.java b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/test/java/kylin/kysdk/java/EventTest.java new file mode 100644 index 0000000..f768d87 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/kysdk-java/src/test/java/kylin/kysdk/java/EventTest.java @@ -0,0 +1,177 @@ +package kylin.kysdk.java; + +import kylin.kysdk.java.method.*; +import lombok.extern.slf4j.Slf4j; +import org.freedesktop.dbus.types.UInt64; +import org.junit.jupiter.api.Test; +import java.util.List; + +@Slf4j +public class EventTest { + @Test + public void disk() throws InterruptedException { + DiskMethod obj = new DiskMethod(); + List list = obj.getDiskList(); + System.out.println(list); + System.out.println("SecSize: " + obj.getDiskSectorSize(list.get(0))); + System.out.println("TotaSize: " + obj.getDiskTotalSizeMiB(list.get(0))); + System.out.println("Model: " + obj.getDiskModel(list.get(0))); + System.out.println("Serial: " + obj.getDiskSerial(list.get(0))); + System.out.println("PartNum: " + obj.getDiskPartitionNums(list.get(0))); + System.out.println("Type: " + obj.getDiskType(list.get(0))); + System.out.println("Version: " + obj.getDiskVersion(list.get(0))); + System.out.println("SecNum: " + obj.getDiskSectorNum(list.get(0))); + } + @Test + public void cpu() throws InterruptedException { + CpuMethod obj = new CpuMethod(); + System.out.println("Arch: " + obj.getCpuArch()); + System.out.println("Vendor: " + obj.getCpuVendor()); + System.out.println("Model: " + obj.getCpuModel()); + System.out.println("Freq: " + obj.getCpuFreqMHz()); + System.out.println("Core: " + obj.getCpuCorenums()); + System.out.println("Virt: " + obj.getCpuVirt()); + System.out.println("Process: " + obj.getCpuProcess()); + } + + @Test + public void netcard() throws InterruptedException { + NetCardMethod obj = new NetCardMethod(); + List list = obj.getNetCardName(); + System.out.println(list); + System.out.println("CardUp: " + obj.getNetCardUp(list.get(0))); + System.out.println("UpCards: " + obj.getNetCardUpcards()); + System.out.println("Mac: " + obj.getNetCardPhymac(list.get(0))); + System.out.println("PrivateIPv4: " + obj.getNetCardPrivateIPv4(list.get(0))); + System.out.println("Ipv4: " + obj.getNetCardIPv4(list.get(0))); + System.out.println("PrivateIPv6: " + obj.getNetCardPrivateIPv6(list.get(0))); + System.out.println("Type: " + obj.getNetCardType(list.get(0))); + System.out.println("Product: " + obj.getNetCardProduct(list.get(0))); + System.out.println("Ipv6: " + obj.getNetCardPrivateIPv6(list.get(0))); + } + + @Test + public void bios() throws InterruptedException { + BiosMethod obj = new BiosMethod(); + System.out.println("Vendor" + obj.getBiosVendor()); + System.out.println("Version" + obj.getBiosVersion()); + } + @Test + public void mainboard() throws InterruptedException { + MainBoardMethod obj = new MainBoardMethod(); + System.out.println("Name: " + obj.getMainboardName()); + System.out.println("date: " + obj.getMainboardDate()); + System.out.println("Serial: " + obj.getMainboardSerial()); + System.out.println("Vendor: " + obj.getMainboardVendor()); + } + + @Test + public void usb() throws InterruptedException { + PeripheralsenumMethod obj = new PeripheralsenumMethod(); + System.out.println("Usb: " + obj.getAllUsbInfo()); + } + + @Test + public void packageinfo() throws InterruptedException { + PackageInfoMethod obj = new PackageInfoMethod(); + List list = obj.getPackageList(); + System.out.println(list); + System.out.println("Version:" + obj.getPackageVersion(list.get(1))); + System.out.println("state: " + obj.getPackageInstalled(list.get(1), obj.getPackageVersion(list.get(1)))); + } + + @Test + public void resource() throws InterruptedException { + ResourceMethod obj = new ResourceMethod(); + System.out.println("Total:" + obj.getMemTotalKiB()); + System.out.println("UsagePer:" + obj.getMemSwapUsagePercent()); + System.out.println("Usage:" + obj.getMemUsageKiB()); + System.out.println("Avail:" + obj.getMemAvailableKiB()); + System.out.println("Free:" + obj.getMemFreeKiB()); + System.out.println("Virt:" + obj.getMemVirtAllocKiB()); + System.out.println("SwapTotal:" + obj.getMemSwapTotalKiB()); + System.out.println("SwapUsagePer:" + obj.getMemSwapUsagePercent()); + System.out.println("SwapUsage:" + obj.getMemSwapUsageKiB()); + System.out.println("SwageFree:" + obj.getMemSwapFreeKiB()); + System.out.println("CpuUsage:" + obj.getCpuCurrentUsage()); + System.out.println("UpTime:" + obj.getUpTime()); + } + + @Test + public void process() throws InterruptedException { + + ProcessMethod obj = new ProcessMethod(); + int proc = 2203; + System.out.println("CpuUsage:" + obj.getProcInfoCpuUsage(proc)); + System.out.println("MemUsage:" + obj.getProcInfoMemUsage(proc)); + System.out.println("State:" + obj.getProcInfoStatus(proc)); + System.out.println("Port:" + obj.getProcInfoPort(proc)); + System.out.println("Start:" + obj.getProcInfoStartTime(proc)); + System.out.println("Running:" + obj.getProcInfoRunningTime(proc)); + System.out.println("CpuTime:" + obj.getProcInfoCpuTime(proc)); + System.out.println("Cmd:" + obj.getProcInfoCmd(proc)); + System.out.println("User:" + obj.getProcInfoUser(proc)); + System.out.println(obj.getProcInfo("code")); + System.out.println(obj.getAllProcInfo()); + } + + @Test + public void sysinfo() throws InterruptedException { + SysInfoMethod obj = new SysInfoMethod(); + System.out.println("Arch:" + obj.getSystemArchitecture()); + System.out.println("Name:" + obj.getSystemName()); + System.out.println("Version:" + obj.getSystemVersion(false)); + System.out.println("Activation:" + obj.getSystemActivationStatus()); + System.out.println("Serial:" + obj.getSystemSerialNumber()); + System.out.println("KernelVer:" + obj.getSystemKernelVersion()); + System.out.println("Effect:" + obj.getSystemEffectUser()); + System.out.println("ProName:" + obj.getSystemProjectName()); + System.out.println("SubName:" + obj.getSystemProjectSubName()); + System.out.println("Product:" + obj.getSystemProductFeatures()); + System.out.println("Virt:" + obj.getSystemHostVirtType()); + System.out.println("Paltform:" + obj.getSystemHostCloudPlatform()); + System.out.println("OSVer:" + obj.getSystemOSVersion()); + System.out.println("UpdateVer:" + obj.getSystemUpdateVersion()); + System.out.println("ZYJ:" + obj.getSystemIsZYJ()); + System.out.println("Arch:" + obj.getSysLegalResolution()); + } + + @Test + public void net() throws InterruptedException { + NetMethod obj = new NetMethod(); + System.out.println("Port:" + obj.getPortState(5868)); + System.out.println("Multiple:" + obj.getMultiplePortStat(0,500)); + System.out.println("Gateway:" + obj.getGatewayInfo()); + System.out.println("Firewall:" + obj.getFirewallState()); + } + + @Test + public void runInfo() throws InterruptedException { + RunInfoMethod obj = new RunInfoMethod(); + System.out.println("NetSpeed:" + obj.getNetSpeed("enp3s0")); + System.out.println("Rate:" + obj.getDiskRate("/dev/sda")); + System.out.println("CpuTemp:" + obj.getCpuTemperature()); + System.out.println("DiskTemp:" + obj.getDiskTemperature("/dev/sda")); + } + + @Test + public static void gps() throws InterruptedException { + GpsMethod obj = new GpsMethod(); + System.out.println(obj.getGPSInfo()); + } + + @Test + public static void print() throws InterruptedException { + PrintMethod obj = new PrintMethod(); + System.out.println("PrinterList:" + obj.getPrinterList()); + System.out.println("AvailableList:" + obj.getPrinterAvailableList()); + obj.setPrinterOptions(1, "A4", "", "one-sided"); + System.out.println("LocalFile:" + obj.getPrinterPrintLocalFile("", "")); + System.out.println("CancleJob:" + obj.getPrinterCancelAllJobs("")); + System.out.println("Statue:" + obj.getPrinterStatus("")); + System.out.println("FileName:" + obj.getPrinterFilename("")); + System.out.println("JobStatus:" + obj.getPrinterJobStatus("", 0)); + System.out.println("RemoteFile:" + obj.getPrinterDownloadRemoteFile("", "")); + } +} + diff --git a/src/kysdk-java/kysdk-java-ext/kysdk-java/target/kysdk-java-1.0.0.jar b/src/kysdk-java/kysdk-java-ext/kysdk-java/target/kysdk-java-1.0.0.jar new file mode 100644 index 0000000..d5d1fcf Binary files /dev/null and b/src/kysdk-java/kysdk-java-ext/kysdk-java/target/kysdk-java-1.0.0.jar differ diff --git a/src/kysdk-java/kysdk-java-ext/package/build.sh b/src/kysdk-java/kysdk-java-ext/package/build.sh new file mode 100755 index 0000000..2df691b --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/package/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +cd ../kysdk-java/ +mvn clean install -DskipTests=true --settings ../package/settings.xml diff --git a/src/kysdk-java/kysdk-java-ext/package/settings.xml b/src/kysdk-java/kysdk-java-ext/package/settings.xml new file mode 100644 index 0000000..bd3c4de --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/package/settings.xml @@ -0,0 +1,367 @@ + + + + + + + + ${user.home}/.m2/repository + + + + + + + + + + + + + + + + + + + + + + + kylin-public + dev + dev@kylin + + + kylin-releases + dev + dev@kylin + + + kylin-snapshots + dev + dev@kylin + + + + + + + + ali-nexus + aliyun nexus + http://maven.aliyun.com/nexus/content/groups/public/ + * + + + + kylin-public + Public Repositories + http://172.20.184.181:18081/repository/maven-public/ + * + + + + kylin-releases + * + releases Proxy Mirror + http://172.20.184.181:18081/repository/maven-releases/ + + + kylin-snapshots + * + snapshots Proxy Mirror + http://172.20.184.181:18081/repository/maven-snapshots/ + + + + + + + + jdk1.8 + + true + 1.8 + + + 1.8 + 1.8 + 1.8 + + + + kylin + + + aliyun-public + aliyun Repository + http://maven.aliyun.com/nexus/content/groups/public/ + + true + always + fail + + + true + always + warn + + + + kylin-public + http://172.20.184.181:18081/repository/maven-public/ + + true + + + true + + + + kylin-releases + + http://172.20.184.181:18081/repository/maven-releases/ + + true + + + + kylin-snapshots + + http://172.20.184.181:18081/repository/maven-snapshots/ + + true + + + + + + kylin-public + Public Repositories + http://172.20.184.181:18081/repository/maven-public/ + + true + + + true + + + + + + + + + + jdk1.8 + kylin + + diff --git a/src/kysdk-java/kysdk-java-ext/pom.xml b/src/kysdk-java/kysdk-java-ext/pom.xml new file mode 100644 index 0000000..ce22b18 --- /dev/null +++ b/src/kysdk-java/kysdk-java-ext/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + kylin.kysdk.java + kysdk-java-ext + pom + 1.0.0 + + kysdk-java + + diff --git a/src/kysdk-java/test/.idea/.gitignore b/src/kysdk-java/test/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/src/kysdk-java/test/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/src/kysdk-java/test/.idea/artifacts/test_jar.xml b/src/kysdk-java/test/.idea/artifacts/test_jar.xml new file mode 100644 index 0000000..a7ec857 --- /dev/null +++ b/src/kysdk-java/test/.idea/artifacts/test_jar.xml @@ -0,0 +1,19 @@ + + + $PROJECT_DIR$/out/artifacts/test_jar + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/compiler.xml b/src/kysdk-java/test/.idea/compiler.xml new file mode 100644 index 0000000..c24cf21 --- /dev/null +++ b/src/kysdk-java/test/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/encodings.xml b/src/kysdk-java/test/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/src/kysdk-java/test/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/jarRepositories.xml b/src/kysdk-java/test/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/src/kysdk-java/test/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/libraries/github_hypfvieh_dbus_java.xml b/src/kysdk-java/test/.idea/libraries/github_hypfvieh_dbus_java.xml new file mode 100644 index 0000000..9f4782a --- /dev/null +++ b/src/kysdk-java/test/.idea/libraries/github_hypfvieh_dbus_java.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/libraries/kysdk_java_1_0_0.xml b/src/kysdk-java/test/.idea/libraries/kysdk_java_1_0_0.xml new file mode 100644 index 0000000..b46ca6f --- /dev/null +++ b/src/kysdk-java/test/.idea/libraries/kysdk_java_1_0_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/libraries/slf4j_api.xml b/src/kysdk-java/test/.idea/libraries/slf4j_api.xml new file mode 100644 index 0000000..171e81a --- /dev/null +++ b/src/kysdk-java/test/.idea/libraries/slf4j_api.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/libraries/slf4j_simple.xml b/src/kysdk-java/test/.idea/libraries/slf4j_simple.xml new file mode 100644 index 0000000..db3f7df --- /dev/null +++ b/src/kysdk-java/test/.idea/libraries/slf4j_simple.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/.idea/misc.xml b/src/kysdk-java/test/.idea/misc.xml new file mode 100644 index 0000000..accd629 --- /dev/null +++ b/src/kysdk-java/test/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-9.2.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-9.2.jar new file mode 100644 index 0000000..3557ae4 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-9.2.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-analysis-9.2.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-analysis-9.2.jar new file mode 100644 index 0000000..9d575ca Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-analysis-9.2.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-commons-9.2.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-commons-9.2.jar new file mode 100644 index 0000000..01028a0 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-commons-9.2.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-tree-9.2.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-tree-9.2.jar new file mode 100644 index 0000000..0a6833a Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-tree-9.2.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-util-9.2.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-util-9.2.jar new file mode 100644 index 0000000..3afe6e6 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/asm-util-9.2.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/dbus-java-3.3.2.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/dbus-java-3.3.2.jar new file mode 100644 index 0000000..53d62cb Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/dbus-java-3.3.2.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jffi-1.3.9-native.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jffi-1.3.9-native.jar new file mode 100644 index 0000000..3af6cd5 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jffi-1.3.9-native.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jffi-1.3.9.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jffi-1.3.9.jar new file mode 100644 index 0000000..2e89e06 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jffi-1.3.9.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-a64asm-1.0.0.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-a64asm-1.0.0.jar new file mode 100644 index 0000000..e736825 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-a64asm-1.0.0.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-constants-0.10.3.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-constants-0.10.3.jar new file mode 100644 index 0000000..54f14d6 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-constants-0.10.3.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-enxio-0.32.13.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-enxio-0.32.13.jar new file mode 100644 index 0000000..94c5c4e Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-enxio-0.32.13.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-ffi-2.2.11.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-ffi-2.2.11.jar new file mode 100644 index 0000000..95794c7 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-ffi-2.2.11.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-posix-3.1.15.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-posix-3.1.15.jar new file mode 100644 index 0000000..5d6492b Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-posix-3.1.15.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-unixsocket-0.38.17.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-unixsocket-0.38.17.jar new file mode 100644 index 0000000..5118678 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-unixsocket-0.38.17.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-x86asm-1.0.2.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-x86asm-1.0.2.jar new file mode 100644 index 0000000..dd4e695 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/jnr-x86asm-1.0.2.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/kysdk-java-1.0.0.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/kysdk-java-1.0.0.jar new file mode 100644 index 0000000..508763a Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/kysdk-java-1.0.0.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/slf4j-api-1.7.36.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/slf4j-api-1.7.36.jar new file mode 100644 index 0000000..7d3ce68 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/slf4j-api-1.7.36.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/lib/slf4j-simple-1.7.36.jar b/src/kysdk-java/test/out/artifacts/test_jar/lib/slf4j-simple-1.7.36.jar new file mode 100644 index 0000000..ef831a8 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/lib/slf4j-simple-1.7.36.jar differ diff --git a/src/kysdk-java/test/out/artifacts/test_jar/test.jar b/src/kysdk-java/test/out/artifacts/test_jar/test.jar new file mode 100644 index 0000000..9c49df8 Binary files /dev/null and b/src/kysdk-java/test/out/artifacts/test_jar/test.jar differ diff --git a/src/kysdk-java/test/pom.xml b/src/kysdk-java/test/pom.xml new file mode 100644 index 0000000..ce75e66 --- /dev/null +++ b/src/kysdk-java/test/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + org.example + test + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + \ No newline at end of file diff --git a/src/kysdk-java/test/src/META-INF/MANIFEST.MF b/src/kysdk-java/test/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..1edc7ab --- /dev/null +++ b/src/kysdk-java/test/src/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Main-Class: org.example.Main +Class-Path: lib/slf4j-api-1.7.36.jar lib/slf4j-simple-1.7.36.jar lib/slf + 4j-api-1.7.36.jar lib/dbus-java-3.3.2.jar lib/jnr-unixsocket-0.38.17.ja + r lib/jnr-ffi-2.2.11.jar lib/jffi-1.3.9.jar lib/jffi-1.3.9-native.jar l + ib/asm-9.2.jar lib/asm-commons-9.2.jar lib/asm-analysis-9.2.jar lib/asm + -tree-9.2.jar lib/asm-util-9.2.jar lib/jnr-a64asm-1.0.0.jar lib/jnr-x86 + asm-1.0.2.jar lib/jnr-constants-0.10.3.jar lib/jnr-enxio-0.32.13.jar li + b/jnr-posix-3.1.15.jar lib/slf4j-api-1.7.36.jar lib/kysdk-java-1.0.0.ja + r + diff --git a/src/kysdk-java/test/src/main/java/org/example/Main.java b/src/kysdk-java/test/src/main/java/org/example/Main.java new file mode 100644 index 0000000..745bb43 --- /dev/null +++ b/src/kysdk-java/test/src/main/java/org/example/Main.java @@ -0,0 +1,257 @@ +package org.example; +import kylin.kysdk.java.method.*; + +import java.util.List; + +public class Main { + public static void main(String[] args) { + switch (args[0]) + { + case "disk": { + disk(args); + } + break; + case "cpu": { + cpu(); + } + break; + case "netcard": { + netcard(args); + } + break; + case "bios": { + bios(); + } + break; + case "mainboard": { + mainboard(); + } + case "usb": { + usb(); + } + break; + case "resource": { + resource(); + } + break; + case "process": { + process(args); + } + break; + case "sysinfo": { + sysinfo(); + } + break; + case "net": { + net(args); + } + break; + case "runinfo": { + runinfo(args); + } + break; + case "gps": { + gps(); + } + break; + case "print": { + print(args); + } + break; + default:{ + System.out.println("使用方式 java -jar <模块> [模块参数]\n 具体模块名及参考使用说明"); + } + } + System.exit(0); + } + + public static void disk(String[] args){ + DiskMethod obj = new DiskMethod(); + List list = obj.getDiskList(); + System.out.println(list); + if(args.length < 2){ + System.out.println("获取磁盘信息 : java -jar test.jar disk <磁盘路径> "); + System.exit(0); + } + System.out.println("SecSize: " + obj.getDiskSectorSize(args[1])); + System.out.println("TotaSize: " + obj.getDiskTotalSizeMiB(args[1])); + System.out.println("Model: " + obj.getDiskModel(args[1])); + System.out.println("Serial: " + obj.getDiskSerial(args[1])); + System.out.println("PartNum: " + obj.getDiskPartitionNums(args[1])); + System.out.println("Type: " + obj.getDiskType(args[1])); + System.out.println("Version: " + obj.getDiskVersion(args[1])); + System.out.println("SecNum: " + obj.getDiskSectorNum(args[1])); + } + + public static void cpu(){ + CpuMethod obj = new CpuMethod(); + System.out.println("Arch: " + obj.getCpuArch()); + System.out.println("Vendor: " + obj.getCpuVendor()); + System.out.println("Model: " + obj.getCpuModel()); + System.out.println("Freq: " + obj.getCpuFreqMHz()); + System.out.println("Core: " + obj.getCpuCorenums()); + System.out.println("Virt: " + obj.getCpuVirt()); + System.out.println("Process: " + obj.getCpuProcess()); + } + public static void netcard(String[] args){ + NetCardMethod obj = new NetCardMethod(); + List list = obj.getNetCardName(); + System.out.println(list); + System.out.println("UpCards: " + obj.getNetCardUpcards()); + if(args.length < 2){ + System.out.println("获取网卡信息 : java -jar test.jar netcard <网卡名> "); + System.exit(0); + } + System.out.println("CardUp: " + obj.getNetCardUp(args[1])); + System.out.println("Mac: " + obj.getNetCardPhymac(args[1])); + System.out.println("PrivateIPv4: " + obj.getNetCardPrivateIPv4(args[1])); + System.out.println("Ipv4: " + obj.getNetCardIPv4(args[1])); + System.out.println("PrivateIPv6: " + obj.getNetCardPrivateIPv6(args[1])); + System.out.println("Type: " + obj.getNetCardType(args[1])); +// System.out.println("Product: " + obj.getNetCardProduct(list.get(0))); + System.out.println("Ipv6: " + obj.getNetCardPrivateIPv6(args[1])); + } + public static void bios(){ + BiosMethod obj = new BiosMethod(); + System.out.println("Vendor" + obj.getBiosVendor()); + System.out.println("Version" + obj.getBiosVersion()); + } + public static void mainboard(){ + MainBoardMethod obj = new MainBoardMethod(); + System.out.println("Name: " + obj.getMainboardName()); + System.out.println("date: " + obj.getMainboardDate()); + System.out.println("Serial: " + obj.getMainboardSerial()); + System.out.println("Vendor: " + obj.getMainboardVendor()); + } + public static void usb(){ + PeripheralsenumMethod obj = new PeripheralsenumMethod(); + System.out.println("Usb: " + obj.getAllUsbInfo()); + } + public static void packageinfo(String[] args){ + PackageInfoMethod obj = new PackageInfoMethod(); + List list = obj.getPackageList(); + System.out.println(list); + if(args.length < 2){ + System.out.println("获取软件包信息 : java -jar test.jar packageinfo <包名> "); + System.exit(0); + } + System.out.println("Version:" + obj.getPackageVersion(args[1])); + System.out.println("state: " + obj.getPackageInstalled(args[1], obj.getPackageVersion(args[1]))); + } + public static void resource(){ + ResourceMethod obj = new ResourceMethod(); + System.out.println("Total:" + obj.getMemTotalKiB()); + System.out.println("UsagePer:" + obj.getMemSwapUsagePercent()); + System.out.println("Usage:" + obj.getMemUsageKiB()); + System.out.println("Avail:" + obj.getMemAvailableKiB()); + System.out.println("Free:" + obj.getMemFreeKiB()); + System.out.println("Virt:" + obj.getMemVirtAllocKiB()); + System.out.println("SwapTotal:" + obj.getMemSwapTotalKiB()); + System.out.println("SwapUsagePer:" + obj.getMemSwapUsagePercent()); + System.out.println("SwapUsage:" + obj.getMemSwapUsageKiB()); + System.out.println("SwageFree:" + obj.getMemSwapFreeKiB()); + System.out.println("CpuUsage:" + obj.getCpuCurrentUsage()); + System.out.println("UpTime:" + obj.getUpTime()); + } + public static void process(String[] args){ + if(args.length < 3){ + System.out.println("获取进程信息 : java -jar test.jar process <进程号> <进程名> "); + System.exit(0); + } + ProcessMethod obj = new ProcessMethod(); + int proc = Integer.parseInt(args[1]); + System.out.println("CpuUsage:" + obj.getProcInfoCpuUsage(proc)); + System.out.println("MemUsage:" + obj.getProcInfoMemUsage(proc)); + System.out.println("State:" + obj.getProcInfoStatus(proc)); + System.out.println("Port:" + obj.getProcInfoPort(proc)); + System.out.println("Start:" + obj.getProcInfoStartTime(proc)); + System.out.println("Running:" + obj.getProcInfoRunningTime(proc)); + System.out.println("CpuTime:" + obj.getProcInfoCpuTime(proc)); + System.out.println("Cmd:" + obj.getProcInfoCmd(proc)); + System.out.println("User:" + obj.getProcInfoUser(proc)); + System.out.println(obj.getProcInfo(args[2])); +// System.out.println(obj.getAllProcInfo()); + } + public static void sysinfo(){ + SysInfoMethod obj = new SysInfoMethod(); + System.out.println("Arch:" + obj.getSystemArchitecture()); + System.out.println("Name:" + obj.getSystemName()); + System.out.println("Version:" + obj.getSystemVersion(false)); + System.out.println("Activation:" + obj.getSystemActivationStatus()); + System.out.println("Serial:" + obj.getSystemSerialNumber()); + System.out.println("KernelVer:" + obj.getSystemKernelVersion()); + System.out.println("Effect:" + obj.getSystemEffectUser()); + System.out.println("ProName:" + obj.getSystemProjectName()); + System.out.println("SubName:" + obj.getSystemProjectSubName()); + System.out.println("Product:" + obj.getSystemProductFeatures()); + System.out.println("Virt:" + obj.getSystemHostVirtType()); + System.out.println("Paltform:" + obj.getSystemHostCloudPlatform()); + System.out.println("OSVer:" + obj.getSystemOSVersion()); + System.out.println("UpdateVer:" + obj.getSystemUpdateVersion()); + System.out.println("ZYJ:" + obj.getSystemIsZYJ()); +// System.out.println("Arch:" + obj.getSysLegalResolution()); + } + public static void net(String[] args){ + if(args.length < 3){ + System.out.println("获取网络信息 : java -jar test.jar net <单端口号> <区间端口号开始> <区间端口号结束> "); + System.exit(0); + } + NetMethod obj = new NetMethod(); + System.out.println("Port:" + obj.getPortState(5868)); + System.out.println("Multiple:" + obj.getMultiplePortStat(0,10)); + System.out.println("Gateway:" + obj.getGatewayInfo()); + System.out.println("Firewall:" + obj.getFirewallState()); + } + public static void runinfo(String[] args){ + if(args.length < 3){ + System.out.println("获取运行时信息 : java -jar test.jar runinfo <网卡名> <磁盘路径> "); + System.exit(0); + } + RunInfoMethod obj = new RunInfoMethod(); + System.out.println("NetSpeed:" + obj.getNetSpeed("enp3s0")); + System.out.println("Rate:" + obj.getDiskRate("/dev/sda")); + System.out.println("CpuTemp:" + obj.getCpuTemperature()); + System.out.println("DiskTemp:" + obj.getDiskTemperature("/dev/sda")); + } + public static void gps(){ + GpsMethod obj = new GpsMethod(); + System.out.println(obj.getGPSInfo()); + } + public static void print(String[] args){ + + PrintMethod obj = new PrintMethod(); + System.out.println("PrinterList:" + obj.getPrinterList()); + System.out.println("AvailableList:" + obj.getPrinterAvailableList()); + if(args.length == 1){ + System.out.println("其它接口需要参数较多 需要看调用文档"); + System.exit(0); + } + if(args.length == 3){ + if(Integer.parseInt(args[2]) == 0){ + System.out.println("CancleJob:" + obj.getPrinterCancelAllJobs(args[1])); + System.out.println("Statue:" + obj.getPrinterStatus(args[1])); + } + else + System.out.println("FileName:" + obj.getPrinterFilename(args[1])); + } + if(args.length == 4){ + switch (Integer.parseInt(args[3])){ + case 0:{ + System.out.println("LocalFile:" + obj.getPrinterPrintLocalFile(args[1],args[2])); + } + break; + case 1:{ + System.out.println("JobStatus:" + obj.getPrinterJobStatus(args[1],Integer.parseInt(args[2]))); + } + break; + case 2:{ + System.out.println("RemoteFile:" + obj.getPrinterDownloadRemoteFile(args[1],args[2])); + } + break; + default: + } + } + if(args.length == 5) + obj.setPrinterOptions(Integer.parseInt(args[1]),args[2],args[3],args[4]); + } +} \ No newline at end of file diff --git a/src/kysdkdbus/CMakeLists.txt b/src/kysdkdbus/CMakeLists.txt new file mode 100644 index 0000000..ab60611 --- /dev/null +++ b/src/kysdkdbus/CMakeLists.txt @@ -0,0 +1,14 @@ +set (CMAKE_AUTOMOC ON) + +aux_source_directory(. SOURCECODE) +include_directories(/usr/include/kysdk/kysdk-system/) + +find_package(Qt5 COMPONENTS Core DBus REQUIRED) +include_directories(${Qt5Core_INCLUDE_DIRS}) +include_directories(${Qt5DBus_INCLUDE_DIRS}) +ADD_EXECUTABLE(kySdkDbus kysdkdbus.cpp main.cpp) +target_link_libraries(kySdkDbus kysysinfo kyhw kydiskinfo kyrtinfo kynetinfo kyrealtime kydiskinfo kylocation kypackage Qt5::Core Qt5::DBus) + + +ADD_EXECUTABLE(kySdkDbus-session kysdkrest.cpp main_rest.cpp) +target_link_libraries(kySdkDbus-session kysysinfo Qt5::Core Qt5::DBus) \ No newline at end of file diff --git a/src/kysdkdbus/com.kylin.kysdk.service.conf b/src/kysdkdbus/com.kylin.kysdk.service.conf new file mode 100644 index 0000000..1f5d6f2 --- /dev/null +++ b/src/kysdkdbus/com.kylin.kysdk.service.conf @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/kysdkdbus/kySdkDbus.pro b/src/kysdkdbus/kySdkDbus.pro new file mode 100644 index 0000000..f4f5353 --- /dev/null +++ b/src/kysdkdbus/kySdkDbus.pro @@ -0,0 +1,33 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + kysdkdbus.cpp \ + main.cpp + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +HEADERS += \ + kysdkdbus.h + +INCLUDEPATH += /usr/include/kysdk/kysdk-system/ +LIBS += -L/usr/lib/kysdk/kysdk-system/ -lkysysinfo -lkyhw -lkydiskinfo -lkyrtinfo -lkynetinfo -lkyrealtime -lkydiskinfo -lkylocation -lkypackage +#BIOS -lkybiosinfo +#主板 -lkyboardinfo diff --git a/src/kysdkdbus/kysdk-dbus-session.desktop b/src/kysdkdbus/kysdk-dbus-session.desktop new file mode 100644 index 0000000..cc2487a --- /dev/null +++ b/src/kysdkdbus/kysdk-dbus-session.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Exec=/usr/bin/kySdkDbus-session +TryExec=/usr/bin/kySdkDbus-session +Name=SDK-SYSTEM-SESSION-BUS +Icon= +Keywords=session;kysdk-dbus-session +Comment=This session date you into sdk diff --git a/src/kysdkdbus/kysdk-dbus.service b/src/kysdkdbus/kysdk-dbus.service new file mode 100644 index 0000000..d3e8eb2 --- /dev/null +++ b/src/kysdkdbus/kysdk-dbus.service @@ -0,0 +1,12 @@ +[Unit] +Description=KYLIN SDK SYSTIME DBUS + +[Service] +Type=dbus +Restart=always +RestartSec=0 +BusName=com.kylin.kysdk.service +ExecStart=/usr/bin/kySdkDbus + +[Install] +WantedBy=multi-user.target diff --git a/src/kysdkdbus/kysdkdbus.cpp b/src/kysdkdbus/kysdkdbus.cpp new file mode 100755 index 0000000..248793f --- /dev/null +++ b/src/kysdkdbus/kysdkdbus.cpp @@ -0,0 +1,757 @@ +#include "kysdkdbus.h" +#include + +KySdkDisk::KySdkDisk(){} + +KySdkDisk::~KySdkDisk(){} + +QStringList KySdkDisk::getDiskList() const +{ + QStringList res = {}; + char** disk = kdk_get_disklist(); + for (int i = 0; disk[i]; i++) + { + res << QString::fromLocal8Bit(disk[i]); + } + + return res; +} + +unsigned int KySdkDisk::getDiskSectorSize(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + return p->sector_size; + }else{ + return NULL; + } +} + +double KySdkDisk::getDiskTotalSizeMiB(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + return static_cast(p->total_size_MiB); + }else{ + return NULL; + } +} + +QString KySdkDisk::getDiskModel(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + return p->model; + }else{ + return NULL; + } +} + +QString KySdkDisk::getDiskSerial(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + return p->serial; + }else{ + return NULL; + } +} + +unsigned int KySdkDisk::getDiskPartitionNums(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + return p->partition_nums; + }else{ + return NULL; + } +} + +QString KySdkDisk::getDiskType(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + if(p->disk_type == DISK_TYPE_HDD) { + return "DISK_TYPE_HDD"; + } + else if(p->disk_type == DISK_TYPE_SSD) { + return "DISK_TYPE_SSD"; + } + else if (p->disk_type == DISK_TYPE_NONE) + { + return "DISK_TYPE_NONE"; + } + else return "DISK_TYPE_OTHER"; + }else{ + return NULL; + } +} + +QString KySdkDisk::getDiskVersion(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + return QString::fromLocal8Bit((p->fwrev)).remove("\n"); + }else{ + return NULL; + } +} + +unsigned long long KySdkDisk::getDiskSectorNum(const QString diskname) const +{ + std::string dn = diskname.toStdString(); + kdk_diskinfo* p = kdk_get_diskinfo(dn.c_str()); + if(p != NULL) + { + return p->sectors_num; + }else{ + return NULL; + } +} + +KySdkCpuInfo::KySdkCpuInfo(){} + +KySdkCpuInfo::~KySdkCpuInfo(){} + +QString KySdkCpuInfo::getCpuArch() const +{ + return QString::fromLocal8Bit(kdk_cpu_get_arch()); +} + +QString KySdkCpuInfo::getCpuVendor() const +{ + return QString::fromLocal8Bit(kdk_cpu_get_vendor()); +} + +QString KySdkCpuInfo::getCpuModel() const +{ + return QString::fromLocal8Bit(kdk_cpu_get_model()); +} + +QString KySdkCpuInfo::getCpuFreqMHz() const +{ + return QString::fromLocal8Bit(kdk_cpu_get_freq_MHz()); +} + +unsigned int KySdkCpuInfo::getCpuCorenums() const +{ + return kdk_cpu_get_corenums(); +} + +QString KySdkCpuInfo::getCpuVirt() const +{ + return QString::fromLocal8Bit(kdk_cpu_get_virt()); +} + +unsigned int KySdkCpuInfo::getCpuProcess() const +{ + return kdk_cpu_get_process(); +} + +KySdkNetCard::KySdkNetCard(){} + +KySdkNetCard::~KySdkNetCard(){} + +QStringList KySdkNetCard::getNetCardName() const +{ + QStringList res = {}; + char** net = kdk_nc_get_cardlist(); + for (int i = 0; net[i]; i++) + { + res << QString::fromLocal8Bit(net[i]); + } + return res; +} + +int KySdkNetCard::getNetCardUp(const QString netCardName) const +{ + std::string stdStr = netCardName.toStdString(); + return kdk_nc_is_up(stdStr.c_str()); +} + +QStringList KySdkNetCard::getNetCardUpcards() const +{ + QStringList res = {}; + char** upcards = kdk_nc_get_upcards(); + for (int i = 0; upcards[i]; i++) + { + res << QString::fromLocal8Bit(upcards[i]); + } + return res; +} + +QString KySdkNetCard::getNetCardPhymac(const QString netCardName) const +{ + std::string stdStr = netCardName.toStdString(); + return QString::fromLocal8Bit(kdk_nc_get_phymac(stdStr.c_str())); +} + +QString KySdkNetCard::getNetCardPrivateIPv4(const QString netCardName) const +{ + std::string stdStr = netCardName.toStdString(); + return QString::fromLocal8Bit(kdk_nc_get_private_ipv4(stdStr.c_str())); +} + +QStringList KySdkNetCard::getNetCardIPv4(const QString netCardName) const +{ + QStringList res; + char ** list = kdk_nc_get_ipv4(netCardName.toStdString().c_str()); + int i = 0; + while(list && list[i]) + { + res << list[i++]; + } + return res; +} + +QString KySdkNetCard::getNetCardPrivateIPv6(const QString netCardName) const +{ + std::string stdStr = netCardName.toStdString(); + return QString::fromLocal8Bit(kdk_nc_get_private_ipv6(stdStr.c_str())); +} + +int KySdkNetCard::getNetCardType(const QString netCardName) const +{ + std::string stdStr = netCardName.toStdString(); + return kdk_nc_is_wireless(stdStr.c_str()); +} + +QStringList KySdkNetCard::getNetCardProduct(const QString netCardName) const +{ + std::string ncn = netCardName.toStdString(); + QStringList res = {}; + char vend[256] = "", prod[256] = ""; + if(kdk_nc_get_vendor_and_product(ncn.c_str(), vend, prod) == 0) { + res << QString::fromLocal8Bit(vend).remove("\n"); + res << QString::fromLocal8Bit(prod).remove("\n"); + } +// qFatal("kdk_nc_get_vendor_and_product 调用失败"); + return res; +} + +QStringList KySdkNetCard::getNetCardIPv6(const QString netCardName) const +{ + QStringList res; + char **list = kdk_nc_get_ipv6(netCardName.toStdString().c_str()); + int i = 0; + while (list && list[i]) + { + res << list[i++]; + } + return res; +} + +KySdkBios::KySdkBios(){} + +KySdkBios::~KySdkBios(){} + +QString KySdkBios::getBiosVendor() const +{ + return QString::fromLocal8Bit(kdk_bios_get_vendor()).remove("\n"); +} + +QString KySdkBios::getBiosVersion() const +{ + return QString::fromLocal8Bit(kdk_bios_get_version()).remove("\n"); +} + +KySdkMainBoard::KySdkMainBoard(){} + +KySdkMainBoard::~KySdkMainBoard(){} + +QString KySdkMainBoard::getMainboardName() const +{ + return QString::fromLocal8Bit(kdk_board_get_name()).remove("\n"); +} + +QString KySdkMainBoard::getMainboardDate() const +{ + return QString::fromLocal8Bit(kdk_board_get_date()).remove("\n"); +} + +QString KySdkMainBoard::getMainboardSerial() const +{ + return QString::fromLocal8Bit(kdk_board_get_serial()).remove("\n"); +} + +QString KySdkMainBoard::getMainboardVendor() const +{ + return QString::fromLocal8Bit(kdk_board_get_vendor()).remove("\n"); +} + +KySdkPeripheralsEnum::KySdkPeripheralsEnum(){} + +QStringList KySdkPeripheralsEnum::getAllUsbInfo() const +{ + QStringList res = {}; + pDriverInfoList usb = kdk_usb_get_list(); + if(usb) + { + pDriverInfoList usb_list = usb; + while(usb_list) + { + res << "name:" + QString::fromLocal8Bit(usb_list->data->name) + "," + + "type:" + QString::fromLocal8Bit(usb_list->data->type) + "," + + "pid:" + QString::fromLocal8Bit(usb_list->data->pid) + "," + + "vid:" + QString::fromLocal8Bit(usb_list->data->vid) + "," + + "serialNo:" + QString::fromLocal8Bit(usb_list->data->serialNo) + "," + + "devNode:" + QString::fromLocal8Bit(usb_list->data->devNode); + usb_list = usb_list->next; + } + } + return res; +} + +KySdkPackageInfo::KySdkPackageInfo(){} + +QStringList KySdkPackageInfo::getPackageList() const +{ + QStringList res = {}; + kdk_package_list *pack = kdk_package_get_packagelist(); + qDebug() << pack->nums; + for (size_t i = 0; i < pack->nums; i ++) + { + res << "包名:" + QString::fromLocal8Bit(pack->list[i]->name) + "," + + "版本号:" + QString::fromLocal8Bit(pack->list[i]->version) + "," + + "包类型:" + QString::fromLocal8Bit(pack->list[i]->section) + "," + + "包状态:" + QString::fromLocal8Bit(pack->list[i]->status) + "," + + "包大小:" + QString::number(pack->list[i]->size_kb); + } + return res; +} + +QString KySdkPackageInfo::getPackageVersion(const QString packageName) const +{ + std::string package = packageName.toStdString(); + return QString::fromLocal8Bit(kdk_package_get_version(package.c_str())); +} + +int KySdkPackageInfo::getPackageInstalled(const QString packageName, const QString version) const +{ + std::string package = packageName.toStdString(); + std::string ver = version.toStdString(); + return kdk_package_is_installed(package.c_str(), ver.c_str()); +} + +KySdkResource::KySdkResource(){} + +double KySdkResource::getMemTotalKiB() const +{ + return static_cast(kdk_rti_get_mem_res_total_KiB()); +} + +double KySdkResource::getMemUsagePercent() const +{ + return static_cast(kdk_rti_get_mem_res_usage_percent()); +} + +double KySdkResource::getMemUsageKiB() const +{ + return static_cast(kdk_rti_get_mem_res_usage_KiB()); +} + +double KySdkResource::getMemAvailableKiB() const +{ + return static_cast(kdk_rti_get_mem_res_available_KiB()); +} + +double KySdkResource::getMemFreeKiB() const +{ + return static_cast(kdk_rti_get_mem_res_free_KiB()); +} + +double KySdkResource::getMemVirtAllocKiB() const +{ + return kdk_rti_get_mem_virt_alloc_KiB(); +} + +double KySdkResource::getMemSwapTotalKiB() const +{ + return static_cast(kdk_rti_get_mem_swap_total_KiB()); +} + +double KySdkResource::getMemSwapUsagePercent() const +{ + return static_cast(kdk_rti_get_mem_swap_usage_percent()); +} + +double KySdkResource::getMemSwapUsageKiB() const +{ + return static_cast(kdk_rti_get_mem_swap_usage_KiB()); +} + +double KySdkResource::getMemSwapFreeKiB() const +{ + return static_cast(kdk_rti_get_mem_swap_free_KiB()); +} + +double KySdkResource::getCpuCurrentUsage() const +{ + return static_cast(kdk_rti_get_cpu_current_usage()); +} + +QString KySdkResource::getUpTime() const +{ + unsigned int day, hour, min, sec; + if(kdk_rti_get_uptime(&day, &hour, &min, &sec) == 0) + { + return QString::number(day) + "天" + QString::number(hour) + "小时" + QString::number(min) + "分" + QString::number(sec) + "秒"; + } + +} + +KySdkProcess::KySdkProcess(){} + +double KySdkProcess::getProcInfoCpuUsage(int pid) const +{ + return static_cast(kdk_get_process_cpu_usage_percent(pid)); +} + +double KySdkProcess::getProcInfoMemUsage(int pid) const +{ + return static_cast(kdk_get_process_mem_usage_percent(pid)); +} + +QString KySdkProcess::getProcInfoStatus(int pid) const +{ + return QString::fromLocal8Bit(kdk_get_process_status(pid)); +} + +int KySdkProcess::getProcInfoPort(int pid) const +{ + return kdk_get_process_port(pid); +} + +QString KySdkProcess::getProcInfoStartTime(int pid) const +{ + return QString::fromLocal8Bit(kdk_get_process_start_time(pid)).remove("\n"); +} + +QString KySdkProcess::getProcInfoRunningTime(int pid) const +{ + return QString::fromLocal8Bit(kdk_get_process_running_time(pid)); +} + +QString KySdkProcess::getProcInfoCpuTime(int pid) const +{ + return QString::fromLocal8Bit(kdk_get_process_cpu_time(pid)); +} + +QString KySdkProcess::getProcInfoCmd(int pid) const +{ + return QString::fromLocal8Bit(kdk_get_process_command(pid)); +} + +QString KySdkProcess::getProcInfoUser(int pid) const +{ + return QString::fromLocal8Bit(kdk_get_process_user(pid)); +} + +QStringList KySdkProcess::getProcInfo(const QString procName) const +{ + std::string pN = procName.toStdString(); + QStringList res = {}; + char **proc = kdk_procname_get_process_infomation(const_cast(pN.c_str())); + size_t count = 0; + while (proc[count]) + { + res << QString::fromLocal8Bit(proc[count]).remove("\n"); + count++; + } + kdk_proc_freeall(proc); + return res; +} + +QStringList KySdkProcess::getAllProcInfo() const +{ + QStringList res = {}; + char** proc_info = kdk_get_process_all_information(); + size_t count = 0; + while (proc_info[count]) + { + res << QString::fromLocal8Bit(proc_info[count]).remove("\n"); + count++; + } + kdk_proc_freeall(proc_info); + return res; +} + +KySdkResolution::KySdkResolution(){} + +QString KySdkResolution::getSystemArchitecture() const +{ + return QString::fromLocal8Bit(kdk_system_get_architecture()); +} + +QString KySdkResolution::getSystemName() const +{ + return QString::fromLocal8Bit(kdk_system_get_systemName()); +} + +QString KySdkResolution::getSystemVersion(bool verbose) const +{ + return QString::fromLocal8Bit(kdk_system_get_version(verbose)); +} + +int KySdkResolution::getSystemActivationStatus() const +{ + int p,d; + return kdk_system_get_activationStatus(&p, &d); +} + +QString KySdkResolution::getSystemSerialNumber() const +{ + return QString::fromLocal8Bit(kdk_system_get_serialNumber()).remove("\n");; +} + +QString KySdkResolution::getSystemKernelVersion() const +{ + return QString::fromLocal8Bit(kdk_system_get_kernelVersion()).remove("\n"); +} + +QString KySdkResolution::getSystemEffectUser() const +{ + return QString::fromLocal8Bit(kdk_system_get_eUser()); +} + +QString KySdkResolution::getSystemProjectName() const +{ + return QString::fromLocal8Bit(kdk_system_get_projectName()); +} + +QString KySdkResolution::getSystemProjectSubName() const +{ + return QString::fromLocal8Bit(kdk_system_get_projectSubName()); +} + +unsigned int KySdkResolution::getSystemProductFeatures() const +{ + return kdk_system_get_productFeatures(); +} + +QString KySdkResolution::getSystemHostVirtType() const +{ + return QString::fromLocal8Bit(kdk_system_get_hostVirtType()); +} + +QString KySdkResolution::getSystemHostCloudPlatform() const +{ + return QString::fromLocal8Bit(kdk_system_get_hostCloudPlatform()); +} + +QString KySdkResolution::getSystemOSVersion() const +{ + version_t ver = kdk_system_get_version_detaile(); + return QString::fromLocal8Bit(ver.os_version); +} + +QString KySdkResolution::getSystemUpdateVersion() const +{ + version_t ver = kdk_system_get_version_detaile(); + return QString::fromLocal8Bit(ver.update_version); +} + +bool KySdkResolution::getSystemIsZYJ() const +{ + return kdk_system_is_zyj(); +} + +KySdkNet::KySdkNet(){} + +int KySdkNet::getPortState(int port) const +{ + return kdk_net_get_port_stat(port); +} + +QStringList KySdkNet::getMultiplePortStat(int start, int end) const +{ + QStringList res = {}; + if(end < start) + return res; + int result[end - start + 1]; + if(0 == kdk_net_get_multiple_port_stat(start, end, result)) + { + size_t count = 0; + int num = end - start + 1; + for(;count < num; count++) + { + res << QString::number(result[count]); + } + } + return res; +} + +QStringList KySdkNet::getGatewayInfo() const +{ + prouteMapList p = kdk_net_get_route(); + QStringList res = {}; + if(p == NULL) + { + return res; + } + while(p) { + res << QString(QLatin1String(p->name)) << QString(QLatin1String(p->addr)); + p = p->next; + } + return res; +} + +QStringList KySdkNet::getFirewallState() const +{ + QStringList res = {}; + pChain chain = kdk_net_get_iptable_rules(); + pChain tmpchain = chain; + while(tmpchain) + { + pChainData tmpData = tmpchain->data; + res << "total:" + QString::fromLocal8Bit(tmpchain->total) + "," + + "policy:" + QString::fromLocal8Bit(tmpchain->policy); + if(tmpData) + { + res << "target:" + QString::fromLocal8Bit(tmpData->target) + "," + + "prot:" + QString::fromLocal8Bit(tmpData->prot) + "," + + "opt:" + QString::fromLocal8Bit(tmpData->opt) + "," + + "source:" + QString::fromLocal8Bit(tmpData->source)+ "," + + "destination:" + QString::fromLocal8Bit(tmpData->destination) + "," + + "option:" + QString::fromLocal8Bit(tmpData->option); + } + tmpchain = tmpchain->next; + } + return res; +} + +KySdkRunInfo::KySdkRunInfo(){} + +double KySdkRunInfo::getNetSpeed(const QString nc) const +{ + std::string dp = nc.toStdString(); + return static_cast(rTI.kdk_real_get_net_speed(dp.c_str())); +} + +int KySdkRunInfo::getDiskRate(const QString diskpath) const +{ + std::string dp = diskpath.toStdString(); + return rTI.kdk_real_get_disk_rate(dp.c_str()); +} + +double KySdkRunInfo::getCpuTemperature() const +{ + return static_cast(rTI.kdk_real_get_cpu_temperature()); +} +double KySdkRunInfo::getDiskTemperature(const QString diskpath) const +{ + std::string dp = diskpath.toStdString(); + return static_cast(rTI.kdk_real_get_disk_temperature(dp.c_str())); +} + +KySdkGps::KySdkGps(){} +QString KySdkGps::getGPSInfo() const +{ + return QString::fromLocal8Bit(kdk_loaction_get()); +} + +KySdkPrint::KySdkPrint(){} + +QStringList KySdkPrint::getPrinterList() const +{ + QStringList res = {}; + char **printers = kdk_printer_get_list(); + size_t index = 0; + while (printers[index]) + { + res << QString::fromLocal8Bit(printers[index]); + index ++; + } + return res; +} + +QStringList KySdkPrint::getPrinterAvailableList() const +{ + QStringList res = {}; + char **printers = kdk_printer_get_available_list(); + size_t index = 0; + while (printers[index]) + { + res << QString::fromLocal8Bit(printers[index]); + index ++; + } + return res; +} + +void KySdkPrint::setPrinterOptions(int number_up, const QString media, const QString number_up_layout, const QString sides) const +{ + std::string med = media.toStdString(); + std::string nul = number_up_layout.toStdString(); + std::string sid = sides.toStdString(); + kdk_printer_set_options(number_up, med.c_str(), nul.c_str(), sid.c_str()); +} + +int KySdkPrint::getPrinterPrintLocalFile (const QString printername, const QString filepath) const +{ + std::string printers = printername.toStdString(); + std::string fp = filepath.toStdString(); + return kdk_printer_print_local_file(printers.c_str(), fp.c_str()); +} + +int KySdkPrint::getPrinterCancelAllJobs(const QString printername) const +{ + std::string printers = printername.toStdString(); + return kdk_printer_cancel_all_jobs(printers.c_str()); +} + +int KySdkPrint::getPrinterStatus(const QString printername) const +{ + std::string printers = printername.toStdString(); + return kdk_printer_get_status(printers.c_str()); +} + +QString KySdkPrint::getPrinterFilename(const QString url) const +{ + std::string urlpath = url.toStdString(); + + return QString::fromLocal8Bit(kdk_printer_get_filename(urlpath.c_str())); +} +int KySdkPrint::getPrinterJobStatus(const QString printername, int jobid) const +{ + std::string printers = printername.toStdString(); + return kdk_printer_get_job_status(printers.c_str(), jobid); +} + +int KySdkPrint::getPrinterDownloadRemoteFile(const QString url, const QString filepath) const +{ + std::string urlpath = url.toStdString(); + std::string fp = filepath.toStdString(); + return kdk_printer_print_download_remote_file(urlpath.c_str(), fp.c_str()); +} + +KySdkRest::KySdkRest(){} + +KySdkRest::~KySdkRest(){} + +QStringList KySdkRest::getSysLegalResolution() const +{ + QStringList res = {}; + char **power = kdk_system_get_resolving_power(); + size_t count = 0; + while(power[count]) + { + res << QString::fromLocal8Bit(power[count]); + count ++; + } + kdk_resolving_freeall(power); + return res; +} \ No newline at end of file diff --git a/src/kysdkdbus/kysdkdbus.h b/src/kysdkdbus/kysdkdbus.h new file mode 100755 index 0000000..da954b0 --- /dev/null +++ b/src/kysdkdbus/kysdkdbus.h @@ -0,0 +1,281 @@ +#ifndef KYSDKDBUS_H +#define KYSDKDBUS_H + +#include +#include +#include +#include +#include + +#include + +#include "../systeminfo/libkysysinfo.h" // 分辨率 +#include "../hardware/libkync.h" // 网卡 +#include "../hardware/libkybios.h" // BIOS +#include "../disk/libkydiskinfo.h" // 磁盘 +#include "../proc/libkyrtinfo.h" // 进程 +#include "../proc/libkyprocess.h" // 进程 +#include "../hardware/libkyboard.h" // 主板 +#include "../net/libkynetinfo.h" // 网络 +#include "../realtime/libkyrealtimeinfo.h" // 运行时信息 +#include "../hardware/libkyusb.h" // USB外设 +#include "../hardware/libkycpu.h" //CPU +#include "../hardware/libkyprinter.h" //打印 +#include "../location/libkylocation.h" //地理位置 +#include "../packages/libkypackages.h" //包 + +// 返回磁盘类型(机械/固态),额定转速,固件版本信息?,扇区数量 +class KySdkDisk : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.disk") +public: + explicit KySdkDisk(); + ~KySdkDisk(); +public slots: + QStringList getDiskList() const; + unsigned int getDiskSectorSize(const QString diskname) const; + double getDiskTotalSizeMiB(const QString diskname) const; + QString getDiskModel(const QString diskname) const; + QString getDiskSerial(const QString diskname) const; + unsigned int getDiskPartitionNums(const QString diskname) const; + QString getDiskType(const QString diskname) const; + QString getDiskVersion(const QString diskname) const; + unsigned long long getDiskSectorNum(const QString diskname) const; +}; + +// 获取CPU架构、制造厂商、型号、额定主频、核心数量、虚拟化的支持、线程数 +class KySdkCpuInfo : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.cpuinfo") +public: + explicit KySdkCpuInfo(); + ~KySdkCpuInfo(); +public slots: + QString getCpuArch() const; + QString getCpuVendor() const; + QString getCpuModel() const; + QString getCpuFreqMHz() const; + unsigned int getCpuCorenums() const; + QString getCpuVirt() const; + unsigned int getCpuProcess() const; +}; + +// 返回网卡驱动、型号、设备类型、制造商 +class KySdkNetCard : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.netcard") +public: + explicit KySdkNetCard(); + ~KySdkNetCard(); +public slots: + QStringList getNetCardName() const; + int getNetCardUp(const QString netCardName) const; + QStringList getNetCardUpcards() const; + QString getNetCardPhymac(const QString netCardName) const; + QString getNetCardPrivateIPv4(const QString netCardName) const; + QStringList getNetCardIPv4(const QString netCardName) const; + QString getNetCardPrivateIPv6(const QString netCardName) const; + int getNetCardType(const QString netCardName) const; + QStringList getNetCardProduct(const QString netCardName) const; + QStringList getNetCardIPv6(const QString netCardName) const; +}; + +// 返回BIOS厂商及版本号 +class KySdkBios : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.bios") +public: + explicit KySdkBios(); + ~KySdkBios(); +public slots: + QString getBiosVendor() const; + QString getBiosVersion() const; +}; + +// 返回主板型号、发布日期、序列号、厂商 +class KySdkMainBoard : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.mainboard") +public: + explicit KySdkMainBoard(); + ~KySdkMainBoard(); +public slots: + QString getMainboardName() const; + QString getMainboardDate() const; + QString getMainboardSerial() const; + QString getMainboardVendor() const; +}; + +// 获取所有usb设备的:名称、类型、PID、VID、序列号、状态 +class KySdkPeripheralsEnum : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.peripheralsenum") +public: + explicit KySdkPeripheralsEnum(); +public slots: + QStringList getAllUsbInfo() const; +}; + +// 获取所有包列表、系统中指定包的版本号、检测指定包名的软件包是否正确安装 +class KySdkPackageInfo : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.packageinfo") +public: + explicit KySdkPackageInfo(); +public slots: + QStringList getPackageList() const; + QString getPackageVersion(const QString packageName) const; + int getPackageInstalled(const QString packageName, const QString version) const; +}; + +// 系统中物理内存总大小、物理内存使用率、物理内存使用大小、实际可用的物理内存大小 +// 实际空闲的物理内存大小、所有应用申请的虚拟内存总量、Swap分区总大小、Swap分区使用率 +// Swap分区使用量、Swap分区空闲大小、CPU瞬时使用率、开机天数 +class KySdkResource : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.resource") +public: + explicit KySdkResource(); +public slots: + double getMemTotalKiB() const; + double getMemUsagePercent() const; + double getMemUsageKiB() const; + double getMemAvailableKiB() const; + double getMemFreeKiB() const; + double getMemVirtAllocKiB() const; + double getMemSwapTotalKiB() const; + double getMemSwapUsagePercent() const; + double getMemSwapUsageKiB() const; + double getMemSwapFreeKiB() const; + double getCpuCurrentUsage() const; + QString getUpTime() const; +}; + +// 返回进程相关信息 + // CPU使用率、内存占用率、进程状态、使用的端口号 + // 启动时间、CPU时间、cmd、进程属主 +class KySdkProcess : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.process") +public: + explicit KySdkProcess(); +public slots: + double getProcInfoCpuUsage(int pid) const; + double getProcInfoMemUsage(int pid) const; + QString getProcInfoStatus(int pid) const; + int getProcInfoPort(int pid) const; + QString getProcInfoStartTime(int pid) const; + QString getProcInfoRunningTime(int pid) const; + QString getProcInfoCpuTime(int pid) const; + QString getProcInfoCmd(int pid) const; + QString getProcInfoUser(int pid) const; + QStringList getProcInfo(const QString procName) const; + QStringList getAllProcInfo() const; +}; + +// 获取屏幕以及屏幕支持的分辨率,获取屏幕设置的分辨率 +class KySdkResolution : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.sysinfo") +public: + explicit KySdkResolution(); +public slots: + QString getSystemArchitecture() const; + QString getSystemName() const; + QString getSystemVersion(bool verbose) const; + int getSystemActivationStatus() const; + QString getSystemSerialNumber() const; + QString getSystemKernelVersion() const; + QString getSystemEffectUser() const; + QString getSystemProjectName() const; + QString getSystemProjectSubName() const; + unsigned int getSystemProductFeatures() const; + QString getSystemHostVirtType() const; + QString getSystemHostCloudPlatform() const; + QString getSystemOSVersion() const; + QString getSystemUpdateVersion() const; + bool getSystemIsZYJ() const; +}; + +// 获取端口状态,网关信息,防火墙状态 +class KySdkNet : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.net") +public: + explicit KySdkNet(); +public slots: + int getPortState(int port) const; + QStringList getMultiplePortStat(int start, int end) const; + QStringList getGatewayInfo() const; + QStringList getFirewallState() const; +}; + +// 返回I/O读写速率,瞬时网速,主板温度,CPU温度,硬盘温度 +class KySdkRunInfo : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.runinfo") +public: + explicit KySdkRunInfo(); +public slots: + double getNetSpeed(const QString nc) const; + int getDiskRate(const QString diskpath) const; + double getCpuTemperature() const; + double getDiskTemperature(const QString diskpath) const; +private: + mutable RealTimeInfo rTI; +}; + +// 返回国家城市经纬度 +class KySdkGps : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.gps") +public: + explicit KySdkGps(); +public slots: + QString getGPSInfo() const; +private: +}; + +class KySdkPrint : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.print") +public: + explicit KySdkPrint(); +public slots: + QStringList getPrinterList() const; + QStringList getPrinterAvailableList() const; + void setPrinterOptions(int number_up, const QString media, const QString number_up_layout, const QString sides) const; + int getPrinterPrintLocalFile (const QString printername, const QString filepath) const; + int getPrinterCancelAllJobs(const QString printername) const; + int getPrinterStatus(const QString printername) const; + QString getPrinterFilename(const QString url) const; + int getPrinterJobStatus(const QString printername, int jobid) const; + int getPrinterDownloadRemoteFile(const QString url, const QString filepath) const; +}; + +class KySdkRest : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.resolution") +public: + explicit KySdkRest(); + ~KySdkRest(); +public slots: + QStringList getSysLegalResolution() const; +}; + +#endif // KYSDKDBUS_H diff --git a/src/kysdkdbus/kysdkrest.cpp b/src/kysdkdbus/kysdkrest.cpp new file mode 100644 index 0000000..f45a219 --- /dev/null +++ b/src/kysdkdbus/kysdkrest.cpp @@ -0,0 +1,20 @@ +#include "kysdkrest.h" +#include "../systeminfo/libkysysinfo.h" // 分辨率 + +KySdkRest::KySdkRest(QObject *parent) : QObject(parent) +{ + +} +QStringList KySdkRest::getSysLegalResolution() const +{ + QStringList res = {}; + char **power = kdk_system_get_resolving_power(); + size_t count = 0; + while(power[count]) + { + res << QString::fromLocal8Bit(power[count]); + count ++; + } + kdk_resolving_freeall(power); + return res; +} diff --git a/src/kysdkdbus/kysdkrest.h b/src/kysdkdbus/kysdkrest.h new file mode 100644 index 0000000..9c01a58 --- /dev/null +++ b/src/kysdkdbus/kysdkrest.h @@ -0,0 +1,24 @@ +#ifndef KYSDKREST_H +#define KYSDKREST_H + +#include +#include +#include +#include +#include + +#include + +class KySdkRest : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.kylin.kysdk.resolution") +public: + explicit KySdkRest(QObject *parent = nullptr); + +public slots: + QStringList getSysLegalResolution() const; + +}; + +#endif // KYSDKREST_H diff --git a/src/kysdkdbus/main.cpp b/src/kysdkdbus/main.cpp new file mode 100755 index 0000000..a27044c --- /dev/null +++ b/src/kysdkdbus/main.cpp @@ -0,0 +1,137 @@ +#include +#include "kysdkdbus.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + QDBusConnection connection = QDBusConnection::systemBus(); + if(!connection.registerService("com.kylin.kysdk.service")) + { + qDebug() << "com.kylin.kysdk.service 服务注册失败"; + return -1; + } + + KySdkDisk disk; + if(!connection.registerObject("/com/kylin/kysdk/disk", &disk, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/disk 对象注册失败"; + return -1; + } + + KySdkCpuInfo cpuinfo; + if(!connection.registerObject("/com/kylin/kysdk/cpuinfo", &cpuinfo, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/cpuinfo 对象注册失败"; + return -1; + } + + KySdkNetCard netcard; + if(!connection.registerObject("/com/kylin/kysdk/netcard", &netcard, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/netcard 对象注册失败"; + return -1; + } + + KySdkBios bios; + if(!connection.registerObject("/com/kylin/kysdk/bios", &bios, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/bios 对象注册失败"; + return -1; + } + + KySdkMainBoard mainboard; + if(!connection.registerObject("/com/kylin/kysdk/mainboard", &mainboard, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/mainboard 对象注册失败"; + return -1; + } + + KySdkPeripheralsEnum peripheralsenum; + if(!connection.registerObject("/com/kylin/kysdk/peripheralsenum", &peripheralsenum, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/peripheralsenum 对象注册失败"; + return -1; + } + + KySdkPackageInfo packageinfo; + if(!connection.registerObject("/com/kylin/kysdk/packageinfo", &packageinfo, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/packageinfo 对象注册失败"; + return -1; + } + + KySdkResource resource; + if(!connection.registerObject("/com/kylin/kysdk/resource", &resource, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/resource 对象注册失败"; + return -1; + } + + KySdkProcess process; + if(!connection.registerObject("/com/kylin/kysdk/process", &process, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/process 对象注册失败"; + return -1; + } + + KySdkResolution resolution; + if(!connection.registerObject("/com/kylin/kysdk/sysinfo", &resolution, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/sysinfo 对象注册失败"; + return -1; + } + + KySdkNet net; + if(!connection.registerObject("/com/kylin/kysdk/net", &net, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/net 对象注册失败"; + return -1; + } + + KySdkRunInfo runinfo; + if(!connection.registerObject("/com/kylin/kysdk/runinfo", &runinfo, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/runinfo 对象注册失败"; + return -1; + } + + KySdkGps gps; + if(!connection.registerObject("/com/kylin/kysdk/gps", &gps, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/gps 对象注册失败"; + return -1; + } + + KySdkPrint print; + if(!connection.registerObject("/com/kylin/kysdk/print", &print, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/print 对象注册失败"; + return -1; + } + + // QDBusConnection con = QDBusConnection::sessionBus(); + // KySdkRest res; + // if(!con.registerObject("/com/kylin/kysdk/resolution", &res, + // QDBusConnection::ExportAllContents)) + // { + // qDebug() << "/com/kylin/kysdk/resolution 对象注册失败"; + // return -1; + // } + + return a.exec(); +} diff --git a/src/kysdkdbus/main_rest.cpp b/src/kysdkdbus/main_rest.cpp new file mode 100644 index 0000000..845eaad --- /dev/null +++ b/src/kysdkdbus/main_rest.cpp @@ -0,0 +1,25 @@ +#include +#include "kysdkrest.h" + + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + QDBusConnection con = QDBusConnection::sessionBus(); + KySdkRest res; + if(!con.registerService("com.kylin.kysdk.service")) + { + qDebug() << "com.kylin.kysdk.service 服务注册失败"; + return -1; + } + + if(!con.registerObject("/com/kylin/kysdk/resolution", &res, + QDBusConnection::ExportAllContents)) + { + qDebug() << "/com/kylin/kysdk/resolution 对象注册失败"; + return -1; + } + + return a.exec(); +} diff --git a/src/libkysdk-system-javascript-http/CMakeLists.txt b/src/libkysdk-system-javascript-http/CMakeLists.txt new file mode 100644 index 0000000..6e08c13 --- /dev/null +++ b/src/libkysdk-system-javascript-http/CMakeLists.txt @@ -0,0 +1,75 @@ +cmake_minimum_required(VERSION 3.13) +#project(libkysdk-system-javascript-httpserver-1.0) +project(libkysdk-system-javascript-http) +include(CMakePackageConfigHelpers) + +include_directories("${PROJECT_BINARY_DIR}") + +set(TARGET httpServer) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_AUTOMOC ON) +set (CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(SOURCES + kysdk/bios.cpp + kysdk/cpuinfo.cpp + kysdk/dependence.cpp + kysdk/disk.cpp + kysdk/gps.cpp + kysdk/kysdkdbus.cpp + kysdk/packageinfo.cpp + kysdk/printer.cpp + kysdk/resource.cpp + kysdk/sysinfo.cpp + main.cpp + kysdk/mainboard.cpp + kysdk/net.cpp + kysdk/netcard.cpp + kysdk/netlink.cpp + kysdk/peripheralsenum.cpp + kysdk/process.cpp + kysdk/resolutionctl.cpp + kysdk/runinfo.cpp + kysdk/widget.cpp + kysdk/customerevent.cpp + kysdk/jqhttpserver.cpp + ) +set(HEADERS + kysdk/bios.h + kysdk/cpuinfo.h + kysdk/dependence.h + kysdk/disk.h + kysdk/gps.h + kysdk/kysdkdbus.h + kysdk/mainboard.h + kysdk/net.h + kysdk/netcard.h + kysdk/netlink.h + kysdk/packageinfo.h + kysdk/peripheralsenum.h + kysdk/printer.h + kysdk/process.h + kysdk/resolutionctl.h + kysdk/resource.h + kysdk/runinfo.h + kysdk/sysinfo.h + kysdk/widget.h + kysdk/customerevent.h + kysdk/jqhttpserver.h + ) +#set(FORMS kysdk/widget.ui) +#set(RESOURCES qrc.qrc) +set(QT Core Gui Widgets DBus Network Concurrent) +find_package(Qt5 REQUIRED ${QT}) +add_executable(${TARGET} ${SOURCES} ${HEADERS} ) +target_link_libraries(${TARGET} + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::DBus + Qt5::Network + Qt5::Concurrent + ) +set (CMAKE_INSTALL_PREFIX /usr) +set (LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}) +set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}) diff --git a/src/libkysdk-system-javascript-http/conf/kysdk.conf b/src/libkysdk-system-javascript-http/conf/kysdk.conf new file mode 100644 index 0000000..d6bb6ae --- /dev/null +++ b/src/libkysdk-system-javascript-http/conf/kysdk.conf @@ -0,0 +1,2 @@ +[kysdk] +port=8888 \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/conf/kysdkHttpServer.desktop b/src/libkysdk-system-javascript-http/conf/kysdkHttpServer.desktop new file mode 100644 index 0000000..5579087 --- /dev/null +++ b/src/libkysdk-system-javascript-http/conf/kysdkHttpServer.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Application +Exec=/usr/bin/httpServer +Name=kysdk-httpServer +Comment=provide kysdk dbus js interface +X-MATE_Autostart-Delay=15 \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/conf/kysdkHttpServer.service b/src/libkysdk-system-javascript-http/conf/kysdkHttpServer.service new file mode 100644 index 0000000..5361622 --- /dev/null +++ b/src/libkysdk-system-javascript-http/conf/kysdkHttpServer.service @@ -0,0 +1,12 @@ +[Unit] +Description= kysdkHttpServer + +[Service] +Type=simple +User=root +Restart=1 # 如果这个服务意外停止,那么就重启 +RestartSec=3s +ExecStart=/usr/bin/httpServer + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Bios.html b/src/libkysdk-system-javascript-http/html/Bios.html new file mode 100644 index 0000000..3beac45 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Bios.html @@ -0,0 +1,41 @@ + + + + BIOS信息测试页面 + + + BIOS信息测试页面 + + + + + + +
+
+ + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Cpuinfo.html b/src/libkysdk-system-javascript-http/html/Cpuinfo.html new file mode 100644 index 0000000..059fb2e --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Cpuinfo.html @@ -0,0 +1,66 @@ + + + + CPU信息测试页面 + + + CPU信息测试页面 + + + + + + +
+
+ + + + + + + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Disk.html b/src/libkysdk-system-javascript-http/html/Disk.html new file mode 100644 index 0000000..d15db55 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Disk.html @@ -0,0 +1,98 @@ + + + + 磁盘测试页面 + + + 磁盘测试页面 + + + + + + +
+
+ + + + + + + + + +
+
+

磁盘名称:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Gps.html b/src/libkysdk-system-javascript-http/html/Gps.html new file mode 100644 index 0000000..ba7d3b3 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Gps.html @@ -0,0 +1,37 @@ + + + + 地理位置测试页面 + + + 地理位置测试页面 + + + + + + +
+
+ + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Mainboard.html b/src/libkysdk-system-javascript-http/html/Mainboard.html new file mode 100644 index 0000000..6a328d4 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Mainboard.html @@ -0,0 +1,52 @@ + + + + 主板信息测试页面 + + + 主板信息测试页面 + + + + + + +
+
+ + + + + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Net.html b/src/libkysdk-system-javascript-http/html/Net.html new file mode 100644 index 0000000..4284cf7 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Net.html @@ -0,0 +1,67 @@ + + + + 网络测试页面 + + + 网络测试页面 + + + + + + +
+
+ + +
+ + +
+
+

端口号:

+ +

结束端口号:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/NetLink.html b/src/libkysdk-system-javascript-http/html/NetLink.html new file mode 100644 index 0000000..8238867 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/NetLink.html @@ -0,0 +1,37 @@ + + + + 网络连接测试页面 + + + 网络连接测试页面 + + + + + + +
+
+ + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Netcard.html b/src/libkysdk-system-javascript-http/html/Netcard.html new file mode 100644 index 0000000..ddf17d5 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Netcard.html @@ -0,0 +1,105 @@ + + + + 网卡信息测试页面 + + + 网卡信息测试页面 + + + + + + +
+
+ + + + + + + + + + + +
+
+

网卡名称:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Packageinfo.html b/src/libkysdk-system-javascript-http/html/Packageinfo.html new file mode 100644 index 0000000..90c2af0 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Packageinfo.html @@ -0,0 +1,59 @@ + + + + 包列表信息测试页面 + + + 包列表信息测试页面 + + + + + + +
+
+ + + +
+
+

软件包名:

+ +

版本号:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Peripheralsenum.html b/src/libkysdk-system-javascript-http/html/Peripheralsenum.html new file mode 100644 index 0000000..e5aadc9 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Peripheralsenum.html @@ -0,0 +1,37 @@ + + + + 外设设备枚举测试页面 + + + 外设设备枚举测试页面 + + + + + + +
+
+ + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Printer.html b/src/libkysdk-system-javascript-http/html/Printer.html new file mode 100644 index 0000000..ea996a8 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Printer.html @@ -0,0 +1,135 @@ + + + + 打印机管理测试页面 + + + 打印机管理测试页面 + + + + + + +
+
+ + + + + + + + + + +
+
+

打印设置:

+ +
+
+

打印作业的id设置:

+ +
+
+

取消所有任务或打印机状态码设置:

+ +
+
+

解析出来的文件名设置:

+ +
+
+

打印任务状态码设置:

+ +
+
+

下载状态码设置:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Process.html b/src/libkysdk-system-javascript-http/html/Process.html new file mode 100644 index 0000000..a3d244a --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Process.html @@ -0,0 +1,134 @@ + + + + 进程信息测试页面 + + + 进程信息测试页面 + + + + + + +
+
+ +
+
+ + + + + +
+
+
+ + + + + +
+
+

进程号:

+ +
+
+
+ +
+
+

进程名:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Resolutionctl.html b/src/libkysdk-system-javascript-http/html/Resolutionctl.html new file mode 100644 index 0000000..5b895d2 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Resolutionctl.html @@ -0,0 +1,37 @@ + + + + 分辨率测试页面 + + + 分辨率测试页面 + + + + + + +
+
+ + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Resource.html b/src/libkysdk-system-javascript-http/html/Resource.html new file mode 100644 index 0000000..750007c --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Resource.html @@ -0,0 +1,92 @@ + + + + 系统资源占用信息测试页面 + + + 系统资源占用信息测试页面 + + + + + + +
+
+ + + + + + + + + + + + + +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Runinfo.html b/src/libkysdk-system-javascript-http/html/Runinfo.html new file mode 100644 index 0000000..a4c4dca --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Runinfo.html @@ -0,0 +1,64 @@ + + + + 运行时信息测试页面 + + + 运行时信息测试页面 + + + + + + +
+
+ +
+ + + + +
+
+

网卡名或硬盘绝对路径:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/Sysinfo.html b/src/libkysdk-system-javascript-http/html/Sysinfo.html new file mode 100644 index 0000000..87ef21a --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/Sysinfo.html @@ -0,0 +1,121 @@ + + + + 操作系统基础信息测试页面 + + + B操作系统基础信息测试页面 + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+ +
+
+

版本号:

+ +
+
+
回复:
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/css/select.css b/src/libkysdk-system-javascript-http/html/css/select.css new file mode 100755 index 0000000..f5cac5c --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/css/select.css @@ -0,0 +1,5 @@ +/*屏蔽页面选中文本*/ +*{ + user-select:none; + moz-user-select: -moz-none;moz-user-select: none;-o-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none; + } \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/css/style.css b/src/libkysdk-system-javascript-http/html/css/style.css new file mode 100755 index 0000000..7132ce9 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/css/style.css @@ -0,0 +1,482 @@ +body { + margin: 0; + padding: 0; +} + +.box { + position: relative; + height: 800px; + width: 80%; + margin: auto; + margin-top: 50px; + background: white; + text-align: center; +} + +h1 { + padding-top: 20px; + font-family: "Do Hyeon", sans-serif; +} + +.spinner { + border-radius: 50%; + margin: 20px; + display: inline-block; + position: relative; + height: 60px; + width: 60px; + background-color: #e0e0eb; +} + +.spinnerOne::after { + content: ""; + position: absolute; + border-radius: 50%; + top: -5px; + left: -5px; + height: 60px; + width: 60px; + border: 5px solid transparent; + border-top-color: #7389AE; + animation: spinneroneani 0.5s linear infinite; +} + +@keyframes spinneroneani { + 25% { + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: #05A8AA; + } + 50% { + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: #416788; + border-left-color: transparent; + transform: rotate(180deg) scale(1.2); + } + 75% { + border-top-color: transparent; + border-right-color: #2F6690; + border-bottom-color: transparent; + border-left-color: transparent; + } + 100% { + border-top-color: #7389AE; + border-right-color: transparent; + border-bottom-color: transparent; + border-left-color: transparent; + transform: rotate(360deg) scale(1); + } +} +.spinnerTwo:before { + position: absolute; + top: 30px; + left: 27.5px; + content: ""; + height: 35px; + width: 5px; + background-color: #535657; + transform-origin: 50% 0%; + border-radius: 8px; + animation: clockmin 2s linear infinite; +} +.spinnerTwo:after { + position: absolute; + content: ""; + height: 35px; + width: 2px; + top: 30px; + left: 29px; + transform-origin: 50% 0%; + background-color: #D1462F; + border-radius: 2px; + animation: clocksec 0.5s linear infinite; +} +.spinnerTwo span { + position: absolute; + top: 10px; + left: 27.5px; + height: 20px; + width: 5px; + border-radius: 2px; + background: #535657; + transform-origin: 50% 100%; + animation: clockhour 10s linear infinite; +} + +@keyframes clockhour { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +@keyframes clockmin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +@keyframes clocksec { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +.spinnerThree { + background-color: white; + border: 3px solid #e0e0eb; +} + +.spinnerThree span { + height: 3px; + width: 3px; + border-radius: 50%; + position: absolute; + background: #7389AE; +} + +.spinnerThree > span:nth-child(1) { + top: 28px; + left: 8px; + animation: bounce 2s linear 0s infinite; +} + +.spinnerThree > span:nth-child(2) { + top: 28px; + left: 14px; + animation: bounce 2s linear 0.09s infinite; +} + +.spinnerThree > span:nth-child(3) { + top: 28px; + left: 20px; + animation: bounce 2s linear 0.18s infinite; +} + +.spinnerThree > span:nth-child(4) { + top: 28px; + left: 26px; + animation: bounce 2s linear 0.27s infinite; +} + +.spinnerThree > span:nth-child(5) { + top: 28px; + left: 32px; + animation: bounce 2s linear 0.36s infinite; +} + +.spinnerThree > span:nth-child(6) { + top: 28px; + left: 38px; + animation: bounce 2s linear 0.45s infinite; +} + +.spinnerThree > span:nth-child(7) { + top: 28px; + left: 44px; + animation: bounce 2s linear 0.54s infinite; +} + +.spinnerThree > span:nth-child(8) { + top: 28px; + left: 50px; + animation: bounce 2s linear 0.63s infinite; +} + +@keyframes bounce { + 0%, 10%, 100% { + top: 28px; + transform: scale(1); + } + 5% { + top: 20px; + transform: scale(2); + } +} +.spinnerFour { + overflow: hidden; + -webkit-mask-image: -webkit-radial-gradient(white, black); +} + +.spinnerFour > span { + height: 60px; + width: 60px; + position: absolute; + border-radius: 50%; + transform: scale(0); +} + +.spinnerFour > span:nth-child(1) { + top: -2px; + left: -11px; + background: #b9dcfa; + animation: grow 2s linear 0s infinite; +} + +.spinnerFour > span:nth-child(2) { + top: 15px; + left: 6px; + background: #4e9e22; + animation: grow 2s linear 0.5s infinite; +} + +.spinnerFour > span:nth-child(3) { + top: -3px; + left: -8px; + background: #1df26c; + animation: grow 2s linear 1s infinite; +} + +.spinnerFour > span:nth-child(4) { + top: -14px; + left: -1px; + background: #683e54; + animation: grow 2s linear 1.5s infinite; +} + +.spinnerFour > span:nth-child(5) { + top: -3px; + left: 14px; + background: #daf610; + animation: grow 2s linear 2s infinite; +} + +.spinnerFour > span:nth-child(6) { + top: -10px; + left: 7px; + background: #cd4902; + animation: grow 2s linear 2.5s infinite; +} + +.spinnerFour > span:nth-child(7) { + top: 6px; + left: 7px; + background: #5d9a5d; + animation: grow 2s linear 3s infinite; +} + +.spinnerFour > span:nth-child(8) { + top: 9px; + left: -1px; + background: #a7bbef; + animation: grow 2s linear 3.5s infinite; +} + +.spinnerFour > span:nth-child(9) { + top: 3px; + left: -2px; + background: #962b6f; + animation: grow 2s linear 4s infinite; +} + +.spinnerFour > span:nth-child(10) { + top: 2px; + left: -3px; + background: #f282be; + animation: grow 2s linear 4.5s infinite; +} + +.spinnerFour > span:nth-child(11) { + top: 10px; + left: 7px; + background: #5dc0f0; + animation: grow 2s linear 5s infinite; +} + +.spinnerFour > span:nth-child(12) { + top: 1px; + left: -7px; + background: #980217; + animation: grow 2s linear 5.5s infinite; +} + +@keyframes grow { + 0% { + opacity: 1; + transform: scale(0); + } + 15% { + opacity: 1; + } + 100% { + transform: scale(1.3); + opacity: 0; + } +} +.spinnerFive { + overflow: hidden; +} + +.spinnerFive > span:nth-child(1) { + position: absolute; + height: 25px; + width: 60px; + background: #7389AE; + bottom: 0; + left: 0; +} + +.spinnerFive > span:nth-child(2) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 8px; + left: 33px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 0.8s infinite; +} + +.spinnerFive > span:nth-child(3) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 8px; + left: 22px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 1.6s infinite; +} + +.spinnerFive > span:nth-child(4) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 10px; + left: 33px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 2.4s infinite; +} + +.spinnerFive > span:nth-child(5) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 1px; + left: 32px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 3.2s infinite; +} + +.spinnerFive > span:nth-child(6) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 3px; + left: 36px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 4s infinite; +} + +.spinnerFive > span:nth-child(7) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 9px; + left: 37px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 4.8s infinite; +} + +.spinnerFive > span:nth-child(8) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 8px; + left: 27px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 5.6s infinite; +} + +.spinnerFive > span:nth-child(9) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 3px; + left: 22px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 6.4s infinite; +} + +.spinnerFive > span:nth-child(10) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 2px; + left: 32px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 7.2s infinite; +} + +.spinnerFive > span:nth-child(11) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 6px; + left: 25px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 8s infinite; +} + +.spinnerFive > span:nth-child(12) { + position: absolute; + height: 10px; + width: 10px; + opacity: 0; + bottom: 6px; + left: 35px; + background: white; + border-radius: 50%; + border: 1px solid #7389AE; + animation: floaty 2s linear 8.8s infinite; +} + +@keyframes floaty { + 10% { + opacity: 1; + } + 25% { + transform: translate(30%, 0%); + } + 50% { + transform: translate(-70%, 0%); + } + 100% { + bottom: 60px; + } +} diff --git a/src/libkysdk-system-javascript-http/html/css/w3.css b/src/libkysdk-system-javascript-http/html/css/w3.css new file mode 100755 index 0000000..809379c --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/css/w3.css @@ -0,0 +1,384 @@ +/* W3.CSS 2.5 by Jan Egil and Borge Refsnes. Do not remove this line */ +*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} +/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ +html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} +article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block} +audio,canvas,video{display:inline-block;vertical-align:baseline} +audio:not([controls]){display:none;height:0} +[hidden],template{display:none} +a{background-color:transparent} +a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted} +b,strong,optgroup{font-weight:bold}dfn{font-style:italic} +mark{background:#ff0;color:#000}small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-0.5em}sub{bottom:-0.25em} +img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px} +hr{-moz-box-sizing:content-box;box-sizing:content-box} +code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em} +button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0} +button{overflow:visible}button,select{text-transform:none} +button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +input[type=checkbox],input[type=radio]{padding:0} +input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto} +input[type=search]{box-sizing:content-box;-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box} +input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none} +fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0}pre,textarea{overflow:auto} +/*End extract from normalize.css*/ +html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5} +html{overflow-x:hidden} +h1,h2,h3,h4,h5,h6,.w3-slim,.w3-wide{font-family:"Segoe UI",Arial,sans-serif} +h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px} +.w3-serif{font-family:"Times New Roman",Times,serif} +h1,h2,h3,h4,h5,h6{font-weight:400;margin:10px 0} +.w3-wide{letter-spacing:4px} +h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit} +hr{height:0;border:0;border-top:1px solid #eee;margin:20px 0} +img{margin-bottom:-5px} +a{color:inherit} +table{border-collapse:collapse;border-spacing:0;width:100%;display:table} +table,th,td{border:none} +.w3-table-all{border:1px solid #ccc} +.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd} +.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} +.w3-table-all tr:nth-child(odd){background-color:#fff} +.w3-table-all tr:nth-child(even){background-color:#f1f1f1} +.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc} +.w3-centered tr th,.w3-centered tr td{text-align:center} +.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:6px 8px;display:table-cell;text-align:left;vertical-align:top} +.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} +.w3-btn,.w3-btn-block{border:none;display:inline-block;outline:0;padding:6px 16px;vertical-align:middle;overflow:hidden;text-decoration:none!important;color:#fff;background-color:#000;text-align:center;cursor:pointer;white-space:nowrap} +.w3-btn.w3-disabled,.w3-btn-block.w3-disabled,.w3-btn-floating.w3-disabled,.w3-btn:disabled,.w3-btn-floating:disabled,.w3-btn-floating-large.w3-disabled,.w3-btn-floating-large:disabled{cursor:not-allowed;opacity:0.3} +.w3-btn.w3-disabled *,.w3-btn-block.w3-disabled,.w3-btn-floating.w3-disabled *,.w3-btn:disabled *,.w3-btn-floating:disabled *{pointer-events:none} +.w3-btn.w3-disabled:hover,.w3-btn-block.w3-disabled:hover,.w3-btn:disabled:hover,.w3-btn-floating.w3-disabled:hover,.w3-btn-floating:disabled:hover, +.w3-btn-floating-large.w3-disabled:hover,.w3-btn-floating-large:disabled:hover{box-shadow:none} +.w3-btn:hover,.w3-btn-block:hover,.w3-btn-floating:hover,.w3-btn-floating-large:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} +.w3-btn-block{width:100%} +.w3-btn,.w3-btn-floating,.w3-btn-floating-large,.w3-closenav,.w3-opennav{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +.w3-btn-floating,.w3-btn-floating-large{display:inline-block;text-align:center;color:#fff;background-color:#000;position:relative;overflow:hidden;z-index:1;padding:0;border-radius:50%;cursor:pointer;font-size:24px} +.w3-btn-floating{width:40px;height:40px;line-height:40px} +.w3-btn-floating-large{width:56px;height:56px;line-height:56px} +.w3-btn-group .w3-btn{float:left} +.w3-btn.w3-ripple{position:relative} +.w3-ripple:after{content:"";background:#90EE90;display:block;position:absolute;padding-top:300%;padding-left:350%;margin-left:-20px!important;margin-top:-120%;opacity:0;transition:.8s} +.w3-ripple:active:after{padding:0;margin:0;opacity:1;transition:0s} +.w3-badge,.w3-tag,.w3-sign{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;font-weight:bold;text-align:center} +.w3-badge{border-radius:50%} +ul.w3-ul{list-style-type:none;padding:0;margin:0} +ul.w3-ul li{padding:6px 2px 6px 16px;border-bottom:1px solid #ddd} +ul.w3-ul li:last-child{border-bottom:none} +.w3-image,.w3-tooltip,.w3-display-container{position:relative} +img.w3-image,.w3-image img{max-width:100%;height:auto} +.w3-image .w3-title{position:absolute;bottom:8px;left:16px;color:#fff;font-size:20px} +.w3-fluid{max-width:100%;height:auto} +.w3-tooltip .w3-text{display:none} +.w3-tooltip:hover .w3-text{display:inline-block} +.w3-navbar{list-style-type:none;margin:0;padding:0;overflow:hidden} +.w3-navbar li{float:left}.w3-navbar li a{display:block;padding:8px 16px}.w3-navbar li a:hover{color:#000;background-color:#ccc} +.w3-navbar .w3-dropdown-hover,.w3-navbar .w3-dropdown-click{position:static} +.w3-navbar .w3-dropdown-hover:hover,.w3-navbar .w3-dropdown-hover:first-child,.w3-navbar .w3-dropdown-click:hover{background-color:#ccc;color:#000} +.w3-navbar a,.w3-topnav a,.w3-sidenav a,.w3-dropnav a,.w3-dropdown-content a,.w3-accordion-content a{text-decoration:none!important} +.w3-navbar .w3-opennav.w3-right{float:right!important} +.w3-topnav{padding:8px 8px} +.w3-topnav a{padding:0 8px;border-bottom:3px solid transparent;-webkit-transition:border-bottom .3s;transition:border-bottom .3s} +.w3-topnav a:hover{border-bottom:3px solid #fff} +.w3-topnav .w3-dropdown-hover a{border-bottom:0} +.w3-opennav,.w3-closenav{color:inherit} +.w3-opennav:hover,.w3-closenav:hover{cursor:pointer;opacity:0.8} +.w3-btn,.w3-btn-floating,.w3-btn-floating-large,.w3-hover-shadow,.w3-hover-opacity, +.w3-navbar a,.w3-sidenav a,.w3-dropnav a,.w3-pagination li a,.w3-hoverable tbody tr,.w3-hoverable li,.w3-accordion-content a,.w3-dropdown-content a,.w3-dropdown-click:hover,.w3-dropdown-hover:hover,.w3-opennav,.w3-closenav,.w3-closebtn, +.w3-hover-amber,.w3-hover-aqua,.w3-hover-blue,.w3-hover-light-blue,.w3-hover-brown,.w3-hover-cyan,.w3-hover-blue-grey,.w3-hover-green,.w3-hover-light-green,.w3-hover-indigo,.w3-hover-khaki,.w3-hover-lime,.w3-hover-orange,.w3-hover-deep-orange,.w3-hover-pink, +.w3-hover-purple,.w3-hover-deep-purple,.w3-hover-red,.w3-hover-sand,.w3-hover-teal,.w3-hover-yellow,.w3-hover-white,.w3-hover-black,.w3-hover-grey,.w3-hover-light-grey,.w3-hover-dark-grey,.w3-hover-text-amber,.w3-hover-text-aqua,.w3-hover-text-blue,.w3-hover-text-light-blue, +.w3-hover-text-brown,.w3-hover-text-cyan,.w3-hover-text-blue-grey,.w3-hover-text-green,.w3-hover-text-light-green,.w3-hover-text-indigo,.w3-hover-text-khaki,.w3-hover-text-lime,.w3-hover-text-orange,.w3-hover-text-deep-orange,.w3-hover-text-pink,.w3-hover-text-purple, +.w3-hover-text-deep-purple,.w3-hover-text-red,.w3-hover-text-sand,.w3-hover-text-teal,.w3-hover-text-yellow,.w3-hover-text-white,.w3-hover-text-black,.w3-hover-text-grey,.w3-hover-text-light-grey,.w3-hover-text-dark-grey +{-webkit-transition:background-color .3s,color .15s,box-shadow .3s,opacity 0.3s;transition:background-color .3s,color .15s,box-shadow .3s,opacity 0.3s} +.w3-sidenav{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} +.w3-sidenav a{padding:4px 2px 4px 16px} +.w3-sidenav a:hover{background-color:#ccc} +.w3-sidenav a,.w3-dropnav a{display:block} +.w3-sidenav .w3-dropdown-hover:hover,.w3-sidenav .w3-dropdown-hover:first-child,.w3-sidenav .w3-dropdown-click:hover{background-color:#ccc;color:#000} +.w3-sidenav .w3-dropdown-hover,.w3-sidenav .w3-dropdown-click {width:100%}.w3-sidenav .w3-dropdown-hover .w3-dropdown-content,.w3-sidenav .w3-dropdown-click .w3-dropdown-content{min-width:100%} +.w3-main,#main{transition:margin-left .4s} +.w3-dropnav{background-color:#fff} +.w3-dropnav a:hover{text-decoration:underline!important} +.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} +.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}.w3-closebtn{text-decoration:none;float:right;font-size:24px;font-weight:bold;color:inherit} +.w3-closebtn:hover,.w3-closebtn:focus{color:#000;text-decoration:none;cursor:pointer} +.w3-pagination{display:inline-block;padding:0;margin:0} +.w3-pagination li{display:inline} +.w3-pagination li a{text-decoration:none;color:#000;float:left;padding:8px 16px} +.w3-pagination li a:hover,.w3-pagination li a:focus{background-color:#ccc} +.w3-input-group,.w3-group{margin-top:24px;margin-bottom:24px} +.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #808080;width:100%} +.w3-label{color:#009688} +.w3-input:not(:valid)~.w3-validate{color:#f44336} +.w3-select{padding:4px 0;width:100%;color:#000;border:1px solid transparent;border-bottom:1px solid #009688} +.w3-select select:focus{color:#000;border:1px solid #009688}.w3-select option[disabled]{color:#009688} +.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} +.w3-dropdown-hover:hover .w3-dropdown-content{display:block;z-index:1} +.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0} +.w3-dropdown-content a{padding:6px 16px;display:block} +.w3-dropdown-content a:hover{background-color:#ccc} +.w3-accordion {width:100%;cursor:pointer} +.w3-accordion-content{cursor:auto;display:none;position:relative;width:100%;margin:0;padding:0} +.w3-accordion-content a{padding:6px 16px;display:block} +.w3-accordion-content a:hover{background-color:#ccc} +.w3-progress-container{width:100%;height:1.5em;position:relative;background-color:#f1f1f1} +.w3-progressbar{background-color:#757575;height:100%;position:absolute;line-height:inherit} +input[type=checkbox].w3-check,input[type=radio].w3-radio{width:24px;height:24px;position:relative;top:6px} +input[type=checkbox].w3-check:checked+.w3-validate,input[type=radio].w3-radio:checked+.w3-validate{color:#009688} +input[type=checkbox].w3-check:disabled+.w3-validate,input[type=radio].w3-radio:disabled+.w3-validate{color:#aaa} +.w3-responsive{overflow-x:auto} +.w3-container-4:after,.w3-container-8:after,.w3-container-12:after,.w3-container:after,.w3-container-16:after, +.w3-container-24:after,.w3-container-32:after,.w3-container-48:after,.w3-container-64:after, +.w3-row:after,.w3-row-padding:after,.w3-topnav:after,.w3-clear:after,.w3-btn-group:before,.w3-btn-group:after{content:"";display:table;clear:both} +.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} +.w3-col.s1{width:8.33333%} +.w3-col.s2{width:16.66666%} +.w3-col.s3{width:24.99999%} +.w3-col.s4{width:33.33333%} +.w3-col.s5{width:41.66666%} +.w3-col.s6{width:49.99999%} +.w3-col.s7{width:58.33333%} +.w3-col.s8{width:66.66666%} +.w3-col.s9{width:74.99999%} +.w3-col.s10{width:83.33333%} +.w3-col.s11{width:91.66666%} +.w3-col.s12,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{width:99.99999%} +@media only screen and (min-width:601px){ +.w3-col.m1{width:8.33333%} +.w3-col.m2{width:16.66666%} +.w3-col.m3,.w3-quarter{width:24.99999%} +.w3-col.m4,.w3-third{width:33.33333%} +.w3-col.m5{width:41.66666%} +.w3-col.m6,.w3-half{width:49.99999%} +.w3-col.m7{width:58.33333%} +.w3-col.m8,.w3-twothird{width:66.66666%} +.w3-col.m9,.w3-threequarter{width:74.99999%} +.w3-col.m10{width:83.33333%} +.w3-col.m11{width:91.66666%} +.w3-col.m12{width:99.99999%}} +@media only screen and (min-width:993px){ +.w3-col.l1{width:8.33333%} +.w3-col.l2{width:16.66666%} +.w3-col.l3,.w3-quarter{width:24.99999%} +.w3-col.l4,.w3-third{width:33.33333%} +.w3-col.l5{width:41.66666%} +.w3-col.l6,.w3-half{width:49.99999%} +.w3-col.l7{width:58.33333%} +.w3-col.l8,.w3-twothird{width:66.66666%} +.w3-col.l9,.w3-threequarter{width:74.99999%} +.w3-col.l10{width:83.33333%} +.w3-col.l11{width:91.66666%} +.w3-col.l12{width:99.99999%}} +.w3-content{max-width:980px;margin:auto} +.w3-rest{overflow:hidden} +.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} +@media (max-width:600px){.w3-modal-content{margin:50px 10px 10px 10px;width:auto!important}} +@media (max-width:768px){.w3-modal-content{width:500px}} +@media (min-width:993px){.w3-modal-content{width:900px}} +@media screen and (max-width:600px){.w3-topnav a{display:block}.w3-navbar li:not(.w3-opennav){float:none;width:100%!important}.w3-navbar li.w3-right{float:none!important}} +@media screen and (max-width:600px){.w3-topnav .w3-dropdown-hover .w3-dropdown-content,.w3-navbar .w3-dropdown-click .w3-dropdown-content,.w3-navbar .w3-dropdown-hover .w3-dropdown-content{position:relative}} +@media screen and (max-width:600px){.w3-topnav,.w3-navbar{text-align:center}} +@media (max-width:600px){.w3-hide-small{display:none!important}} +@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} +@media (min-width:993px){.w3-hide-large{display:none!important}} +@media screen and (max-width:992px){.w3-sidenav.w3-collapse{display:none}.w3-main{margin-left:0!important}} +@media screen and (min-width:992px){.w3-sidenav.w3-collapse{display:block!important}} +.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} +.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} +.w3-left{float:left!important}.w3-right{float:right!important} +.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important} +.w3-medium{font-size:15px!important} +.w3-large{font-size:18px!important} +.w3-xlarge{font-size:24px!important} +.w3-xxlarge{font-size:36px!important} +.w3-xxxlarge{font-size:48px!important} +.w3-jumbo{font-size:64px!important} +.w3-vertical{word-break:break-all;line-height:1;text-align:center;width:0.6em} +.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important} +.w3-justify{text-align:justify!important} +.w3-center{text-align:center!important} +.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} +.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} +.w3-display-middle{position:absolute;left:0;bottom:50%;width:100%;text-align:center} +.w3-display-topmiddle{position:absolute;left:0;top:0;width:100%;text-align:center}.w3-display-bottommiddle{position:absolute;left:0;bottom:0;width:100%;text-align:center} +.w3-circle{border-radius:50%!important} +.w3-round-small{border-radius:2px!important}.w3-round,.w3-round-medium{border-radius:4px!important} +.w3-round-large{border-radius:8px!important}.w3-round-xlarge{border-radius:16px!important} +.w3-round-xxlarge{border-radius:32px!important}.w3-round-jumbo{border-radius:64px!important} +.w3-border-0{border:0!important} +.w3-border{border:1px solid #ccc!important} +.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} +.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} +.w3-section-4{margin-top:4px;margin-bottom:4px} +.w3-section-8{margin-top:8px;margin-bottom:8px} +.w3-section-12{margin-top:12px;margin-bottom:12px} +.w3-section,.w3-section-16,.w3.paragraph{margin-top:16px;margin-bottom:16px} +.w3-section-24{margin-top:24px;margin-bottom:24px} +.w3-section-32{margin-top:32px;margin-bottom:32px} +.w3-section-48{margin-top:48px;margin-bottom:48px} +.w3-section-64{margin-top:64px;margin-bottom:64px} +.w3-section-128{margin-top:128px;margin-bottom:128px} +.w3-margin-0{margin:0!important} +.w3-margin-4{margin:4px!important} +.w3-margin-8{margin:8px!important} +.w3-margin-12{margin:12px!important} +.w3-margin-16,.w3-margin{margin:16px!important} +.w3-margin-24{margin:24px!important} +.w3-margin-32{margin:32px!important} +.w3-margin-48{margin:48px!important} +.w3-margin-64{margin:64px!important} +.w3-padding-tiny{padding:2px 4px!important} +.w3-padding-small{padding:4px 8px!important} +.w3-padding-medium,.w3-padding,.w3-form{padding:8px 16px!important} +.w3-padding-large{padding:12px 24px!important} +.w3-padding-xlarge{padding:16px 32px!important} +.w3-padding-xxlarge{padding:24px 48px!important} +.w3-padding-jumbo{padding:32px 64px!important} +.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} +.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} +.w3-padding-0{padding:0!important} +.w3-padding-4{padding-top:4px!important;padding-bottom:4px!important} +.w3-padding-8{padding-top:8px!important;padding-bottom:8px!important} +.w3-padding-12{padding-top:12px!important;padding-bottom:12px!important} +.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important} +.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} +.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important} +.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} +.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} +.w3-padding-128{padding-top:128px!important;padding-bottom:128px!important} +.w3-padding-top{padding-top:8px!important}.w3-padding-bottom{padding-bottom:8px!important} +.w3-padding-left{padding-left:16px!important}.w3-padding-right{padding-right:16px!important} +.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} +.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} +.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} +.w3-spin{animation:w3-spin 2s infinite linear;-webkit-animation:w3-spin 2s infinite linear} +@-webkit-keyframes w3-spin{ +0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)} +100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}} +@keyframes w3-spin{ +0%{-webkit-transform:rotate(0deg);transform: rotate(0deg)} +100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}} +.w3-container-4{padding:0.01em 4px}.w3-container-8{padding:0.01em 8px}.w3-container-12{padding:0.01em 12px}.w3-container,.w3-container-16{padding:0.01em 16px} +.w3-container-24{padding:0.01em 24px}.w3-container-32{padding:0.01em 32px}.w3-container-48{padding:0.01em 48px}.w3-container-64{padding:0.01em 64px} +.w3-example{background-color:#f1f1f1;padding:0.01em 16px} +.w3-code{font-family:Consolas,"courier new";font-size:16px;line-height:1.4;width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #009688;word-wrap:break-word} +.w3-example,.w3-code,.w3-reference{margin:20px 0} +.w3-card{border:1px solid #ccc} +.w3-card-2,.w3-example{box-shadow:0 2px 4px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)!important} +.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)!important} +.w3-card-8{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)!important} +.w3-card-12{box-shadow:0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19)!important} +.w3-card-16{box-shadow:0 16px 24px 0 rgba(0,0,0,0.22),0 25px 55px 0 rgba(0,0,0,0.21)!important} +.w3-card-24{box-shadow:0 24px 24px 0 rgba(0,0,0,0.2),0 40px 77px 0 rgba(0,0,0,0.22)!important} +.w3-animate-fading{-webkit-animation:fading 10s infinite;animation:fading 10s infinite} +@-webkit-keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +.w3-animate-opacity{-webkit-animation:opac 1.5s;animation:opac 1.5s} +@-webkit-keyframes opac{from{opacity:0} to{opacity:1}} +@keyframes opac{from{opacity:0} to{opacity:1}} +.w3-animate-top{position:relative;-webkit-animation:animatetop 0.4s;animation:animatetop 0.4s} +@-webkit-keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} +@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} +.w3-animate-left{position:relative;-webkit-animation:animateleft 0.4s;animation:animateleft 0.4s} +@-webkit-keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} +@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} +.w3-animate-right{position:relative;-webkit-animation:animateright 0.4s;animation:animateright 0.4s} +@-webkit-keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} +@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} +.w3-animate-bottom{position:relative;-webkit-animation:animatebottom 0.4s;animation:animatebottom 0.4s} +@-webkit-keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0px;opacity:1}} +@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} +.w3-animate-zoom {-webkit-animation:animatezoom 0.6s;animation:animatezoom 0.6s} +@-webkit-keyframes animatezoom{from{-webkit-transform:scale(0)} to{-webkit-transform:scale(1)}} +@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} +.w3-animate-input{-webkit-transition:width 0.4s ease-in-out;transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} +.w3-transparent{background-color:transparent!important} +.w3-hover-none:hover{box-shadow:none!important;background-color:transparent!important} +.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} +.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} +.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} +.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} +.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} +.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} +.w3-blue-grey,.w3-hover-blue-grey:hover{color:#fff!important;background-color:#607d8b!important} +.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} +.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} +.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} +.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} +.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} +.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} +.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} +.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} +.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} +.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} +.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} +.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} +.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} +.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} +.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} +.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} +.w3-grey,.w3-hover-grey:hover{color:#000!important;background-color:#9e9e9e!important} +.w3-light-grey,.w3-hover-light-grey:hover{color:#000!important;background-color:#f1f1f1!important} +.w3-dark-grey,.w3-hover-dark-grey:hover{color:#fff!important;background-color:#616161!important} +.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffe7e7!important}.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#e7ffe7!important} +.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffd7!important}.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#e7ffff!important} +.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} +.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} +.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} +.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} +.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} +.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} +.w3-text-blue-grey,.w3-hover-text-blue-grey:hover{color:#607d8b!important} +.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} +.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} +.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} +.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} +.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} +.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} +.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} +.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} +.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} +.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} +.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} +.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} +.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} +.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} +.w3-text-white,.w3-hover-text-white:hover{color:#fff!important} +.w3-text-black,.w3-hover-text-black:hover{color:#000!important} +.w3-text-grey,.w3-hover-text-grey:hover{color:#757575!important} +.w3-text-light-grey,.w3-hover-text-light-grey:hover{color:#f1f1f1!important} +.w3-text-dark-grey,.w3-hover-text-dark-grey:hover{color:#3a3a3a!important} +.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} +.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} +.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} +.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} +.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} +.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} +.w3-border-blue-grey,.w3-hover-blue-grey:hover{border-color:#607d8b!important} +.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} +.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} +.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} +.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} +.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} +.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} +.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} +.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} +.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} +.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} +.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} +.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} +.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} +.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} +.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} +.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} +.w3-border-grey,.w3-hover-border-grey:hover{border-color:#9e9e9e!important} +.w3-border-light-grey,.w3-hover-border-light-grey:hover{border-color:#f1f1f1!important} +.w3-border-dark-grey,.w3-hover-border-dark-grey:hover{border-color:#616161!important} +.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} +.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffd7!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} +.w3-opacity,.w3-hover-opacity:hover{opacity:0.60} +.w3-text-shadow{text-shadow:1px 1px 0 #444}.w3-text-shadow-white{text-shadow:1px 1px 0 #ddd} \ No newline at end of file diff --git a/src/libkysdk-system-javascript-http/html/image/checkok.png b/src/libkysdk-system-javascript-http/html/image/checkok.png new file mode 100755 index 0000000..a9aedf9 Binary files /dev/null and b/src/libkysdk-system-javascript-http/html/image/checkok.png differ diff --git a/src/libkysdk-system-javascript-http/html/index.html b/src/libkysdk-system-javascript-http/html/index.html new file mode 100755 index 0000000..1bbc5c1 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/index.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + +
+ +
+ + diff --git a/src/libkysdk-system-javascript-http/html/index_page.html b/src/libkysdk-system-javascript-http/html/index_page.html new file mode 100644 index 0000000..25ec500 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/index_page.html @@ -0,0 +1,33 @@ + + + + + + SelfCheck + + + + + + + + + + + +
+ + +
+

Loading...

+ +
+ +
+ + + + + diff --git a/src/libkysdk-system-javascript-http/html/js/RecordRTC.js b/src/libkysdk-system-javascript-http/html/js/RecordRTC.js new file mode 100755 index 0000000..1bccef5 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/js/RecordRTC.js @@ -0,0 +1,5856 @@ +'use strict'; + +// Last time updated: 2019-02-06 11:32:40 AM UTC + +// ________________ +// RecordRTC v5.5.4 + +// Open-Sourced: https://github.com/muaz-khan/RecordRTC + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +// ____________ +// RecordRTC.js + +/** + * {@link https://github.com/muaz-khan/RecordRTC|RecordRTC} is a WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows. + * @summary Record audio, video or screen inside the browser. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef RecordRTC + * @class + * @example + * var recorder = RecordRTC(mediaStream or [arrayOfMediaStream], { + * type: 'video', // audio or video or gif or canvas + * recorderType: MediaStreamRecorder || CanvasRecorder || StereoAudioRecorder || Etc + * }); + * recorder.startRecording(); + * @see For further information: + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. + * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, desiredSampRate: 16000, video: HTMLVideoElement, etc.} + */ + +function RecordRTC(mediaStream, config) { + if (!mediaStream) { + throw 'First parameter is required.'; + } + + config = config || { + type: 'video' + }; + + config = new RecordRTCConfiguration(mediaStream, config); + + // a reference to user's recordRTC object + var self = this; + + function startRecording(config2) { + if (!!config2) { + // allow users to set options using startRecording method + // config2 is similar to main "config" object (second parameter over RecordRTC constructor) + config = new RecordRTCConfiguration(mediaStream, config2); + } + + if (!config.disableLogs) { + console.log('started recording ' + config.type + ' stream.'); + } + + if (mediaRecorder) { + mediaRecorder.clearRecordedData(); + mediaRecorder.record(); + + setState('recording'); + + if (self.recordingDuration) { + handleRecordingDuration(); + } + return self; + } + + initRecorder(function() { + if (self.recordingDuration) { + handleRecordingDuration(); + } + }); + + return self; + } + + function initRecorder(initCallback) { + if (initCallback) { + config.initCallback = function() { + initCallback(); + initCallback = config.initCallback = null; // recorder.initRecorder should be call-backed once. + }; + } + + var Recorder = new GetRecorderType(mediaStream, config); + + mediaRecorder = new Recorder(mediaStream, config); + mediaRecorder.record(); + + setState('recording'); + + if (!config.disableLogs) { + console.log('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type); + } + } + + function stopRecording(callback) { + callback = callback || function() {}; + + if (!mediaRecorder) { + warningLog(); + return; + } + + if (self.state === 'paused') { + self.resumeRecording(); + + setTimeout(function() { + stopRecording(callback); + }, 1); + return; + } + + if (self.state !== 'recording' && !config.disableLogs) { + console.warn('Recording state should be: "recording", however current state is: ', self.state); + } + + if (!config.disableLogs) { + console.log('Stopped recording ' + config.type + ' stream.'); + } + + if (config.type !== 'gif') { + mediaRecorder.stop(_callback); + } else { + mediaRecorder.stop(); + _callback(); + } + + setState('stopped'); + + function _callback(__blob) { + if (!mediaRecorder) { + if (typeof callback.call === 'function') { + callback.call(self, ''); + } else { + callback(''); + } + return; + } + + Object.keys(mediaRecorder).forEach(function(key) { + if (typeof mediaRecorder[key] === 'function') { + return; + } + + self[key] = mediaRecorder[key]; + }); + + var blob = mediaRecorder.blob; + + if (!blob) { + if (__blob) { + mediaRecorder.blob = blob = __blob; + } else { + throw 'Recording failed.'; + } + } + + if (blob && !config.disableLogs) { + console.log(blob.type, '->', bytesToSize(blob.size)); + } + + if (callback) { + var url; + + try { + url = URL.createObjectURL(blob); + } catch (e) {} + + if (typeof callback.call === 'function') { + callback.call(self, url); + } else { + callback(url); + } + } + + if (!config.autoWriteToDisk) { + return; + } + + getDataURL(function(dataURL) { + var parameter = {}; + parameter[config.type + 'Blob'] = dataURL; + DiskStorage.Store(parameter); + }); + } + } + + function pauseRecording() { + if (!mediaRecorder) { + warningLog(); + return; + } + + if (self.state !== 'recording') { + if (!config.disableLogs) { + console.warn('Unable to pause the recording. Recording state: ', self.state); + } + return; + } + + setState('paused'); + + mediaRecorder.pause(); + + if (!config.disableLogs) { + console.log('Paused recording.'); + } + } + + function resumeRecording() { + if (!mediaRecorder) { + warningLog(); + return; + } + + if (self.state !== 'paused') { + if (!config.disableLogs) { + console.warn('Unable to resume the recording. Recording state: ', self.state); + } + return; + } + + setState('recording'); + + // not all libs have this method yet + mediaRecorder.resume(); + + if (!config.disableLogs) { + console.log('Resumed recording.'); + } + } + + function readFile(_blob) { + postMessage(new FileReaderSync().readAsDataURL(_blob)); + } + + function getDataURL(callback, _mediaRecorder) { + if (!callback) { + throw 'Pass a callback function over getDataURL.'; + } + + var blob = _mediaRecorder ? _mediaRecorder.blob : (mediaRecorder || {}).blob; + + if (!blob) { + if (!config.disableLogs) { + console.warn('Blob encoder did not finish its job yet.'); + } + + setTimeout(function() { + getDataURL(callback, _mediaRecorder); + }, 1000); + return; + } + + if (typeof Worker !== 'undefined' && !navigator.mozGetUserMedia) { + var webWorker = processInWebWorker(readFile); + + webWorker.onmessage = function(event) { + callback(event.data); + }; + + webWorker.postMessage(blob); + } else { + var reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onload = function(event) { + callback(event.target.result); + }; + } + + function processInWebWorker(_function) { + try { + var blob = URL.createObjectURL(new Blob([_function.toString(), + 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(blob); + URL.revokeObjectURL(blob); + return worker; + } catch (e) {} + } + } + + function handleRecordingDuration(counter) { + counter = counter || 0; + + if (self.state === 'paused') { + setTimeout(function() { + handleRecordingDuration(counter); + }, 1000); + return; + } + + if (self.state === 'stopped') { + return; + } + + if (counter >= self.recordingDuration) { + stopRecording(self.onRecordingStopped); + return; + } + + counter += 1000; // 1-second + + setTimeout(function() { + handleRecordingDuration(counter); + }, 1000); + } + + function setState(state) { + if (!self) { + return; + } + + self.state = state; + + if (typeof self.onStateChanged.call === 'function') { + self.onStateChanged.call(self, state); + } else { + self.onStateChanged(state); + } + } + + var WARNING = 'It seems that recorder is destroyed or "startRecording" is not invoked for ' + config.type + ' recorder.'; + + function warningLog() { + if (config.disableLogs === true) { + return; + } + + console.warn(WARNING); + } + + var mediaRecorder; + + var returnObject = { + /** + * This method starts the recording. + * @method + * @memberof RecordRTC + * @instance + * @example + * var recorder = RecordRTC(mediaStream, { + * type: 'video' + * }); + * recorder.startRecording(); + */ + startRecording: startRecording, + + /** + * This method stops the recording. It is strongly recommended to get "blob" or "URI" inside the callback to make sure all recorders finished their job. + * @param {function} callback - Callback to get the recorded blob. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * // use either "this" or "recorder" object; both are identical + * video.src = this.toURL(); + * var blob = this.getBlob(); + * }); + */ + stopRecording: stopRecording, + + /** + * This method pauses the recording. You can resume recording using "resumeRecording" method. + * @method + * @memberof RecordRTC + * @instance + * @todo Firefox is unable to pause the recording. Fix it. + * @example + * recorder.pauseRecording(); // pause the recording + * recorder.resumeRecording(); // resume again + */ + pauseRecording: pauseRecording, + + /** + * This method resumes the recording. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.pauseRecording(); // first of all, pause the recording + * recorder.resumeRecording(); // now resume it + */ + resumeRecording: resumeRecording, + + /** + * This method initializes the recording. + * @method + * @memberof RecordRTC + * @instance + * @todo This method should be deprecated. + * @example + * recorder.initRecorder(); + */ + initRecorder: initRecorder, + + /** + * Ask RecordRTC to auto-stop the recording after 5 minutes. + * @method + * @memberof RecordRTC + * @instance + * @example + * var fiveMinutes = 5 * 1000 * 60; + * recorder.setRecordingDuration(fiveMinutes, function() { + * var blob = this.getBlob(); + * video.src = this.toURL(); + * }); + * + * // or otherwise + * recorder.setRecordingDuration(fiveMinutes).onRecordingStopped(function() { + * var blob = this.getBlob(); + * video.src = this.toURL(); + * }); + */ + setRecordingDuration: function(recordingDuration, callback) { + if (typeof recordingDuration === 'undefined') { + throw 'recordingDuration is required.'; + } + + if (typeof recordingDuration !== 'number') { + throw 'recordingDuration must be a number.'; + } + + self.recordingDuration = recordingDuration; + self.onRecordingStopped = callback || function() {}; + + return { + onRecordingStopped: function(callback) { + self.onRecordingStopped = callback; + } + }; + }, + + /** + * This method can be used to clear/reset all the recorded data. + * @method + * @memberof RecordRTC + * @instance + * @todo Figure out the difference between "reset" and "clearRecordedData" methods. + * @example + * recorder.clearRecordedData(); + */ + clearRecordedData: function() { + if (!mediaRecorder) { + warningLog(); + return; + } + + mediaRecorder.clearRecordedData(); + + if (!config.disableLogs) { + console.log('Cleared old recorded data.'); + } + }, + + /** + * Get the recorded blob. Use this method inside the "stopRecording" callback. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * var blob = this.getBlob(); + * + * var file = new File([blob], 'filename.webm', { + * type: 'video/webm' + * }); + * + * var formData = new FormData(); + * formData.append('file', file); // upload "File" object rather than a "Blob" + * uploadToServer(formData); + * }); + * @returns {Blob} Returns recorded data as "Blob" object. + */ + getBlob: function() { + if (!mediaRecorder) { + warningLog(); + return; + } + + return mediaRecorder.blob; + }, + + /** + * Get data-URI instead of Blob. + * @param {function} callback - Callback to get the Data-URI. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * recorder.getDataURL(function(dataURI) { + * video.src = dataURI; + * }); + * }); + */ + getDataURL: getDataURL, + + /** + * Get virtual/temporary URL. Usage of this URL is limited to current tab. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * video.src = this.toURL(); + * }); + * @returns {String} Returns a virtual/temporary URL for the recorded "Blob". + */ + toURL: function() { + if (!mediaRecorder) { + warningLog(); + return; + } + + return URL.createObjectURL(mediaRecorder.blob); + }, + + /** + * Get internal recording object (i.e. internal module) e.g. MutliStreamRecorder, MediaStreamRecorder, StereoAudioRecorder or WhammyRecorder etc. + * @method + * @memberof RecordRTC + * @instance + * @example + * var internal = recorder.getInternalRecorder(); + * if(internal instanceof MultiStreamRecorder) { + * internal.addStreams([newAudioStream]); + * internal.resetVideoStreams([screenStream]); + * } + * @returns {Object} Returns internal recording object. + */ + getInternalRecorder: function() { + return mediaRecorder; + }, + + /** + * Invoke save-as dialog to save the recorded blob into your disk. + * @param {string} fileName - Set your own file name. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * this.save('file-name'); + * + * // or manually: + * invokeSaveAsDialog(this.getBlob(), 'filename.webm'); + * }); + */ + save: function(fileName) { + if (!mediaRecorder) { + warningLog(); + return; + } + + invokeSaveAsDialog(mediaRecorder.blob, fileName); + }, + + /** + * This method gets a blob from indexed-DB storage. + * @param {function} callback - Callback to get the recorded blob. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.getFromDisk(function(dataURL) { + * video.src = dataURL; + * }); + */ + getFromDisk: function(callback) { + if (!mediaRecorder) { + warningLog(); + return; + } + + RecordRTC.getFromDisk(config.type, callback); + }, + + /** + * This method appends an array of webp images to the recorded video-blob. It takes an "array" object. + * @type {Array.} + * @param {Array} arrayOfWebPImages - Array of webp images. + * @method + * @memberof RecordRTC + * @instance + * @todo This method should be deprecated. + * @example + * var arrayOfWebPImages = []; + * arrayOfWebPImages.push({ + * duration: index, + * image: 'data:image/webp;base64,...' + * }); + * recorder.setAdvertisementArray(arrayOfWebPImages); + */ + setAdvertisementArray: function(arrayOfWebPImages) { + config.advertisement = []; + + var length = arrayOfWebPImages.length; + for (var i = 0; i < length; i++) { + config.advertisement.push({ + duration: i, + image: arrayOfWebPImages[i] + }); + } + }, + + /** + * It is equivalent to "recorder.getBlob()" method. Usage of "getBlob" is recommended, though. + * @property {Blob} blob - Recorded Blob can be accessed using this property. + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * var blob = this.blob; + * + * // below one is recommended + * var blob = this.getBlob(); + * }); + */ + blob: null, + + /** + * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. + * @property {number} bufferSize - Buffer-size used to encode the WAV container + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * alert('Recorder used this buffer-size: ' + this.bufferSize); + * }); + */ + bufferSize: 0, + + /** + * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. + * @property {number} sampleRate - Sample-rates used to encode the WAV container + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * alert('Recorder used these sample-rates: ' + this.sampleRate); + * }); + */ + sampleRate: 0, + + /** + * {recorderType:StereoAudioRecorder} returns ArrayBuffer object. + * @property {ArrayBuffer} buffer - Audio ArrayBuffer, supported only in Chrome. + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * var arrayBuffer = this.buffer; + * alert(arrayBuffer.byteLength); + * }); + */ + buffer: null, + + /** + * This method resets the recorder. So that you can reuse single recorder instance many times. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.reset(); + * recorder.startRecording(); + */ + reset: function() { + if (mediaRecorder && typeof mediaRecorder.clearRecordedData === 'function') { + mediaRecorder.clearRecordedData(); + } + mediaRecorder = null; + setState('inactive'); + self.blob = null; + }, + + /** + * This method is called whenever recorder's state changes. Use this as an "event". + * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.onStateChanged = function(state) { + * console.log('Recorder state: ', state); + * }; + */ + onStateChanged: function(state) { + if (!config.disableLogs) { + console.log('Recorder state changed:', state); + } + }, + + /** + * A recorder can have inactive, recording, paused or stopped states. + * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. + * @memberof RecordRTC + * @static + * @readonly + * @example + * // this looper function will keep you updated about the recorder's states. + * (function looper() { + * document.querySelector('h1').innerHTML = 'Recorder\'s state is: ' + recorder.state; + * if(recorder.state === 'stopped') return; // ignore+stop + * setTimeout(looper, 1000); // update after every 3-seconds + * })(); + * recorder.startRecording(); + */ + state: 'inactive', + + /** + * Get recorder's readonly state. + * @method + * @memberof RecordRTC + * @example + * var state = recorder.getState(); + * @returns {String} Returns recording state. + */ + getState: function() { + return self.state; + }, + + /** + * Destroy RecordRTC instance. Clear all recorders and objects. + * @method + * @memberof RecordRTC + * @example + * recorder.destroy(); + */ + destroy: function() { + var disableLogsCache = config.disableLogs; + + config = { + disableLogs: true + }; + self.reset(); + setState('destroyed'); + returnObject = self = null; + + if (Storage.AudioContextConstructor) { + Storage.AudioContextConstructor.close(); + Storage.AudioContextConstructor = null; + } + + config.disableLogs = disableLogsCache; + + if (!config.disableLogs) { + console.warn('RecordRTC is destroyed.'); + } + }, + + /** + * RecordRTC version number + * @property {String} version - Release version number. + * @memberof RecordRTC + * @static + * @readonly + * @example + * alert(recorder.version); + */ + version: '5.5.4' + }; + + if (!this) { + self = returnObject; + return returnObject; + } + + // if someone wants to use RecordRTC with the "new" keyword. + for (var prop in returnObject) { + this[prop] = returnObject[prop]; + } + + self = this; + + return returnObject; +} + +RecordRTC.version = '5.5.4'; + +if (typeof module !== 'undefined' /* && !!module.exports*/ ) { + module.exports = RecordRTC; +} + +if (typeof define === 'function' && define.amd) { + define('RecordRTC', [], function() { + return RecordRTC; + }); +} + +RecordRTC.getFromDisk = function(type, callback) { + if (!callback) { + throw 'callback is mandatory.'; + } + + console.log('Getting recorded ' + (type === 'all' ? 'blobs' : type + ' blob ') + ' from disk!'); + DiskStorage.Fetch(function(dataURL, _type) { + if (type !== 'all' && _type === type + 'Blob' && callback) { + callback(dataURL); + } + + if (type === 'all' && callback) { + callback(dataURL, _type.replace('Blob', '')); + } + }); +}; + +/** + * This method can be used to store recorded blobs into IndexedDB storage. + * @param {object} options - {audio: Blob, video: Blob, gif: Blob} + * @method + * @memberof RecordRTC + * @example + * RecordRTC.writeToDisk({ + * audio: audioBlob, + * video: videoBlob, + * gif : gifBlob + * }); + */ +RecordRTC.writeToDisk = function(options) { + console.log('Writing recorded blob(s) to disk!'); + options = options || {}; + if (options.audio && options.video && options.gif) { + options.audio.getDataURL(function(audioDataURL) { + options.video.getDataURL(function(videoDataURL) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL, + videoBlob: videoDataURL, + gifBlob: gifDataURL + }); + }); + }); + }); + } else if (options.audio && options.video) { + options.audio.getDataURL(function(audioDataURL) { + options.video.getDataURL(function(videoDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL, + videoBlob: videoDataURL + }); + }); + }); + } else if (options.audio && options.gif) { + options.audio.getDataURL(function(audioDataURL) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL, + gifBlob: gifDataURL + }); + }); + }); + } else if (options.video && options.gif) { + options.video.getDataURL(function(videoDataURL) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + videoBlob: videoDataURL, + gifBlob: gifDataURL + }); + }); + }); + } else if (options.audio) { + options.audio.getDataURL(function(audioDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL + }); + }); + } else if (options.video) { + options.video.getDataURL(function(videoDataURL) { + DiskStorage.Store({ + videoBlob: videoDataURL + }); + }); + } else if (options.gif) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + gifBlob: gifDataURL + }); + }); + } +}; + +// __________________________ +// RecordRTC-Configuration.js + +/** + * {@link RecordRTCConfiguration} is an inner/private helper for {@link RecordRTC}. + * @summary It configures the 2nd parameter passed over {@link RecordRTC} and returns a valid "config" object. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef RecordRTCConfiguration + * @class + * @example + * var options = RecordRTCConfiguration(mediaStream, options); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, getNativeBlob:true, etc.} + */ + +function RecordRTCConfiguration(mediaStream, config) { + if (!config.recorderType && !config.type) { + if (!!config.audio && !!config.video) { + config.type = 'video'; + } else if (!!config.audio && !config.video) { + config.type = 'audio'; + } + } + + if (config.recorderType && !config.type) { + if (config.recorderType === WhammyRecorder || config.recorderType === CanvasRecorder || (typeof WebAssemblyRecorder !== 'undefined' && config.recorderType === WebAssemblyRecorder)) { + config.type = 'video'; + } else if (config.recorderType === GifRecorder) { + config.type = 'gif'; + } else if (config.recorderType === StereoAudioRecorder) { + config.type = 'audio'; + } else if (config.recorderType === MediaStreamRecorder) { + if (getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { + config.type = 'video'; + } else if (!getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { + config.type = 'video'; + } else if (getTracks(mediaStream, 'audio').length && !getTracks(mediaStream, 'video').length) { + config.type = 'audio'; + } else { + // config.type = 'UnKnown'; + } + } + } + + if (typeof MediaStreamRecorder !== 'undefined' && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { + if (!config.mimeType) { + config.mimeType = 'video/webm'; + } + + if (!config.type) { + config.type = config.mimeType.split('/')[0]; + } + + if (!config.bitsPerSecond) { + // config.bitsPerSecond = 128000; + } + } + + // consider default type=audio + if (!config.type) { + if (config.mimeType) { + config.type = config.mimeType.split('/')[0]; + } + if (!config.type) { + config.type = 'audio'; + } + } + + return config; +} + +// __________________ +// GetRecorderType.js + +/** + * {@link GetRecorderType} is an inner/private helper for {@link RecordRTC}. + * @summary It returns best recorder-type available for your browser. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef GetRecorderType + * @class + * @example + * var RecorderType = GetRecorderType(options); + * var recorder = new RecorderType(options); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} + */ + +function GetRecorderType(mediaStream, config) { + var recorder; + + // StereoAudioRecorder can work with all three: Edge, Firefox and Chrome + // todo: detect if it is Edge, then auto use: StereoAudioRecorder + if (isChrome || isEdge || isOpera) { + // Media Stream Recording API has not been implemented in chrome yet; + // That's why using WebAudio API to record stereo audio in WAV format + recorder = StereoAudioRecorder; + } + + if (typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype && !isChrome) { + recorder = MediaStreamRecorder; + } + + // video recorder (in WebM format) + if (config.type === 'video' && (isChrome || isOpera)) { + recorder = WhammyRecorder; + + if (typeof WebAssemblyRecorder !== 'undefined' && typeof ReadableStream !== 'undefined') { + recorder = WebAssemblyRecorder; + } + } + + // video recorder (in Gif format) + if (config.type === 'gif') { + recorder = GifRecorder; + } + + // html2canvas recording! + if (config.type === 'canvas') { + recorder = CanvasRecorder; + } + + if (isMediaRecorderCompatible() && recorder !== CanvasRecorder && recorder !== GifRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { + if (getTracks(mediaStream, 'video').length || getTracks(mediaStream, 'audio').length) { + // audio-only recording + if (config.type === 'audio') { + if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('audio/webm')) { + recorder = MediaStreamRecorder; + } + // else recorder = StereoAudioRecorder; + } else { + // video or screen tracks + if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('video/webm')) { + recorder = MediaStreamRecorder; + } + } + } + } + + if (mediaStream instanceof Array && mediaStream.length) { + recorder = MultiStreamRecorder; + } + + if (config.recorderType) { + recorder = config.recorderType; + } + + if (!config.disableLogs && !!recorder && !!recorder.name) { + console.log('Using recorderType:', recorder.name || recorder.constructor.name); + } + + if (!recorder && isSafari) { + recorder = MediaStreamRecorder; + } + + return recorder; +} + +// _____________ +// MRecordRTC.js + +/** + * MRecordRTC runs on top of {@link RecordRTC} to bring multiple recordings in a single place, by providing simple API. + * @summary MRecordRTC stands for "Multiple-RecordRTC". + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef MRecordRTC + * @class + * @example + * var recorder = new MRecordRTC(); + * recorder.addStream(MediaStream); + * recorder.mediaType = { + * audio: true, // or StereoAudioRecorder or MediaStreamRecorder + * video: true, // or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder + * gif: true // or GifRecorder + * }; + * // mimeType is optional and should be set only in advance cases. + * recorder.mimeType = { + * audio: 'audio/wav', + * video: 'video/webm', + * gif: 'image/gif' + * }; + * recorder.startRecording(); + * @see For further information: + * @see {@link https://github.com/muaz-khan/RecordRTC/tree/master/MRecordRTC|MRecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @requires {@link RecordRTC} + */ + +function MRecordRTC(mediaStream) { + + /** + * This method attaches MediaStream object to {@link MRecordRTC}. + * @param {MediaStream} mediaStream - A MediaStream object, either fetched using getUserMedia API, or generated using captureStreamUntilEnded or WebAudio API. + * @method + * @memberof MRecordRTC + * @example + * recorder.addStream(MediaStream); + */ + this.addStream = function(_mediaStream) { + if (_mediaStream) { + mediaStream = _mediaStream; + } + }; + + /** + * This property can be used to set the recording type e.g. audio, or video, or gif, or canvas. + * @property {object} mediaType - {audio: true, video: true, gif: true} + * @memberof MRecordRTC + * @example + * var recorder = new MRecordRTC(); + * recorder.mediaType = { + * audio: true, // TRUE or StereoAudioRecorder or MediaStreamRecorder + * video: true, // TRUE or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder + * gif : true // TRUE or GifRecorder + * }; + */ + this.mediaType = { + audio: true, + video: true + }; + + /** + * This method starts recording. + * @method + * @memberof MRecordRTC + * @example + * recorder.startRecording(); + */ + this.startRecording = function() { + var mediaType = this.mediaType; + var recorderType; + var mimeType = this.mimeType || { + audio: null, + video: null, + gif: null + }; + + if (typeof mediaType.audio !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'audio').length) { + mediaType.audio = false; + } + + if (typeof mediaType.video !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { + mediaType.video = false; + } + + if (typeof mediaType.gif !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { + mediaType.gif = false; + } + + if (!mediaType.audio && !mediaType.video && !mediaType.gif) { + throw 'MediaStream must have either audio or video tracks.'; + } + + if (!!mediaType.audio) { + recorderType = null; + if (typeof mediaType.audio === 'function') { + recorderType = mediaType.audio; + } + + this.audioRecorder = new RecordRTC(mediaStream, { + type: 'audio', + bufferSize: this.bufferSize, + sampleRate: this.sampleRate, + numberOfAudioChannels: this.numberOfAudioChannels || 2, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.audio, + timeSlice: this.timeSlice, + onTimeStamp: this.onTimeStamp + }); + + if (!mediaType.video) { + this.audioRecorder.startRecording(); + } + } + + if (!!mediaType.video) { + recorderType = null; + if (typeof mediaType.video === 'function') { + recorderType = mediaType.video; + } + + var newStream = mediaStream; + + if (isMediaRecorderCompatible() && !!mediaType.audio && typeof mediaType.audio === 'function') { + var videoTrack = getTracks(mediaStream, 'video')[0]; + + if (isFirefox) { + newStream = new MediaStream(); + newStream.addTrack(videoTrack); + + if (recorderType && recorderType === WhammyRecorder) { + // Firefox does NOT supports webp-encoding yet + // But Firefox do supports WebAssemblyRecorder + recorderType = MediaStreamRecorder; + } + } else { + newStream = new MediaStream(); + newStream.addTrack(videoTrack); + } + } + + this.videoRecorder = new RecordRTC(newStream, { + type: 'video', + video: this.video, + canvas: this.canvas, + frameInterval: this.frameInterval || 10, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.video, + timeSlice: this.timeSlice, + onTimeStamp: this.onTimeStamp, + workerPath: this.workerPath, + webAssemblyPath: this.webAssemblyPath, + frameRate: this.frameRate, // used by WebAssemblyRecorder; values: usually 30; accepts any. + bitrate: this.bitrate // used by WebAssemblyRecorder; values: 0 to 1000+ + }); + + if (!mediaType.audio) { + this.videoRecorder.startRecording(); + } + } + + if (!!mediaType.audio && !!mediaType.video) { + var self = this; + + var isSingleRecorder = isMediaRecorderCompatible() === true; + + if (mediaType.audio instanceof StereoAudioRecorder && !!mediaType.video) { + isSingleRecorder = false; + } else if (mediaType.audio !== true && mediaType.video !== true && mediaType.audio !== mediaType.video) { + isSingleRecorder = false; + } + + if (isSingleRecorder === true) { + self.audioRecorder = null; + self.videoRecorder.startRecording(); + } else { + self.videoRecorder.initRecorder(function() { + self.audioRecorder.initRecorder(function() { + // Both recorders are ready to record things accurately + self.videoRecorder.startRecording(); + self.audioRecorder.startRecording(); + }); + }); + } + } + + if (!!mediaType.gif) { + recorderType = null; + if (typeof mediaType.gif === 'function') { + recorderType = mediaType.gif; + } + this.gifRecorder = new RecordRTC(mediaStream, { + type: 'gif', + frameRate: this.frameRate || 200, + quality: this.quality || 10, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.gif + }); + this.gifRecorder.startRecording(); + } + }; + + /** + * This method stops recording. + * @param {function} callback - Callback function is invoked when all encoders finished their jobs. + * @method + * @memberof MRecordRTC + * @example + * recorder.stopRecording(function(recording){ + * var audioBlob = recording.audio; + * var videoBlob = recording.video; + * var gifBlob = recording.gif; + * }); + */ + this.stopRecording = function(callback) { + callback = callback || function() {}; + + if (this.audioRecorder) { + this.audioRecorder.stopRecording(function(blobURL) { + callback(blobURL, 'audio'); + }); + } + + if (this.videoRecorder) { + this.videoRecorder.stopRecording(function(blobURL) { + callback(blobURL, 'video'); + }); + } + + if (this.gifRecorder) { + this.gifRecorder.stopRecording(function(blobURL) { + callback(blobURL, 'gif'); + }); + } + }; + + /** + * This method pauses recording. + * @method + * @memberof MRecordRTC + * @example + * recorder.pauseRecording(); + */ + this.pauseRecording = function() { + if (this.audioRecorder) { + this.audioRecorder.pauseRecording(); + } + + if (this.videoRecorder) { + this.videoRecorder.pauseRecording(); + } + + if (this.gifRecorder) { + this.gifRecorder.pauseRecording(); + } + }; + + /** + * This method resumes recording. + * @method + * @memberof MRecordRTC + * @example + * recorder.resumeRecording(); + */ + this.resumeRecording = function() { + if (this.audioRecorder) { + this.audioRecorder.resumeRecording(); + } + + if (this.videoRecorder) { + this.videoRecorder.resumeRecording(); + } + + if (this.gifRecorder) { + this.gifRecorder.resumeRecording(); + } + }; + + /** + * This method can be used to manually get all recorded blobs. + * @param {function} callback - All recorded blobs are passed back to the "callback" function. + * @method + * @memberof MRecordRTC + * @example + * recorder.getBlob(function(recording){ + * var audioBlob = recording.audio; + * var videoBlob = recording.video; + * var gifBlob = recording.gif; + * }); + * // or + * var audioBlob = recorder.getBlob().audio; + * var videoBlob = recorder.getBlob().video; + */ + this.getBlob = function(callback) { + var output = {}; + + if (this.audioRecorder) { + output.audio = this.audioRecorder.getBlob(); + } + + if (this.videoRecorder) { + output.video = this.videoRecorder.getBlob(); + } + + if (this.gifRecorder) { + output.gif = this.gifRecorder.getBlob(); + } + + if (callback) { + callback(output); + } + + return output; + }; + + /** + * Destroy all recorder instances. + * @method + * @memberof MRecordRTC + * @example + * recorder.destroy(); + */ + this.destroy = function() { + if (this.audioRecorder) { + this.audioRecorder.destroy(); + this.audioRecorder = null; + } + + if (this.videoRecorder) { + this.videoRecorder.destroy(); + this.videoRecorder = null; + } + + if (this.gifRecorder) { + this.gifRecorder.destroy(); + this.gifRecorder = null; + } + }; + + /** + * This method can be used to manually get all recorded blobs' DataURLs. + * @param {function} callback - All recorded blobs' DataURLs are passed back to the "callback" function. + * @method + * @memberof MRecordRTC + * @example + * recorder.getDataURL(function(recording){ + * var audioDataURL = recording.audio; + * var videoDataURL = recording.video; + * var gifDataURL = recording.gif; + * }); + */ + this.getDataURL = function(callback) { + this.getBlob(function(blob) { + if (blob.audio && blob.video) { + getDataURL(blob.audio, function(_audioDataURL) { + getDataURL(blob.video, function(_videoDataURL) { + callback({ + audio: _audioDataURL, + video: _videoDataURL + }); + }); + }); + } else if (blob.audio) { + getDataURL(blob.audio, function(_audioDataURL) { + callback({ + audio: _audioDataURL + }); + }); + } else if (blob.video) { + getDataURL(blob.video, function(_videoDataURL) { + callback({ + video: _videoDataURL + }); + }); + } + }); + + function getDataURL(blob, callback00) { + if (typeof Worker !== 'undefined') { + var webWorker = processInWebWorker(function readFile(_blob) { + postMessage(new FileReaderSync().readAsDataURL(_blob)); + }); + + webWorker.onmessage = function(event) { + callback00(event.data); + }; + + webWorker.postMessage(blob); + } else { + var reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onload = function(event) { + callback00(event.target.result); + }; + } + } + + function processInWebWorker(_function) { + var blob = URL.createObjectURL(new Blob([_function.toString(), + 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(blob); + var url; + if (typeof URL !== 'undefined') { + url = URL; + } else if (typeof webkitURL !== 'undefined') { + url = webkitURL; + } else { + throw 'Neither URL nor webkitURL detected.'; + } + url.revokeObjectURL(blob); + return worker; + } + }; + + /** + * This method can be used to ask {@link MRecordRTC} to write all recorded blobs into IndexedDB storage. + * @method + * @memberof MRecordRTC + * @example + * recorder.writeToDisk(); + */ + this.writeToDisk = function() { + RecordRTC.writeToDisk({ + audio: this.audioRecorder, + video: this.videoRecorder, + gif: this.gifRecorder + }); + }; + + /** + * This method can be used to invoke a save-as dialog for all recorded blobs. + * @param {object} args - {audio: 'audio-name', video: 'video-name', gif: 'gif-name'} + * @method + * @memberof MRecordRTC + * @example + * recorder.save({ + * audio: 'audio-file-name', + * video: 'video-file-name', + * gif : 'gif-file-name' + * }); + */ + this.save = function(args) { + args = args || { + audio: true, + video: true, + gif: true + }; + + if (!!args.audio && this.audioRecorder) { + this.audioRecorder.save(typeof args.audio === 'string' ? args.audio : ''); + } + + if (!!args.video && this.videoRecorder) { + this.videoRecorder.save(typeof args.video === 'string' ? args.video : ''); + } + if (!!args.gif && this.gifRecorder) { + this.gifRecorder.save(typeof args.gif === 'string' ? args.gif : ''); + } + }; +} + +/** + * This method can be used to get all recorded blobs from IndexedDB storage. + * @param {string} type - 'all' or 'audio' or 'video' or 'gif' + * @param {function} callback - Callback function to get all stored blobs. + * @method + * @memberof MRecordRTC + * @example + * MRecordRTC.getFromDisk('all', function(dataURL, type){ + * if(type === 'audio') { } + * if(type === 'video') { } + * if(type === 'gif') { } + * }); + */ +MRecordRTC.getFromDisk = RecordRTC.getFromDisk; + +/** + * This method can be used to store recorded blobs into IndexedDB storage. + * @param {object} options - {audio: Blob, video: Blob, gif: Blob} + * @method + * @memberof MRecordRTC + * @example + * MRecordRTC.writeToDisk({ + * audio: audioBlob, + * video: videoBlob, + * gif : gifBlob + * }); + */ +MRecordRTC.writeToDisk = RecordRTC.writeToDisk; + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.MRecordRTC = MRecordRTC; +} + +var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; + +(function(that) { + if (!that) { + return; + } + + if (typeof window !== 'undefined') { + return; + } + + if (typeof global === 'undefined') { + return; + } + + global.navigator = { + userAgent: browserFakeUserAgent, + getUserMedia: function() {} + }; + + if (!global.console) { + global.console = {}; + } + + if (typeof global.console.log === 'undefined' || typeof global.console.error === 'undefined') { + global.console.error = global.console.log = global.console.log || function() { + console.log(arguments); + }; + } + + if (typeof document === 'undefined') { + /*global document:true */ + that.document = {}; + + document.createElement = document.captureStream = document.mozCaptureStream = function() { + var obj = { + getContext: function() { + return obj; + }, + play: function() {}, + pause: function() {}, + drawImage: function() {}, + toDataURL: function() { + return ''; + } + }; + return obj; + }; + + that.HTMLVideoElement = function() {}; + } + + if (typeof location === 'undefined') { + /*global location:true */ + that.location = { + protocol: 'file:', + href: '', + hash: '' + }; + } + + if (typeof screen === 'undefined') { + /*global screen:true */ + that.screen = { + width: 0, + height: 0 + }; + } + + if (typeof URL === 'undefined') { + /*global screen:true */ + that.URL = { + createObjectURL: function() { + return ''; + }, + revokeObjectURL: function() { + return ''; + } + }; + } + + /*global window:true */ + that.window = global; +})(typeof global !== 'undefined' ? global : null); + +// _____________________________ +// Cross-Browser-Declarations.js + +// animation-frame used in WebM recording + +/*jshint -W079 */ +var requestAnimationFrame = window.requestAnimationFrame; +if (typeof requestAnimationFrame === 'undefined') { + if (typeof webkitRequestAnimationFrame !== 'undefined') { + /*global requestAnimationFrame:true */ + requestAnimationFrame = webkitRequestAnimationFrame; + } else if (typeof mozRequestAnimationFrame !== 'undefined') { + /*global requestAnimationFrame:true */ + requestAnimationFrame = mozRequestAnimationFrame; + } else if (typeof msRequestAnimationFrame !== 'undefined') { + /*global requestAnimationFrame:true */ + requestAnimationFrame = msRequestAnimationFrame; + } else if (typeof requestAnimationFrame === 'undefined') { + // via: https://gist.github.com/paulirish/1579671 + var lastTime = 0; + + /*global requestAnimationFrame:true */ + requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = setTimeout(function() { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } +} + +/*jshint -W079 */ +var cancelAnimationFrame = window.cancelAnimationFrame; +if (typeof cancelAnimationFrame === 'undefined') { + if (typeof webkitCancelAnimationFrame !== 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = webkitCancelAnimationFrame; + } else if (typeof mozCancelAnimationFrame !== 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = mozCancelAnimationFrame; + } else if (typeof msCancelAnimationFrame !== 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = msCancelAnimationFrame; + } else if (typeof cancelAnimationFrame === 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + } +} + +// WebAudio API representer +var AudioContext = window.AudioContext; + +if (typeof AudioContext === 'undefined') { + if (typeof webkitAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = webkitAudioContext; + } + + if (typeof mozAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = mozAudioContext; + } +} + +/*jshint -W079 */ +var URL = window.URL; + +if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { + /*global URL:true */ + URL = webkitURL; +} + +if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? + if (typeof navigator.webkitGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + + if (typeof navigator.mozGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.mozGetUserMedia; + } +} + +var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveBlob || !!navigator.msSaveOrOpenBlob); +var isOpera = !!window.opera || navigator.userAgent.indexOf('OPR/') !== -1; +var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 && ('netscape' in window) && / rv:/.test(navigator.userAgent); +var isChrome = (!isOpera && !isEdge && !!navigator.webkitGetUserMedia) || isElectron() || navigator.userAgent.toLowerCase().indexOf('chrome/') !== -1; + +var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + +if (isSafari && !isChrome && navigator.userAgent.indexOf('CriOS') !== -1) { + isSafari = false; + isChrome = true; +} + +var MediaStream = window.MediaStream; + +if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { + MediaStream = webkitMediaStream; +} + +/*global MediaStream:true */ +if (typeof MediaStream !== 'undefined') { + // override "stop" method for all browsers + if (typeof MediaStream.prototype.stop === 'undefined') { + MediaStream.prototype.stop = function() { + this.getTracks().forEach(function(track) { + track.stop(); + }); + }; + } +} + +// below function via: http://goo.gl/B3ae8c +/** + * Return human-readable file size. + * @param {number} bytes - Pass bytes and get formatted string. + * @returns {string} - formatted string + * @example + * bytesToSize(1024*1024*5) === '5 GB' + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ +function bytesToSize(bytes) { + var k = 1000; + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) { + return '0 Bytes'; + } + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); + return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; +} + +/** + * @param {Blob} file - File or Blob object. This parameter is required. + * @param {string} fileName - Optional file name e.g. "Recorded-Video.webm" + * @example + * invokeSaveAsDialog(blob or file, [optional] fileName); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ +function invokeSaveAsDialog(file, fileName) { + if (!file) { + throw 'Blob object is required.'; + } + + if (!file.type) { + try { + file.type = 'video/webm'; + } catch (e) {} + } + + var fileExtension = (file.type || 'video/webm').split('/')[1]; + + if (fileName && fileName.indexOf('.') !== -1) { + var splitted = fileName.split('.'); + fileName = splitted[0]; + fileExtension = splitted[1]; + } + + var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension; + + if (typeof navigator.msSaveOrOpenBlob !== 'undefined') { + return navigator.msSaveOrOpenBlob(file, fileFullName); + } else if (typeof navigator.msSaveBlob !== 'undefined') { + return navigator.msSaveBlob(file, fileFullName); + } + + var hyperlink = document.createElement('a'); + hyperlink.href = URL.createObjectURL(file); + hyperlink.download = fileFullName; + + hyperlink.style = 'display:none;opacity:0;color:transparent;'; + (document.body || document.documentElement).appendChild(hyperlink); + + if (typeof hyperlink.click === 'function') { + hyperlink.click(); + } else { + hyperlink.target = '_blank'; + hyperlink.dispatchEvent(new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true + })); + } + + URL.revokeObjectURL(hyperlink.href); +} + +/** + * from: https://github.com/cheton/is-electron/blob/master/index.js + **/ +function isElectron() { + // Renderer process + if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') { + return true; + } + + // Main process + if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) { + return true; + } + + // Detect the user agent when the `nodeIntegration` option is set to true + if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) { + return true; + } + + return false; +} + +function getTracks(stream, kind) { + if (!stream || !stream.getTracks) { + return []; + } + + return stream.getTracks().filter(function(t) { + return t.kind === (kind || 'audio'); + }); +} + +function setSrcObject(stream, element) { + if ('srcObject' in element) { + element.srcObject = stream; + } else if ('mozSrcObject' in element) { + element.mozSrcObject = stream; + } else { + element.srcObject = stream; + } +} + +/** + * @param {Blob} file - File or Blob object. + * @param {function} callback - Callback function. + * @example + * getSeekableBlob(blob or file, callback); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ +function getSeekableBlob(inputBlob, callback) { + // EBML.js copyrights goes to: https://github.com/legokichi/ts-ebml + if (typeof EBML === 'undefined') { + throw new Error('Please link: https://cdn.webrtc-experiment.com/EBML.js'); + } + + var reader = new EBML.Reader(); + var decoder = new EBML.Decoder(); + var tools = EBML.tools; + + var fileReader = new FileReader(); + fileReader.onload = function(e) { + var ebmlElms = decoder.decode(this.result); + ebmlElms.forEach(function(element) { + reader.read(element); + }); + reader.stop(); + var refinedMetadataBuf = tools.makeMetadataSeekable(reader.metadatas, reader.duration, reader.cues); + var body = this.result.slice(reader.metadataSize); + var newBlob = new Blob([refinedMetadataBuf, body], { + type: 'video/webm' + }); + + callback(newBlob); + }; + fileReader.readAsArrayBuffer(inputBlob); +} + +// __________ (used to handle stuff like http://goo.gl/xmE5eg) issue #129 +// Storage.js + +/** + * Storage is a standalone object used by {@link RecordRTC} to store reusable objects e.g. "new AudioContext". + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @example + * Storage.AudioContext === webkitAudioContext + * @property {webkitAudioContext} AudioContext - Keeps a reference to AudioContext object. + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ + +var Storage = {}; + +if (typeof AudioContext !== 'undefined') { + Storage.AudioContext = AudioContext; +} else if (typeof webkitAudioContext !== 'undefined') { + Storage.AudioContext = webkitAudioContext; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.Storage = Storage; +} + +function isMediaRecorderCompatible() { + if (isFirefox || isSafari || isEdge) { + return true; + } + + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + var fullVersion = '' + parseFloat(navigator.appVersion); + var majorVersion = parseInt(navigator.appVersion, 10); + var nameOffset, verOffset, ix; + + if (isChrome || isOpera) { + verOffset = nAgt.indexOf('Chrome'); + fullVersion = nAgt.substring(verOffset + 7); + } + + // trim the fullVersion string at semicolon/space if present + if ((ix = fullVersion.indexOf(';')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + if ((ix = fullVersion.indexOf(' ')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + majorVersion = parseInt('' + fullVersion, 10); + + if (isNaN(majorVersion)) { + fullVersion = '' + parseFloat(navigator.appVersion); + majorVersion = parseInt(navigator.appVersion, 10); + } + + return majorVersion >= 49; +} + +// ______________________ +// MediaStreamRecorder.js + +/** + * MediaStreamRecorder is an abstraction layer for {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. It is used by {@link RecordRTC} to record MediaStream(s) in both Chrome and Firefox. + * @summary Runs top over {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link https://github.com/muaz-khan|Muaz Khan} + * @typedef MediaStreamRecorder + * @class + * @example + * var config = { + * mimeType: 'video/webm', // vp8, vp9, h264, mkv, opus/vorbis + * audioBitsPerSecond : 256 * 8 * 1024, + * videoBitsPerSecond : 256 * 8 * 1024, + * bitsPerSecond: 256 * 8 * 1024, // if this is provided, skip above two + * checkForInactiveTracks: true, + * timeSlice: 1000, // concatenate intervals based blobs + * ondataavailable: function() {} // get intervals based blobs + * } + * var recorder = new MediaStreamRecorder(mediaStream, config); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * + * // or + * var blob = recorder.blob; + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {disableLogs:true, initCallback: function, mimeType: "video/webm", timeSlice: 1000} + * @throws Will throw an error if first argument "MediaStream" is missing. Also throws error if "MediaRecorder API" are not supported by the browser. + */ + +function MediaStreamRecorder(mediaStream, config) { + var self = this; + + if (typeof mediaStream === 'undefined') { + throw 'First argument "MediaStream" is required.'; + } + + if (typeof MediaRecorder === 'undefined') { + throw 'Your browser does not supports Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.'; + } + + config = config || { + // bitsPerSecond: 256 * 8 * 1024, + mimeType: 'video/webm' + }; + + if (config.type === 'audio') { + if (getTracks(mediaStream, 'video').length && getTracks(mediaStream, 'audio').length) { + var stream; + if (!!navigator.mozGetUserMedia) { + stream = new MediaStream(); + stream.addTrack(getTracks(mediaStream, 'audio')[0]); + } else { + // webkitMediaStream + stream = new MediaStream(getTracks(mediaStream, 'audio')); + } + mediaStream = stream; + } + + if (!config.mimeType || config.mimeType.toString().toLowerCase().indexOf('audio') === -1) { + config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg'; + } + + if (config.mimeType && config.mimeType.toString().toLowerCase() !== 'audio/ogg' && !!navigator.mozGetUserMedia) { + // forcing better codecs on Firefox (via #166) + config.mimeType = 'audio/ogg'; + } + } + + var arrayOfBlobs = []; + + /** + * This method returns array of blobs. Use only with "timeSlice". Its useful to preview recording anytime, without using the "stop" method. + * @method + * @memberof MediaStreamRecorder + * @example + * var arrayOfBlobs = recorder.getArrayOfBlobs(); + * @returns {Array} Returns array of recorded blobs. + */ + this.getArrayOfBlobs = function() { + return arrayOfBlobs; + }; + + /** + * This method records MediaStream. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.record(); + */ + this.record = function() { + // set defaults + self.blob = null; + self.clearRecordedData(); + self.timestamps = []; + allStates = []; + arrayOfBlobs = []; + + var recorderHints = config; + + if (!config.disableLogs) { + console.log('Passing following config over MediaRecorder API.', recorderHints); + } + + if (mediaRecorder) { + // mandatory to make sure Firefox doesn't fails to record streams 3-4 times without reloading the page. + mediaRecorder = null; + } + + if (isChrome && !isMediaRecorderCompatible()) { + // to support video-only recording on stable + recorderHints = 'video/vp8'; + } + + if (typeof MediaRecorder.isTypeSupported === 'function' && recorderHints.mimeType) { + if (!MediaRecorder.isTypeSupported(recorderHints.mimeType)) { + if (!config.disableLogs) { + console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); + } + + recorderHints.mimeType = config.type === 'audio' ? 'audio/webm' : 'video/webm'; + } + } + + // using MediaRecorder API here + try { + mediaRecorder = new MediaRecorder(mediaStream, recorderHints); + + // reset + config.mimeType = recorderHints.mimeType; + } catch (e) { + // chrome-based fallback + mediaRecorder = new MediaRecorder(mediaStream); + } + + // old hack? + if (recorderHints.mimeType && !MediaRecorder.isTypeSupported && 'canRecordMimeType' in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === false) { + if (!config.disableLogs) { + console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); + } + } + + // Dispatching OnDataAvailable Handler + mediaRecorder.ondataavailable = function(e) { + if (e.data) { + allStates.push('ondataavailable: ' + bytesToSize(e.data.size)); + } + + if (typeof config.timeSlice === 'number') { + if (e.data && e.data.size && e.data.size > 100) { + arrayOfBlobs.push(e.data); + updateTimeStamp(); + + if (typeof config.ondataavailable === 'function') { + // intervals based blobs + var blob = config.getNativeBlob ? e.data : new Blob([e.data], { + type: getMimeType(recorderHints) + }); + config.ondataavailable(blob); + } + } + return; + } + + if (!e.data || !e.data.size || e.data.size < 100 || self.blob) { + // make sure that stopRecording always getting fired + // even if there is invalid data + if (self.recordingCallback) { + self.recordingCallback(new Blob([], { + type: getMimeType(recorderHints) + })); + self.recordingCallback = null; + } + return; + } + + self.blob = config.getNativeBlob ? e.data : new Blob([e.data], { + type: getMimeType(recorderHints) + }); + + if (self.recordingCallback) { + self.recordingCallback(self.blob); + self.recordingCallback = null; + } + }; + + mediaRecorder.onstart = function() { + allStates.push('started'); + }; + + mediaRecorder.onpause = function() { + allStates.push('paused'); + }; + + mediaRecorder.onresume = function() { + allStates.push('resumed'); + }; + + mediaRecorder.onstop = function() { + allStates.push('stopped'); + }; + + mediaRecorder.onerror = function(error) { + if (!error) { + return; + } + + if (!error.name) { + error.name = 'UnknownError'; + } + + allStates.push('error: ' + error); + + if (!config.disableLogs) { + // via: https://w3c.github.io/mediacapture-record/MediaRecorder.html#exception-summary + if (error.name.toString().toLowerCase().indexOf('invalidstate') !== -1) { + console.error('The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.', error); + } else if (error.name.toString().toLowerCase().indexOf('notsupported') !== -1) { + console.error('MIME type (', recorderHints.mimeType, ') is not supported.', error); + } else if (error.name.toString().toLowerCase().indexOf('security') !== -1) { + console.error('MediaRecorder security error', error); + } + + // older code below + else if (error.name === 'OutOfMemory') { + console.error('The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.', error); + } else if (error.name === 'IllegalStreamModification') { + console.error('A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.', error); + } else if (error.name === 'OtherRecordingError') { + console.error('Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.', error); + } else if (error.name === 'GenericError') { + console.error('The UA cannot provide the codec or recording option that has been requested.', error); + } else { + console.error('MediaRecorder Error', error); + } + } + + (function(looper) { + if (!self.manuallyStopped && mediaRecorder && mediaRecorder.state === 'inactive') { + delete config.timeslice; + + // 10 minutes, enough? + mediaRecorder.start(10 * 60 * 1000); + return; + } + + setTimeout(looper, 1000); + })(); + + if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') { + mediaRecorder.stop(); + } + }; + + if (typeof config.timeSlice === 'number') { + updateTimeStamp(); + mediaRecorder.start(config.timeSlice); + } else { + // default is 60 minutes; enough? + // use config => {timeSlice: 1000} otherwise + + mediaRecorder.start(3.6e+6); + } + + if (config.initCallback) { + config.initCallback(); // old code + } + }; + + /** + * @property {Array} timestamps - Array of time stamps + * @memberof MediaStreamRecorder + * @example + * console.log(recorder.timestamps); + */ + this.timestamps = []; + + function updateTimeStamp() { + self.timestamps.push(new Date().getTime()); + + if (typeof config.onTimeStamp === 'function') { + config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps); + } + } + + function getMimeType(secondObject) { + if (mediaRecorder && mediaRecorder.mimeType) { + return mediaRecorder.mimeType; + } + + return secondObject.mimeType || 'video/webm'; + } + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + self.manuallyStopped = true; // used inside the mediaRecorder.onerror + + if (!mediaRecorder) { + return; + } + + this.recordingCallback = callback; + + if (mediaRecorder.state === 'recording') { + mediaRecorder.stop(); + } + + if (typeof config.timeSlice === 'number') { + setTimeout(function() { + self.blob = new Blob(arrayOfBlobs, { + type: getMimeType(config) + }); + + self.recordingCallback(self.blob); + }, 100); + } + }; + + /** + * This method pauses the recording process. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + if (!mediaRecorder) { + return; + } + + if (mediaRecorder.state === 'recording') { + mediaRecorder.pause(); + } + }; + + /** + * This method resumes the recording process. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + if (!mediaRecorder) { + return; + } + + if (mediaRecorder.state === 'paused') { + mediaRecorder.resume(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (mediaRecorder && mediaRecorder.state === 'recording') { + self.stop(clearRecordedDataCB); + } + + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + arrayOfBlobs = []; + mediaRecorder = null; + self.timestamps = []; + } + + // Reference to "MediaRecorder" object + var mediaRecorder; + + /** + * Access to native MediaRecorder API + * @method + * @memberof MediaStreamRecorder + * @instance + * @example + * var internal = recorder.getInternalRecorder(); + * internal.ondataavailable = function() {}; // override + * internal.stream, internal.onpause, internal.onstop, etc. + * @returns {Object} Returns internal recording object. + */ + this.getInternalRecorder = function() { + return mediaRecorder; + }; + + function isMediaStreamActive() { + if ('active' in mediaStream) { + if (!mediaStream.active) { + return false; + } + } else if ('ended' in mediaStream) { // old hack + if (mediaStream.ended) { + return false; + } + } + return true; + } + + /** + * @property {Blob} blob - Recorded data as "Blob" object. + * @memberof MediaStreamRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + this.blob = null; + + + /** + * Get MediaRecorder readonly state. + * @method + * @memberof MediaStreamRecorder + * @example + * var state = recorder.getState(); + * @returns {String} Returns recording state. + */ + this.getState = function() { + if (!mediaRecorder) { + return 'inactive'; + } + + return mediaRecorder.state || 'inactive'; + }; + + // list of all recording states + var allStates = []; + + /** + * Get MediaRecorder all recording states. + * @method + * @memberof MediaStreamRecorder + * @example + * var state = recorder.getAllStates(); + * @returns {Array} Returns all recording states + */ + this.getAllStates = function() { + return allStates; + }; + + // if any Track within the MediaStream is muted or not enabled at any time, + // the browser will only record black frames + // or silence since that is the content produced by the Track + // so we need to stopRecording as soon as any single track ends. + if (typeof config.checkForInactiveTracks === 'undefined') { + config.checkForInactiveTracks = false; // disable to minimize CPU usage + } + + var self = this; + + // this method checks if media stream is stopped + // or if any track is ended. + (function looper() { + if (!mediaRecorder || config.checkForInactiveTracks === false) { + return; + } + + if (isMediaStreamActive() === false) { + if (!config.disableLogs) { + console.log('MediaStream seems stopped.'); + } + self.stop(); + return; + } + + setTimeout(looper, 1000); // check every second + })(); + + // for debugging + this.name = 'MediaStreamRecorder'; + this.toString = function() { + return this.name; + }; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.MediaStreamRecorder = MediaStreamRecorder; +} + +// source code from: http://typedarray.org/wp-content/projects/WebAudioRecorder/script.js +// https://github.com/mattdiamond/Recorderjs#license-mit +// ______________________ +// StereoAudioRecorder.js + +/** + * StereoAudioRecorder is a standalone class used by {@link RecordRTC} to bring "stereo" audio-recording in chrome. + * @summary JavaScript standalone object for stereo audio recording. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef StereoAudioRecorder + * @class + * @example + * var recorder = new StereoAudioRecorder(MediaStream, { + * sampleRate: 44100, + * bufferSize: 4096 + * }); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {sampleRate: 44100, bufferSize: 4096, numberOfAudioChannels: 1, etc.} + */ + +function StereoAudioRecorder(mediaStream, config) { + if (!getTracks(mediaStream, 'audio').length) { + throw 'Your stream has no audio tracks.'; + } + + config = config || {}; + + var self = this; + + // variables + var leftchannel = []; + var rightchannel = []; + var recording = false; + var recordingLength = 0; + var jsAudioNode; + + var numberOfAudioChannels = 2; + + /** + * Set sample rates such as 8K or 16K. Reference: http://stackoverflow.com/a/28977136/552182 + * @property {number} desiredSampRate - Desired Bits per sample * 1000 + * @memberof StereoAudioRecorder + * @instance + * @example + * var recorder = StereoAudioRecorder(mediaStream, { + * desiredSampRate: 16 * 1000 // bits-per-sample * 1000 + * }); + */ + var desiredSampRate = config.desiredSampRate; + + // backward compatibility + if (config.leftChannel === true) { + numberOfAudioChannels = 1; + } + + if (config.numberOfAudioChannels === 1) { + numberOfAudioChannels = 1; + } + + if (!numberOfAudioChannels || numberOfAudioChannels < 1) { + numberOfAudioChannels = 2; + } + + if (!config.disableLogs) { + console.log('StereoAudioRecorder is set to record number of channels: ' + numberOfAudioChannels); + } + + // if any Track within the MediaStream is muted or not enabled at any time, + // the browser will only record black frames + // or silence since that is the content produced by the Track + // so we need to stopRecording as soon as any single track ends. + if (typeof config.checkForInactiveTracks === 'undefined') { + config.checkForInactiveTracks = true; + } + + function isMediaStreamActive() { + if (config.checkForInactiveTracks === false) { + // always return "true" + return true; + } + + if ('active' in mediaStream) { + if (!mediaStream.active) { + return false; + } + } else if ('ended' in mediaStream) { // old hack + if (mediaStream.ended) { + return false; + } + } + return true; + } + + /** + * This method records MediaStream. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.record(); + */ + this.record = function() { + if (isMediaStreamActive() === false) { + throw 'Please make sure MediaStream is active.'; + } + + resetVariables(); + + isAudioProcessStarted = isPaused = false; + recording = true; + + if (typeof config.timeSlice !== 'undefined') { + looper(); + } + }; + + function mergeLeftRightBuffers(config, callback) { + function mergeAudioBuffers(config, cb) { + var numberOfAudioChannels = config.numberOfAudioChannels; + + // todo: "slice(0)" --- is it causes loop? Should be removed? + var leftBuffers = config.leftBuffers.slice(0); + var rightBuffers = config.rightBuffers.slice(0); + var sampleRate = config.sampleRate; + var internalInterleavedLength = config.internalInterleavedLength; + var desiredSampRate = config.desiredSampRate; + + if (numberOfAudioChannels === 2) { + leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); + rightBuffers = mergeBuffers(rightBuffers, internalInterleavedLength); + + if (desiredSampRate) { + leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); + rightBuffers = interpolateArray(rightBuffers, desiredSampRate, sampleRate); + } + } + + if (numberOfAudioChannels === 1) { + leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); + + if (desiredSampRate) { + leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); + } + } + + // set sample rate as desired sample rate + if (desiredSampRate) { + sampleRate = desiredSampRate; + } + + // for changing the sampling rate, reference: + // http://stackoverflow.com/a/28977136/552182 + function interpolateArray(data, newSampleRate, oldSampleRate) { + var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate)); + var newData = []; + var springFactor = Number((data.length - 1) / (fitCount - 1)); + newData[0] = data[0]; + for (var i = 1; i < fitCount - 1; i++) { + var tmp = i * springFactor; + var before = Number(Math.floor(tmp)).toFixed(); + var after = Number(Math.ceil(tmp)).toFixed(); + var atPoint = tmp - before; + newData[i] = linearInterpolate(data[before], data[after], atPoint); + } + newData[fitCount - 1] = data[data.length - 1]; + return newData; + } + + function linearInterpolate(before, after, atPoint) { + return before + (after - before) * atPoint; + } + + function mergeBuffers(channelBuffer, rLength) { + var result = new Float64Array(rLength); + var offset = 0; + var lng = channelBuffer.length; + + for (var i = 0; i < lng; i++) { + var buffer = channelBuffer[i]; + result.set(buffer, offset); + offset += buffer.length; + } + + return result; + } + + function interleave(leftChannel, rightChannel) { + var length = leftChannel.length + rightChannel.length; + + var result = new Float64Array(length); + + var inputIndex = 0; + + for (var index = 0; index < length;) { + result[index++] = leftChannel[inputIndex]; + result[index++] = rightChannel[inputIndex]; + inputIndex++; + } + return result; + } + + function writeUTFBytes(view, offset, string) { + var lng = string.length; + for (var i = 0; i < lng; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } + } + + // interleave both channels together + var interleaved; + + if (numberOfAudioChannels === 2) { + interleaved = interleave(leftBuffers, rightBuffers); + } + + if (numberOfAudioChannels === 1) { + interleaved = leftBuffers; + } + + var interleavedLength = interleaved.length; + + // create wav file + var resultingBufferLength = 44 + interleavedLength * 2; + + var buffer = new ArrayBuffer(resultingBufferLength); + + var view = new DataView(buffer); + + // RIFF chunk descriptor/identifier + writeUTFBytes(view, 0, 'RIFF'); + + // RIFF chunk length + // changed "44" to "36" via #401 + view.setUint32(4, 36 + interleavedLength * 2, true); + + // RIFF type + writeUTFBytes(view, 8, 'WAVE'); + + // format chunk identifier + // FMT sub-chunk + writeUTFBytes(view, 12, 'fmt '); + + // format chunk length + view.setUint32(16, 16, true); + + // sample format (raw) + view.setUint16(20, 1, true); + + // stereo (2 channels) + view.setUint16(22, numberOfAudioChannels, true); + + // sample rate + view.setUint32(24, sampleRate, true); + + // byte rate (sample rate * block align) + view.setUint32(28, sampleRate * 2, true); + + // block align (channel count * bytes per sample) + view.setUint16(32, numberOfAudioChannels * 2, true); + + // bits per sample + view.setUint16(34, 16, true); + + // data sub-chunk + // data chunk identifier + writeUTFBytes(view, 36, 'data'); + + // data chunk length + view.setUint32(40, interleavedLength * 2, true); + + // write the PCM samples + var lng = interleavedLength; + var index = 44; + var volume = 1; + for (var i = 0; i < lng; i++) { + view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); + index += 2; + } + + if (cb) { + return cb({ + buffer: buffer, + view: view + }); + } + + postMessage({ + buffer: buffer, + view: view + }); + } + + if (config.noWorker) { + mergeAudioBuffers(config, function(data) { + callback(data.buffer, data.view); + }); + return; + } + + + var webWorker = processInWebWorker(mergeAudioBuffers); + + webWorker.onmessage = function(event) { + callback(event.data.buffer, event.data.view); + + // release memory + URL.revokeObjectURL(webWorker.workerURL); + + // kill webworker (or Chrome will kill your page after ~25 calls) + webWorker.terminate(); + }; + + webWorker.postMessage(config); + } + + function processInWebWorker(_function) { + var workerURL = URL.createObjectURL(new Blob([_function.toString(), + ';this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(workerURL); + worker.workerURL = workerURL; + return worker; + } + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + // stop recording + recording = false; + + mergeLeftRightBuffers({ + desiredSampRate: desiredSampRate, + sampleRate: sampleRate, + numberOfAudioChannels: numberOfAudioChannels, + internalInterleavedLength: recordingLength, + leftBuffers: leftchannel, + rightBuffers: numberOfAudioChannels === 1 ? [] : rightchannel, + noWorker: config.noWorker + }, function(buffer, view) { + /** + * @property {Blob} blob - The recorded blob object. + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(){ + * var blob = recorder.blob; + * }); + */ + self.blob = new Blob([view], { + type: 'audio/wav' + }); + + /** + * @property {ArrayBuffer} buffer - The recorded buffer object. + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(){ + * var buffer = recorder.buffer; + * }); + */ + self.buffer = new ArrayBuffer(view.buffer.byteLength); + + /** + * @property {DataView} view - The recorded data-view object. + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(){ + * var view = recorder.view; + * }); + */ + self.view = view; + + self.sampleRate = desiredSampRate || sampleRate; + self.bufferSize = bufferSize; + + // recorded audio length + self.length = recordingLength; + + isAudioProcessStarted = false; + + if (callback) { + callback(self.blob); + } + }); + }; + + if (typeof Storage === 'undefined') { + var Storage = { + AudioContextConstructor: null, + AudioContext: window.AudioContext || window.webkitAudioContext + }; + } + + if (!Storage.AudioContextConstructor) { + Storage.AudioContextConstructor = new Storage.AudioContext(); + } + + var context = Storage.AudioContextConstructor; + + // creates an audio node from the microphone incoming stream + var audioInput = context.createMediaStreamSource(mediaStream); + + var legalBufferValues = [0, 256, 512, 1024, 2048, 4096, 8192, 16384]; + + /** + * From the spec: This value controls how frequently the audioprocess event is + * dispatched and how many sample-frames need to be processed each call. + * Lower values for buffer size will result in a lower (better) latency. + * Higher values will be necessary to avoid audio breakup and glitches + * The size of the buffer (in sample-frames) which needs to + * be processed each time onprocessaudio is called. + * Legal values are (256, 512, 1024, 2048, 4096, 8192, 16384). + * @property {number} bufferSize - Buffer-size for how frequently the audioprocess event is dispatched. + * @memberof StereoAudioRecorder + * @example + * recorder = new StereoAudioRecorder(mediaStream, { + * bufferSize: 4096 + * }); + */ + + // "0" means, let chrome decide the most accurate buffer-size for current platform. + var bufferSize = typeof config.bufferSize === 'undefined' ? 4096 : config.bufferSize; + + if (legalBufferValues.indexOf(bufferSize) === -1) { + if (!config.disableLogs) { + console.log('Legal values for buffer-size are ' + JSON.stringify(legalBufferValues, null, '\t')); + } + } + + if (context.createJavaScriptNode) { + jsAudioNode = context.createJavaScriptNode(bufferSize, numberOfAudioChannels, numberOfAudioChannels); + } else if (context.createScriptProcessor) { + jsAudioNode = context.createScriptProcessor(bufferSize, numberOfAudioChannels, numberOfAudioChannels); + } else { + throw 'WebAudio API has no support on this browser.'; + } + + // connect the stream to the script processor + audioInput.connect(jsAudioNode); + + if (!config.bufferSize) { + bufferSize = jsAudioNode.bufferSize; // device buffer-size + } + + /** + * The sample rate (in sample-frames per second) at which the + * AudioContext handles audio. It is assumed that all AudioNodes + * in the context run at this rate. In making this assumption, + * sample-rate converters or "varispeed" processors are not supported + * in real-time processing. + * The sampleRate parameter describes the sample-rate of the + * linear PCM audio data in the buffer in sample-frames per second. + * An implementation must support sample-rates in at least + * the range 22050 to 96000. + * @property {number} sampleRate - Buffer-size for how frequently the audioprocess event is dispatched. + * @memberof StereoAudioRecorder + * @example + * recorder = new StereoAudioRecorder(mediaStream, { + * sampleRate: 44100 + * }); + */ + var sampleRate = typeof config.sampleRate !== 'undefined' ? config.sampleRate : context.sampleRate || 44100; + + if (sampleRate < 22050 || sampleRate > 96000) { + // Ref: http://stackoverflow.com/a/26303918/552182 + if (!config.disableLogs) { + console.log('sample-rate must be under range 22050 and 96000.'); + } + } + + if (!config.disableLogs) { + if (config.desiredSampRate) { + console.log('Desired sample-rate: ' + config.desiredSampRate); + } + } + + var isPaused = false; + /** + * This method pauses the recording process. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPaused = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + if (isMediaStreamActive() === false) { + throw 'Please make sure MediaStream is active.'; + } + + if (!recording) { + if (!config.disableLogs) { + console.log('Seems recording has been restarted.'); + } + this.record(); + return; + } + + isPaused = false; + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + config.checkForInactiveTracks = false; + + if (recording) { + this.stop(clearRecordedDataCB); + } + + clearRecordedDataCB(); + }; + + function resetVariables() { + leftchannel = []; + rightchannel = []; + recordingLength = 0; + isAudioProcessStarted = false; + recording = false; + isPaused = false; + context = null; + + self.leftchannel = leftchannel; + self.rightchannel = rightchannel; + self.numberOfAudioChannels = numberOfAudioChannels; + self.desiredSampRate = desiredSampRate; + self.sampleRate = sampleRate; + self.recordingLength = recordingLength; + + intervalsBasedBuffers = { + left: [], + right: [], + recordingLength: 0 + }; + } + + function clearRecordedDataCB() { + if (jsAudioNode) { + jsAudioNode.onaudioprocess = null; + jsAudioNode.disconnect(); + jsAudioNode = null; + } + + if (audioInput) { + audioInput.disconnect(); + audioInput = null; + } + + resetVariables(); + } + + // for debugging + this.name = 'StereoAudioRecorder'; + this.toString = function() { + return this.name; + }; + + var isAudioProcessStarted = false; + + function onAudioProcessDataAvailable(e) { + if (isPaused) { + return; + } + + if (isMediaStreamActive() === false) { + if (!config.disableLogs) { + console.log('MediaStream seems stopped.'); + } + jsAudioNode.disconnect(); + recording = false; + } + + if (!recording) { + if (audioInput) { + audioInput.disconnect(); + audioInput = null; + } + return; + } + + /** + * This method is called on "onaudioprocess" event's first invocation. + * @method {function} onAudioProcessStarted + * @memberof StereoAudioRecorder + * @example + * recorder.onAudioProcessStarted: function() { }; + */ + if (!isAudioProcessStarted) { + isAudioProcessStarted = true; + if (config.onAudioProcessStarted) { + config.onAudioProcessStarted(); + } + + if (config.initCallback) { + config.initCallback(); + } + } + + var left = e.inputBuffer.getChannelData(0); + + // we clone the samples + var chLeft = new Float32Array(left); + leftchannel.push(chLeft); + + if (numberOfAudioChannels === 2) { + var right = e.inputBuffer.getChannelData(1); + var chRight = new Float32Array(right); + rightchannel.push(chRight); + } + + recordingLength += bufferSize; + + // export raw PCM + self.recordingLength = recordingLength; + + if (typeof config.timeSlice !== 'undefined') { + intervalsBasedBuffers.recordingLength += bufferSize; + intervalsBasedBuffers.left.push(chLeft); + + if (numberOfAudioChannels === 2) { + intervalsBasedBuffers.right.push(chRight); + } + } + } + + jsAudioNode.onaudioprocess = onAudioProcessDataAvailable; + + // to prevent self audio to be connected with speakers + if (context.createMediaStreamDestination) { + jsAudioNode.connect(context.createMediaStreamDestination()); + } else { + jsAudioNode.connect(context.destination); + } + + // export raw PCM + this.leftchannel = leftchannel; + this.rightchannel = rightchannel; + this.numberOfAudioChannels = numberOfAudioChannels; + this.desiredSampRate = desiredSampRate; + this.sampleRate = sampleRate; + self.recordingLength = recordingLength; + + // helper for intervals based blobs + var intervalsBasedBuffers = { + left: [], + right: [], + recordingLength: 0 + }; + + // this looper is used to support intervals based blobs (via timeSlice+ondataavailable) + function looper() { + if (!recording || typeof config.ondataavailable !== 'function' || typeof config.timeSlice === 'undefined') { + return; + } + + if (intervalsBasedBuffers.left.length) { + mergeLeftRightBuffers({ + desiredSampRate: desiredSampRate, + sampleRate: sampleRate, + numberOfAudioChannels: numberOfAudioChannels, + internalInterleavedLength: intervalsBasedBuffers.recordingLength, + leftBuffers: intervalsBasedBuffers.left, + rightBuffers: numberOfAudioChannels === 1 ? [] : intervalsBasedBuffers.right + }, function(buffer, view) { + var blob = new Blob([view], { + type: 'audio/wav' + }); + config.ondataavailable(blob); + + setTimeout(looper, config.timeSlice); + }); + + intervalsBasedBuffers = { + left: [], + right: [], + recordingLength: 0 + }; + } else { + setTimeout(looper, config.timeSlice); + } + } +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.StereoAudioRecorder = StereoAudioRecorder; +} + +// _________________ +// CanvasRecorder.js + +/** + * CanvasRecorder is a standalone class used by {@link RecordRTC} to bring HTML5-Canvas recording into video WebM. It uses HTML2Canvas library and runs top over {@link Whammy}. + * @summary HTML2Canvas recording into video WebM. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef CanvasRecorder + * @class + * @example + * var recorder = new CanvasRecorder(htmlElement, { disableLogs: true, useWhammyRecorder: true }); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {HTMLElement} htmlElement - querySelector/getElementById/getElementsByTagName[0]/etc. + * @param {object} config - {disableLogs:true, initCallback: function} + */ + +function CanvasRecorder(htmlElement, config) { + if (typeof html2canvas === 'undefined') { + throw 'Please link: https://cdn.webrtc-experiment.com/screenshot.js'; + } + + config = config || {}; + if (!config.frameInterval) { + config.frameInterval = 10; + } + + // via DetectRTC.js + var isCanvasSupportsStreamCapturing = false; + ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) { + if (item in document.createElement('canvas')) { + isCanvasSupportsStreamCapturing = true; + } + }); + + var _isChrome = (!!window.webkitRTCPeerConnection || !!window.webkitGetUserMedia) && !!window.chrome; + + var chromeVersion = 50; + var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); + if (_isChrome && matchArray && matchArray[2]) { + chromeVersion = parseInt(matchArray[2], 10); + } + + if (_isChrome && chromeVersion < 52) { + isCanvasSupportsStreamCapturing = false; + } + + if (config.useWhammyRecorder) { + isCanvasSupportsStreamCapturing = false; + } + + var globalCanvas, mediaStreamRecorder; + + if (isCanvasSupportsStreamCapturing) { + if (!config.disableLogs) { + console.log('Your browser supports both MediRecorder API and canvas.captureStream!'); + } + + if (htmlElement instanceof HTMLCanvasElement) { + globalCanvas = htmlElement; + } else if (htmlElement instanceof CanvasRenderingContext2D) { + globalCanvas = htmlElement.canvas; + } else { + throw 'Please pass either HTMLCanvasElement or CanvasRenderingContext2D.'; + } + } else if (!!navigator.mozGetUserMedia) { + if (!config.disableLogs) { + console.error('Canvas recording is NOT supported in Firefox.'); + } + } + + var isRecording; + + /** + * This method records Canvas. + * @method + * @memberof CanvasRecorder + * @example + * recorder.record(); + */ + this.record = function() { + isRecording = true; + + if (isCanvasSupportsStreamCapturing && !config.useWhammyRecorder) { + // CanvasCaptureMediaStream + var canvasMediaStream; + if ('captureStream' in globalCanvas) { + canvasMediaStream = globalCanvas.captureStream(25); // 25 FPS + } else if ('mozCaptureStream' in globalCanvas) { + canvasMediaStream = globalCanvas.mozCaptureStream(25); + } else if ('webkitCaptureStream' in globalCanvas) { + canvasMediaStream = globalCanvas.webkitCaptureStream(25); + } + + try { + var mdStream = new MediaStream(); + mdStream.addTrack(getTracks(canvasMediaStream, 'video')[0]); + canvasMediaStream = mdStream; + } catch (e) {} + + if (!canvasMediaStream) { + throw 'captureStream API are NOT available.'; + } + + // Note: Jan 18, 2016 status is that, + // Firefox MediaRecorder API can't record CanvasCaptureMediaStream object. + mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, { + mimeType: config.mimeType || 'video/webm' + }); + mediaStreamRecorder.record(); + } else { + whammy.frames = []; + lastTime = new Date().getTime(); + drawCanvasFrame(); + } + + if (config.initCallback) { + config.initCallback(); + } + }; + + this.getWebPImages = function(callback) { + if (htmlElement.nodeName.toLowerCase() !== 'canvas') { + callback(); + return; + } + + var framesLength = whammy.frames.length; + whammy.frames.forEach(function(frame, idx) { + var framesRemaining = framesLength - idx; + if (!config.disableLogs) { + console.log(framesRemaining + '/' + framesLength + ' frames remaining'); + } + + if (config.onEncodingCallback) { + config.onEncodingCallback(framesRemaining, framesLength); + } + + var webp = frame.image.toDataURL('image/webp', 1); + whammy.frames[idx].image = webp; + }); + + if (!config.disableLogs) { + console.log('Generating WebM'); + } + + callback(); + }; + + /** + * This method stops recording Canvas. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof CanvasRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + isRecording = false; + + var that = this; + + if (isCanvasSupportsStreamCapturing && mediaStreamRecorder) { + mediaStreamRecorder.stop(callback); + return; + } + + this.getWebPImages(function() { + /** + * @property {Blob} blob - Recorded frames in video/webm blob. + * @memberof CanvasRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + whammy.compile(function(blob) { + if (!config.disableLogs) { + console.log('Recording finished!'); + } + + that.blob = blob; + + if (that.blob.forEach) { + that.blob = new Blob([], { + type: 'video/webm' + }); + } + + if (callback) { + callback(that.blob); + } + + whammy.frames = []; + }); + }); + }; + + var isPausedRecording = false; + + /** + * This method pauses the recording process. + * @method + * @memberof CanvasRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPausedRecording = true; + + if (mediaStreamRecorder instanceof MediaStreamRecorder) { + mediaStreamRecorder.pause(); + return; + } + }; + + /** + * This method resumes the recording process. + * @method + * @memberof CanvasRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPausedRecording = false; + + if (mediaStreamRecorder instanceof MediaStreamRecorder) { + mediaStreamRecorder.resume(); + return; + } + + if (!isRecording) { + this.record(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof CanvasRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (isRecording) { + this.stop(clearRecordedDataCB); + } + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + whammy.frames = []; + isRecording = false; + isPausedRecording = false; + } + + // for debugging + this.name = 'CanvasRecorder'; + this.toString = function() { + return this.name; + }; + + function cloneCanvas() { + //create a new canvas + var newCanvas = document.createElement('canvas'); + var context = newCanvas.getContext('2d'); + + //set dimensions + newCanvas.width = htmlElement.width; + newCanvas.height = htmlElement.height; + + //apply the old canvas to the new one + context.drawImage(htmlElement, 0, 0); + + //return the new canvas + return newCanvas; + } + + function drawCanvasFrame() { + if (isPausedRecording) { + lastTime = new Date().getTime(); + return setTimeout(drawCanvasFrame, 500); + } + + if (htmlElement.nodeName.toLowerCase() === 'canvas') { + var duration = new Date().getTime() - lastTime; + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); + + whammy.frames.push({ + image: cloneCanvas(), + duration: duration + }); + + if (isRecording) { + setTimeout(drawCanvasFrame, config.frameInterval); + } + return; + } + + html2canvas(htmlElement, { + grabMouse: typeof config.showMousePointer === 'undefined' || config.showMousePointer, + onrendered: function(canvas) { + var duration = new Date().getTime() - lastTime; + if (!duration) { + return setTimeout(drawCanvasFrame, config.frameInterval); + } + + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); + + whammy.frames.push({ + image: canvas.toDataURL('image/webp', 1), + duration: duration + }); + + if (isRecording) { + setTimeout(drawCanvasFrame, config.frameInterval); + } + } + }); + } + + var lastTime = new Date().getTime(); + + var whammy = new Whammy.Video(100); +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.CanvasRecorder = CanvasRecorder; +} + +// _________________ +// WhammyRecorder.js + +/** + * WhammyRecorder is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It runs top over {@link Whammy}. + * @summary Video recording feature in Chrome. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef WhammyRecorder + * @class + * @example + * var recorder = new WhammyRecorder(mediaStream); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {disableLogs: true, initCallback: function, video: HTMLVideoElement, etc.} + */ + +function WhammyRecorder(mediaStream, config) { + + config = config || {}; + + if (!config.frameInterval) { + config.frameInterval = 10; + } + + if (!config.disableLogs) { + console.log('Using frames-interval:', config.frameInterval); + } + + /** + * This method records video. + * @method + * @memberof WhammyRecorder + * @example + * recorder.record(); + */ + this.record = function() { + if (!config.width) { + config.width = 320; + } + + if (!config.height) { + config.height = 240; + } + + if (!config.video) { + config.video = { + width: config.width, + height: config.height + }; + } + + if (!config.canvas) { + config.canvas = { + width: config.width, + height: config.height + }; + } + + canvas.width = config.canvas.width || 320; + canvas.height = config.canvas.height || 240; + + context = canvas.getContext('2d'); + + // setting defaults + if (config.video && config.video instanceof HTMLVideoElement) { + video = config.video.cloneNode(); + + if (config.initCallback) { + config.initCallback(); + } + } else { + video = document.createElement('video'); + + setSrcObject(mediaStream, video); + + video.onloadedmetadata = function() { // "onloadedmetadata" may NOT work in FF? + if (config.initCallback) { + config.initCallback(); + } + }; + + video.width = config.video.width; + video.height = config.video.height; + } + + video.muted = true; + video.play(); + + lastTime = new Date().getTime(); + whammy = new Whammy.Video(); + + if (!config.disableLogs) { + console.log('canvas resolutions', canvas.width, '*', canvas.height); + console.log('video width/height', video.width || canvas.width, '*', video.height || canvas.height); + } + + drawFrames(config.frameInterval); + }; + + /** + * Draw and push frames to Whammy + * @param {integer} frameInterval - set minimum interval (in milliseconds) between each time we push a frame to Whammy + */ + function drawFrames(frameInterval) { + frameInterval = typeof frameInterval !== 'undefined' ? frameInterval : 10; + + var duration = new Date().getTime() - lastTime; + if (!duration) { + return setTimeout(drawFrames, frameInterval, frameInterval); + } + + if (isPausedRecording) { + lastTime = new Date().getTime(); + return setTimeout(drawFrames, 100); + } + + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); + + if (video.paused) { + // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 + // Tweak for Android Chrome + video.play(); + } + + context.drawImage(video, 0, 0, canvas.width, canvas.height); + whammy.frames.push({ + duration: duration, + image: canvas.toDataURL('image/webp') + }); + + if (!isStopDrawing) { + setTimeout(drawFrames, frameInterval, frameInterval); + } + } + + function asyncLoop(o) { + var i = -1, + length = o.length; + + (function loop() { + i++; + if (i === length) { + o.callback(); + return; + } + + // "setTimeout" added by Jim McLeod + setTimeout(function() { + o.functionToLoop(loop, i); + }, 1); + })(); + } + + + /** + * remove black frames from the beginning to the specified frame + * @param {Array} _frames - array of frames to be checked + * @param {number} _framesToCheck - number of frame until check will be executed (-1 - will drop all frames until frame not matched will be found) + * @param {number} _pixTolerance - 0 - very strict (only black pixel color) ; 1 - all + * @param {number} _frameTolerance - 0 - very strict (only black frame color) ; 1 - all + * @returns {Array} - array of frames + */ + // pull#293 by @volodalexey + function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance, callback) { + var localCanvas = document.createElement('canvas'); + localCanvas.width = canvas.width; + localCanvas.height = canvas.height; + var context2d = localCanvas.getContext('2d'); + var resultFrames = []; + + var checkUntilNotBlack = _framesToCheck === -1; + var endCheckFrame = (_framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length) ? + _framesToCheck : _frames.length; + var sampleColor = { + r: 0, + g: 0, + b: 0 + }; + var maxColorDifference = Math.sqrt( + Math.pow(255, 2) + + Math.pow(255, 2) + + Math.pow(255, 2) + ); + var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0; + var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0; + var doNotCheckNext = false; + + asyncLoop({ + length: endCheckFrame, + functionToLoop: function(loop, f) { + var matchPixCount, endPixCheck, maxPixCount; + + var finishImage = function() { + if (!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance) { + // console.log('removed black frame : ' + f + ' ; frame duration ' + _frames[f].duration); + } else { + // console.log('frame is passed : ' + f); + if (checkUntilNotBlack) { + doNotCheckNext = true; + } + resultFrames.push(_frames[f]); + } + loop(); + }; + + if (!doNotCheckNext) { + var image = new Image(); + image.onload = function() { + context2d.drawImage(image, 0, 0, canvas.width, canvas.height); + var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height); + matchPixCount = 0; + endPixCheck = imageData.data.length; + maxPixCount = imageData.data.length / 4; + + for (var pix = 0; pix < endPixCheck; pix += 4) { + var currentColor = { + r: imageData.data[pix], + g: imageData.data[pix + 1], + b: imageData.data[pix + 2] + }; + var colorDifference = Math.sqrt( + Math.pow(currentColor.r - sampleColor.r, 2) + + Math.pow(currentColor.g - sampleColor.g, 2) + + Math.pow(currentColor.b - sampleColor.b, 2) + ); + // difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2) + if (colorDifference <= maxColorDifference * pixTolerance) { + matchPixCount++; + } + } + finishImage(); + }; + image.src = _frames[f].image; + } else { + finishImage(); + } + }, + callback: function() { + resultFrames = resultFrames.concat(_frames.slice(endCheckFrame)); + + if (resultFrames.length <= 0) { + // at least one last frame should be available for next manipulation + // if total duration of all frames will be < 1000 than ffmpeg doesn't work well... + resultFrames.push(_frames[_frames.length - 1]); + } + callback(resultFrames); + } + }); + } + + var isStopDrawing = false; + + /** + * This method stops recording video. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof WhammyRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + isStopDrawing = true; + + var _this = this; + // analyse of all frames takes some time! + setTimeout(function() { + // e.g. dropBlackFrames(frames, 10, 1, 1) - will cut all 10 frames + // e.g. dropBlackFrames(frames, 10, 0.5, 0.5) - will analyse 10 frames + // e.g. dropBlackFrames(frames, 10) === dropBlackFrames(frames, 10, 0, 0) - will analyse 10 frames with strict black color + dropBlackFrames(whammy.frames, -1, null, null, function(frames) { + whammy.frames = frames; + + // to display advertisement images! + if (config.advertisement && config.advertisement.length) { + whammy.frames = config.advertisement.concat(whammy.frames); + } + + /** + * @property {Blob} blob - Recorded frames in video/webm blob. + * @memberof WhammyRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + whammy.compile(function(blob) { + _this.blob = blob; + + if (_this.blob.forEach) { + _this.blob = new Blob([], { + type: 'video/webm' + }); + } + + if (callback) { + callback(_this.blob); + } + }); + }); + }, 10); + }; + + var isPausedRecording = false; + + /** + * This method pauses the recording process. + * @method + * @memberof WhammyRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPausedRecording = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof WhammyRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPausedRecording = false; + + if (isStopDrawing) { + this.record(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof WhammyRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (!isStopDrawing) { + this.stop(clearRecordedDataCB); + } + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + whammy.frames = []; + isStopDrawing = true; + isPausedRecording = false; + } + + // for debugging + this.name = 'WhammyRecorder'; + this.toString = function() { + return this.name; + }; + + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + + var video; + var lastTime; + var whammy; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.WhammyRecorder = WhammyRecorder; +} + +// https://github.com/antimatter15/whammy/blob/master/LICENSE +// _________ +// Whammy.js + +// todo: Firefox now supports webp for webm containers! +// their MediaRecorder implementation works well! +// should we provide an option to record via Whammy.js or MediaRecorder API is a better solution? + +/** + * Whammy is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It is written by {@link https://github.com/antimatter15|antimatter15} + * @summary A real time javascript webm encoder based on a canvas hack. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef Whammy + * @class + * @example + * var recorder = new Whammy().Video(15); + * recorder.add(context || canvas || dataURL); + * var output = recorder.compile(); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ + +var Whammy = (function() { + // a more abstract-ish API + + function WhammyVideo(duration) { + this.frames = []; + this.duration = duration || 1; + this.quality = 0.8; + } + + /** + * Pass Canvas or Context or image/webp(string) to {@link Whammy} encoder. + * @method + * @memberof Whammy + * @example + * recorder = new Whammy().Video(0.8, 100); + * recorder.add(canvas || context || 'image/webp'); + * @param {string} frame - Canvas || Context || image/webp + * @param {number} duration - Stick a duration (in milliseconds) + */ + WhammyVideo.prototype.add = function(frame, duration) { + if ('canvas' in frame) { //CanvasRenderingContext2D + frame = frame.canvas; + } + + if ('toDataURL' in frame) { + frame = frame.toDataURL('image/webp', this.quality); + } + + if (!(/^data:image\/webp;base64,/ig).test(frame)) { + throw 'Input must be formatted properly as a base64 encoded DataURI of type image/webp'; + } + this.frames.push({ + image: frame, + duration: duration || this.duration + }); + }; + + function processInWebWorker(_function) { + var blob = URL.createObjectURL(new Blob([_function.toString(), + 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(blob); + URL.revokeObjectURL(blob); + return worker; + } + + function whammyInWebWorker(frames) { + function ArrayToWebM(frames) { + var info = checkFrames(frames); + if (!info) { + return []; + } + + var clusterMaxDuration = 30000; + + var EBML = [{ + 'id': 0x1a45dfa3, // EBML + 'data': [{ + 'data': 1, + 'id': 0x4286 // EBMLVersion + }, { + 'data': 1, + 'id': 0x42f7 // EBMLReadVersion + }, { + 'data': 4, + 'id': 0x42f2 // EBMLMaxIDLength + }, { + 'data': 8, + 'id': 0x42f3 // EBMLMaxSizeLength + }, { + 'data': 'webm', + 'id': 0x4282 // DocType + }, { + 'data': 2, + 'id': 0x4287 // DocTypeVersion + }, { + 'data': 2, + 'id': 0x4285 // DocTypeReadVersion + }] + }, { + 'id': 0x18538067, // Segment + 'data': [{ + 'id': 0x1549a966, // Info + 'data': [{ + 'data': 1e6, //do things in millisecs (num of nanosecs for duration scale) + 'id': 0x2ad7b1 // TimecodeScale + }, { + 'data': 'whammy', + 'id': 0x4d80 // MuxingApp + }, { + 'data': 'whammy', + 'id': 0x5741 // WritingApp + }, { + 'data': doubleToString(info.duration), + 'id': 0x4489 // Duration + }] + }, { + 'id': 0x1654ae6b, // Tracks + 'data': [{ + 'id': 0xae, // TrackEntry + 'data': [{ + 'data': 1, + 'id': 0xd7 // TrackNumber + }, { + 'data': 1, + 'id': 0x73c5 // TrackUID + }, { + 'data': 0, + 'id': 0x9c // FlagLacing + }, { + 'data': 'und', + 'id': 0x22b59c // Language + }, { + 'data': 'V_VP8', + 'id': 0x86 // CodecID + }, { + 'data': 'VP8', + 'id': 0x258688 // CodecName + }, { + 'data': 1, + 'id': 0x83 // TrackType + }, { + 'id': 0xe0, // Video + 'data': [{ + 'data': info.width, + 'id': 0xb0 // PixelWidth + }, { + 'data': info.height, + 'id': 0xba // PixelHeight + }] + }] + }] + }] + }]; + + //Generate clusters (max duration) + var frameNumber = 0; + var clusterTimecode = 0; + while (frameNumber < frames.length) { + + var clusterFrames = []; + var clusterDuration = 0; + do { + clusterFrames.push(frames[frameNumber]); + clusterDuration += frames[frameNumber].duration; + frameNumber++; + } while (frameNumber < frames.length && clusterDuration < clusterMaxDuration); + + var clusterCounter = 0; + var cluster = { + 'id': 0x1f43b675, // Cluster + 'data': getClusterData(clusterTimecode, clusterCounter, clusterFrames) + }; //Add cluster to segment + EBML[1].data.push(cluster); + clusterTimecode += clusterDuration; + } + + return generateEBML(EBML); + } + + function getClusterData(clusterTimecode, clusterCounter, clusterFrames) { + return [{ + 'data': clusterTimecode, + 'id': 0xe7 // Timecode + }].concat(clusterFrames.map(function(webp) { + var block = makeSimpleBlock({ + discardable: 0, + frame: webp.data.slice(4), + invisible: 0, + keyframe: 1, + lacing: 0, + trackNum: 1, + timecode: Math.round(clusterCounter) + }); + clusterCounter += webp.duration; + return { + data: block, + id: 0xa3 + }; + })); + } + + // sums the lengths of all the frames and gets the duration + + function checkFrames(frames) { + if (!frames[0]) { + postMessage({ + error: 'Something went wrong. Maybe WebP format is not supported in the current browser.' + }); + return; + } + + var width = frames[0].width, + height = frames[0].height, + duration = frames[0].duration; + + for (var i = 1; i < frames.length; i++) { + duration += frames[i].duration; + } + return { + duration: duration, + width: width, + height: height + }; + } + + function numToBuffer(num) { + var parts = []; + while (num > 0) { + parts.push(num & 0xff); + num = num >> 8; + } + return new Uint8Array(parts.reverse()); + } + + function strToBuffer(str) { + return new Uint8Array(str.split('').map(function(e) { + return e.charCodeAt(0); + })); + } + + function bitsToBuffer(bits) { + var data = []; + var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; + bits = pad + bits; + for (var i = 0; i < bits.length; i += 8) { + data.push(parseInt(bits.substr(i, 8), 2)); + } + return new Uint8Array(data); + } + + function generateEBML(json) { + var ebml = []; + for (var i = 0; i < json.length; i++) { + var data = json[i].data; + + if (typeof data === 'object') { + data = generateEBML(data); + } + + if (typeof data === 'number') { + data = bitsToBuffer(data.toString(2)); + } + + if (typeof data === 'string') { + data = strToBuffer(data); + } + + var len = data.size || data.byteLength || data.length; + var zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8); + var sizeToString = len.toString(2); + var padded = (new Array((zeroes * 7 + 7 + 1) - sizeToString.length)).join('0') + sizeToString; + var size = (new Array(zeroes)).join('0') + '1' + padded; + + ebml.push(numToBuffer(json[i].id)); + ebml.push(bitsToBuffer(size)); + ebml.push(data); + } + + return new Blob(ebml, { + type: 'video/webm' + }); + } + + function toBinStrOld(bits) { + var data = ''; + var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; + bits = pad + bits; + for (var i = 0; i < bits.length; i += 8) { + data += String.fromCharCode(parseInt(bits.substr(i, 8), 2)); + } + return data; + } + + function makeSimpleBlock(data) { + var flags = 0; + + if (data.keyframe) { + flags |= 128; + } + + if (data.invisible) { + flags |= 8; + } + + if (data.lacing) { + flags |= (data.lacing << 1); + } + + if (data.discardable) { + flags |= 1; + } + + if (data.trackNum > 127) { + throw 'TrackNumber > 127 not supported'; + } + + var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e) { + return String.fromCharCode(e); + }).join('') + data.frame; + + return out; + } + + function parseWebP(riff) { + var VP8 = riff.RIFF[0].WEBP[0]; + + var frameStart = VP8.indexOf('\x9d\x01\x2a'); // A VP8 keyframe starts with the 0x9d012a header + for (var i = 0, c = []; i < 4; i++) { + c[i] = VP8.charCodeAt(frameStart + 3 + i); + } + + var width, height, tmp; + + //the code below is literally copied verbatim from the bitstream spec + tmp = (c[1] << 8) | c[0]; + width = tmp & 0x3FFF; + tmp = (c[3] << 8) | c[2]; + height = tmp & 0x3FFF; + return { + width: width, + height: height, + data: VP8, + riff: riff + }; + } + + function getStrLength(string, offset) { + return parseInt(string.substr(offset + 4, 4).split('').map(function(i) { + var unpadded = i.charCodeAt(0).toString(2); + return (new Array(8 - unpadded.length + 1)).join('0') + unpadded; + }).join(''), 2); + } + + function parseRIFF(string) { + var offset = 0; + var chunks = {}; + + while (offset < string.length) { + var id = string.substr(offset, 4); + var len = getStrLength(string, offset); + var data = string.substr(offset + 4 + 4, len); + offset += 4 + 4 + len; + chunks[id] = chunks[id] || []; + + if (id === 'RIFF' || id === 'LIST') { + chunks[id].push(parseRIFF(data)); + } else { + chunks[id].push(data); + } + } + return chunks; + } + + function doubleToString(num) { + return [].slice.call( + new Uint8Array((new Float64Array([num])).buffer), 0).map(function(e) { + return String.fromCharCode(e); + }).reverse().join(''); + } + + var webm = new ArrayToWebM(frames.map(function(frame) { + var webp = parseWebP(parseRIFF(atob(frame.image.slice(23)))); + webp.duration = frame.duration; + return webp; + })); + + postMessage(webm); + } + + /** + * Encodes frames in WebM container. It uses WebWorkinvoke to invoke 'ArrayToWebM' method. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof Whammy + * @example + * recorder = new Whammy().Video(0.8, 100); + * recorder.compile(function(blob) { + * // blob.size - blob.type + * }); + */ + WhammyVideo.prototype.compile = function(callback) { + var webWorker = processInWebWorker(whammyInWebWorker); + + webWorker.onmessage = function(event) { + if (event.data.error) { + console.error(event.data.error); + return; + } + callback(event.data); + }; + + webWorker.postMessage(this.frames); + }; + + return { + /** + * A more abstract-ish API. + * @method + * @memberof Whammy + * @example + * recorder = new Whammy().Video(0.8, 100); + * @param {?number} speed - 0.8 + * @param {?number} quality - 100 + */ + Video: WhammyVideo + }; +})(); + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.Whammy = Whammy; +} + +// ______________ (indexed-db) +// DiskStorage.js + +/** + * DiskStorage is a standalone object used by {@link RecordRTC} to store recorded blobs in IndexedDB storage. + * @summary Writing blobs into IndexedDB. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @example + * DiskStorage.Store({ + * audioBlob: yourAudioBlob, + * videoBlob: yourVideoBlob, + * gifBlob : yourGifBlob + * }); + * DiskStorage.Fetch(function(dataURL, type) { + * if(type === 'audioBlob') { } + * if(type === 'videoBlob') { } + * if(type === 'gifBlob') { } + * }); + * // DiskStorage.dataStoreName = 'recordRTC'; + * // DiskStorage.onError = function(error) { }; + * @property {function} init - This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. + * @property {function} Fetch - This method fetches stored blobs from IndexedDB. + * @property {function} Store - This method stores blobs in IndexedDB. + * @property {function} onError - This function is invoked for any known/unknown error. + * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ + + +var DiskStorage = { + /** + * This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.init(); + */ + init: function() { + var self = this; + + if (typeof indexedDB === 'undefined' || typeof indexedDB.open === 'undefined') { + console.error('IndexedDB API are not available in this browser.'); + return; + } + + var dbVersion = 1; + var dbName = this.dbName || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''), + db; + var request = indexedDB.open(dbName, dbVersion); + + function createObjectStore(dataBase) { + dataBase.createObjectStore(self.dataStoreName); + } + + function putInDB() { + var transaction = db.transaction([self.dataStoreName], 'readwrite'); + + if (self.videoBlob) { + transaction.objectStore(self.dataStoreName).put(self.videoBlob, 'videoBlob'); + } + + if (self.gifBlob) { + transaction.objectStore(self.dataStoreName).put(self.gifBlob, 'gifBlob'); + } + + if (self.audioBlob) { + transaction.objectStore(self.dataStoreName).put(self.audioBlob, 'audioBlob'); + } + + function getFromStore(portionName) { + transaction.objectStore(self.dataStoreName).get(portionName).onsuccess = function(event) { + if (self.callback) { + self.callback(event.target.result, portionName); + } + }; + } + + getFromStore('audioBlob'); + getFromStore('videoBlob'); + getFromStore('gifBlob'); + } + + request.onerror = self.onError; + + request.onsuccess = function() { + db = request.result; + db.onerror = self.onError; + + if (db.setVersion) { + if (db.version !== dbVersion) { + var setVersion = db.setVersion(dbVersion); + setVersion.onsuccess = function() { + createObjectStore(db); + putInDB(); + }; + } else { + putInDB(); + } + } else { + putInDB(); + } + }; + request.onupgradeneeded = function(event) { + createObjectStore(event.target.result); + }; + }, + /** + * This method fetches stored blobs from IndexedDB. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.Fetch(function(dataURL, type) { + * if(type === 'audioBlob') { } + * if(type === 'videoBlob') { } + * if(type === 'gifBlob') { } + * }); + */ + Fetch: function(callback) { + this.callback = callback; + this.init(); + + return this; + }, + /** + * This method stores blobs in IndexedDB. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.Store({ + * audioBlob: yourAudioBlob, + * videoBlob: yourVideoBlob, + * gifBlob : yourGifBlob + * }); + */ + Store: function(config) { + this.audioBlob = config.audioBlob; + this.videoBlob = config.videoBlob; + this.gifBlob = config.gifBlob; + + this.init(); + + return this; + }, + /** + * This function is invoked for any known/unknown error. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.onError = function(error){ + * alerot( JSON.stringify(error) ); + * }; + */ + onError: function(error) { + console.error(JSON.stringify(error, null, '\t')); + }, + + /** + * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.dataStoreName = 'recordRTC'; + */ + dataStoreName: 'recordRTC', + dbName: null +}; + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.DiskStorage = DiskStorage; +} + +// ______________ +// GifRecorder.js + +/** + * GifRecorder is standalone calss used by {@link RecordRTC} to record video or canvas into animated gif. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef GifRecorder + * @class + * @example + * var recorder = new GifRecorder(mediaStream || canvas || context, { onGifPreview: function, onGifRecordingStarted: function, width: 1280, height: 720, frameRate: 200, quality: 10 }); + * recorder.record(); + * recorder.stop(function(blob) { + * img.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object or HTMLCanvasElement or CanvasRenderingContext2D. + * @param {object} config - {disableLogs:true, initCallback: function, width: 320, height: 240, frameRate: 200, quality: 10} + */ + +function GifRecorder(mediaStream, config) { + if (typeof GIFEncoder === 'undefined') { + var script = document.createElement('script'); + script.src = 'https://cdn.webrtc-experiment.com/gif-recorder.js'; + (document.body || document.documentElement).appendChild(script); + } + + config = config || {}; + + var isHTMLObject = mediaStream instanceof CanvasRenderingContext2D || mediaStream instanceof HTMLCanvasElement; + + /** + * This method records MediaStream. + * @method + * @memberof GifRecorder + * @example + * recorder.record(); + */ + this.record = function() { + if (typeof GIFEncoder === 'undefined') { + setTimeout(self.record, 1000); + return; + } + + if (!isLoadedMetaData) { + setTimeout(self.record, 1000); + return; + } + + if (!isHTMLObject) { + if (!config.width) { + config.width = video.offsetWidth || 320; + } + + if (!config.height) { + config.height = video.offsetHeight || 240; + } + + if (!config.video) { + config.video = { + width: config.width, + height: config.height + }; + } + + if (!config.canvas) { + config.canvas = { + width: config.width, + height: config.height + }; + } + + canvas.width = config.canvas.width || 320; + canvas.height = config.canvas.height || 240; + + video.width = config.video.width || 320; + video.height = config.video.height || 240; + } + + // external library to record as GIF images + gifEncoder = new GIFEncoder(); + + // void setRepeat(int iter) + // Sets the number of times the set of GIF frames should be played. + // Default is 1; 0 means play indefinitely. + gifEncoder.setRepeat(0); + + // void setFrameRate(Number fps) + // Sets frame rate in frames per second. + // Equivalent to setDelay(1000/fps). + // Using "setDelay" instead of "setFrameRate" + gifEncoder.setDelay(config.frameRate || 200); + + // void setQuality(int quality) + // Sets quality of color quantization (conversion of images to the + // maximum 256 colors allowed by the GIF specification). + // Lower values (minimum = 1) produce better colors, + // but slow processing significantly. 10 is the default, + // and produces good color mapping at reasonable speeds. + // Values greater than 20 do not yield significant improvements in speed. + gifEncoder.setQuality(config.quality || 10); + + // Boolean start() + // This writes the GIF Header and returns false if it fails. + gifEncoder.start(); + + if (typeof config.onGifRecordingStarted === 'function') { + config.onGifRecordingStarted(); + } + + startTime = Date.now(); + + function drawVideoFrame(time) { + if (self.clearedRecordedData === true) { + return; + } + + if (isPausedRecording) { + return setTimeout(function() { + drawVideoFrame(time); + }, 100); + } + + lastAnimationFrame = requestAnimationFrame(drawVideoFrame); + + if (typeof lastFrameTime === undefined) { + lastFrameTime = time; + } + + // ~10 fps + if (time - lastFrameTime < 90) { + return; + } + + if (!isHTMLObject && video.paused) { + // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 + // Tweak for Android Chrome + video.play(); + } + + if (!isHTMLObject) { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + } + + if (config.onGifPreview) { + config.onGifPreview(canvas.toDataURL('image/png')); + } + + gifEncoder.addFrame(context); + lastFrameTime = time; + } + + lastAnimationFrame = requestAnimationFrame(drawVideoFrame); + + if (config.initCallback) { + config.initCallback(); + } + }; + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof GifRecorder + * @example + * recorder.stop(function(blob) { + * img.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + if (lastAnimationFrame) { + cancelAnimationFrame(lastAnimationFrame); + } + + endTime = Date.now(); + + /** + * @property {Blob} blob - The recorded blob object. + * @memberof GifRecorder + * @example + * recorder.stop(function(){ + * var blob = recorder.blob; + * }); + */ + this.blob = new Blob([new Uint8Array(gifEncoder.stream().bin)], { + type: 'image/gif' + }); + + callback(this.blob); + + // bug: find a way to clear old recorded blobs + gifEncoder.stream().bin = []; + }; + + var isPausedRecording = false; + + /** + * This method pauses the recording process. + * @method + * @memberof GifRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPausedRecording = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof GifRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPausedRecording = false; + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof GifRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + self.clearedRecordedData = true; + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + if (gifEncoder) { + gifEncoder.stream().bin = []; + } + } + + // for debugging + this.name = 'GifRecorder'; + this.toString = function() { + return this.name; + }; + + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + + if (isHTMLObject) { + if (mediaStream instanceof CanvasRenderingContext2D) { + context = mediaStream; + canvas = context.canvas; + } else if (mediaStream instanceof HTMLCanvasElement) { + context = mediaStream.getContext('2d'); + canvas = mediaStream; + } + } + + var isLoadedMetaData = true; + + if (!isHTMLObject) { + var video = document.createElement('video'); + video.muted = true; + video.autoplay = true; + + isLoadedMetaData = false; + video.onloadedmetadata = function() { + isLoadedMetaData = true; + }; + + setSrcObject(mediaStream, video); + + video.play(); + } + + var lastAnimationFrame = null; + var startTime, endTime, lastFrameTime; + + var gifEncoder; + + var self = this; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.GifRecorder = GifRecorder; +} + +// Last time updated: 2018-12-22 9:13:29 AM UTC + +// ________________________ +// MultiStreamsMixer v1.0.7 + +// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +function MultiStreamsMixer(arrayOfMediaStreams) { + + // requires: chrome://flags/#enable-experimental-web-platform-features + + var videos = []; + var isStopDrawingFrames = false; + + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + canvas.style = 'opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;'; + (document.body || document.documentElement).appendChild(canvas); + + this.disableLogs = false; + this.frameInterval = 10; + + this.width = 360; + this.height = 240; + + // use gain node to prevent echo + this.useGainNode = true; + + var self = this; + + // _____________________________ + // Cross-Browser-Declarations.js + + // WebAudio API representer + var AudioContext = window.AudioContext; + + if (typeof AudioContext === 'undefined') { + if (typeof webkitAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = webkitAudioContext; + } + + if (typeof mozAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = mozAudioContext; + } + } + + /*jshint -W079 */ + var URL = window.URL; + + if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { + /*global URL:true */ + URL = webkitURL; + } + + if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? + if (typeof navigator.webkitGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + + if (typeof navigator.mozGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.mozGetUserMedia; + } + } + + var MediaStream = window.MediaStream; + + if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { + MediaStream = webkitMediaStream; + } + + /*global MediaStream:true */ + if (typeof MediaStream !== 'undefined') { + // override "stop" method for all browsers + if (typeof MediaStream.prototype.stop === 'undefined') { + MediaStream.prototype.stop = function() { + this.getTracks().forEach(function(track) { + track.stop(); + }); + }; + } + } + + var Storage = {}; + + if (typeof AudioContext !== 'undefined') { + Storage.AudioContext = AudioContext; + } else if (typeof webkitAudioContext !== 'undefined') { + Storage.AudioContext = webkitAudioContext; + } + + function setSrcObject(stream, element) { + if ('srcObject' in element) { + element.srcObject = stream; + } else if ('mozSrcObject' in element) { + element.mozSrcObject = stream; + } else { + element.srcObject = stream; + } + } + + this.startDrawingFrames = function() { + drawVideosToCanvas(); + }; + + function drawVideosToCanvas() { + if (isStopDrawingFrames) { + return; + } + + var videosLength = videos.length; + + var fullcanvas = false; + var remaining = []; + videos.forEach(function(video) { + if (!video.stream) { + video.stream = {}; + } + + if (video.stream.fullcanvas) { + fullcanvas = video; + } else { + remaining.push(video); + } + }); + + if (fullcanvas) { + canvas.width = fullcanvas.stream.width; + canvas.height = fullcanvas.stream.height; + } else if (remaining.length) { + canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width; + + var height = 1; + if (videosLength === 3 || videosLength === 4) { + height = 2; + } + if (videosLength === 5 || videosLength === 6) { + height = 3; + } + if (videosLength === 7 || videosLength === 8) { + height = 4; + } + if (videosLength === 9 || videosLength === 10) { + height = 5; + } + canvas.height = remaining[0].height * height; + } else { + canvas.width = self.width || 360; + canvas.height = self.height || 240; + } + + if (fullcanvas && fullcanvas instanceof HTMLVideoElement) { + drawImage(fullcanvas); + } + + remaining.forEach(function(video, idx) { + drawImage(video, idx); + }); + + setTimeout(drawVideosToCanvas, self.frameInterval); + } + + function drawImage(video, idx) { + if (isStopDrawingFrames) { + return; + } + + var x = 0; + var y = 0; + var width = video.width; + var height = video.height; + + if (idx === 1) { + x = video.width; + } + + if (idx === 2) { + y = video.height; + } + + if (idx === 3) { + x = video.width; + y = video.height; + } + + if (idx === 4) { + y = video.height * 2; + } + + if (idx === 5) { + x = video.width; + y = video.height * 2; + } + + if (idx === 6) { + y = video.height * 3; + } + + if (idx === 7) { + x = video.width; + y = video.height * 3; + } + + if (typeof video.stream.left !== 'undefined') { + x = video.stream.left; + } + + if (typeof video.stream.top !== 'undefined') { + y = video.stream.top; + } + + if (typeof video.stream.width !== 'undefined') { + width = video.stream.width; + } + + if (typeof video.stream.height !== 'undefined') { + height = video.stream.height; + } + + context.drawImage(video, x, y, width, height); + + if (typeof video.stream.onRender === 'function') { + video.stream.onRender(context, x, y, width, height, idx); + } + } + + function getMixedStream() { + isStopDrawingFrames = false; + var mixedVideoStream = getMixedVideoStream(); + + var mixedAudioStream = getMixedAudioStream(); + if (mixedAudioStream) { + mixedAudioStream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).forEach(function(track) { + mixedVideoStream.addTrack(track); + }); + } + + var fullcanvas; + arrayOfMediaStreams.forEach(function(stream) { + if (stream.fullcanvas) { + fullcanvas = true; + } + }); + + return mixedVideoStream; + } + + function getMixedVideoStream() { + resetVideoStreams(); + + var capturedStream; + + if ('captureStream' in canvas) { + capturedStream = canvas.captureStream(); + } else if ('mozCaptureStream' in canvas) { + capturedStream = canvas.mozCaptureStream(); + } else if (!self.disableLogs) { + console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features'); + } + + var videoStream = new MediaStream(); + + capturedStream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).forEach(function(track) { + videoStream.addTrack(track); + }); + + canvas.stream = videoStream; + + return videoStream; + } + + function getMixedAudioStream() { + // via: @pehrsons + if (!Storage.AudioContextConstructor) { + Storage.AudioContextConstructor = new Storage.AudioContext(); + } + + self.audioContext = Storage.AudioContextConstructor; + + self.audioSources = []; + + if (self.useGainNode === true) { + self.gainNode = self.audioContext.createGain(); + self.gainNode.connect(self.audioContext.destination); + self.gainNode.gain.value = 0; // don't hear self + } + + var audioTracksLength = 0; + arrayOfMediaStreams.forEach(function(stream) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length) { + return; + } + + audioTracksLength++; + + var audioSource = self.audioContext.createMediaStreamSource(stream); + + if (self.useGainNode === true) { + audioSource.connect(self.gainNode); + } + + self.audioSources.push(audioSource); + }); + + if (!audioTracksLength) { + return; + } + + self.audioDestination = self.audioContext.createMediaStreamDestination(); + self.audioSources.forEach(function(audioSource) { + audioSource.connect(self.audioDestination); + }); + return self.audioDestination.stream; + } + + function getVideo(stream) { + var video = document.createElement('video'); + + setSrcObject(stream, video); + + video.muted = true; + video.volume = 0; + + video.width = stream.width || self.width || 360; + video.height = stream.height || self.height || 240; + + video.play(); + + return video; + } + + this.appendStreams = function(streams) { + if (!streams) { + throw 'First parameter is required.'; + } + + if (!(streams instanceof Array)) { + streams = [streams]; + } + + arrayOfMediaStreams.concat(streams); + + streams.forEach(function(stream) { + if (stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { + var video = getVideo(stream); + video.stream = stream; + videos.push(video); + } + + if (stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length && self.audioContext) { + var audioSource = self.audioContext.createMediaStreamSource(stream); + audioSource.connect(self.audioDestination); + self.audioSources.push(audioSource); + } + }); + }; + + this.releaseStreams = function() { + videos = []; + isStopDrawingFrames = true; + + if (self.gainNode) { + self.gainNode.disconnect(); + self.gainNode = null; + } + + if (self.audioSources.length) { + self.audioSources.forEach(function(source) { + source.disconnect(); + }); + self.audioSources = []; + } + + if (self.audioDestination) { + self.audioDestination.disconnect(); + self.audioDestination = null; + } + + if (self.audioContext) { + self.audioContext.close(); + } + + self.audioContext = null; + + context.clearRect(0, 0, canvas.width, canvas.height); + + if (canvas.stream) { + canvas.stream.stop(); + canvas.stream = null; + } + }; + + this.resetVideoStreams = function(streams) { + if (streams && !(streams instanceof Array)) { + streams = [streams]; + } + + resetVideoStreams(streams); + }; + + function resetVideoStreams(streams) { + videos = []; + streams = streams || arrayOfMediaStreams; + + // via: @adrian-ber + streams.forEach(function(stream) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { + return; + } + + var video = getVideo(stream); + video.stream = stream; + videos.push(video); + }); + } + + // for debugging + this.name = 'MultiStreamsMixer'; + this.toString = function() { + return this.name; + }; + + this.getMixedStream = getMixedStream; + +} + +// ______________________ +// MultiStreamRecorder.js + +/* + * Video conference recording, using captureStream API along with WebAudio and Canvas2D API. + */ + +/** + * MultiStreamRecorder can record multiple videos in single container. + * @summary Multi-videos recorder. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef MultiStreamRecorder + * @class + * @example + * var options = { + * mimeType: 'video/webm' + * } + * var recorder = new MultiStreamRecorder(ArrayOfMediaStreams, options); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * + * // or + * var blob = recorder.blob; + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStreams} mediaStreams - Array of MediaStreams. + * @param {object} config - {disableLogs:true, frameInterval: 1, mimeType: "video/webm"} + */ + +function MultiStreamRecorder(arrayOfMediaStreams, options) { + arrayOfMediaStreams = arrayOfMediaStreams || []; + var self = this; + + var mixer; + var mediaRecorder; + + options = options || { + mimeType: 'video/webm', + video: { + width: 360, + height: 240 + } + }; + + if (!options.frameInterval) { + options.frameInterval = 10; + } + + if (!options.video) { + options.video = {}; + } + + if (!options.video.width) { + options.video.width = 360; + } + + if (!options.video.height) { + options.video.height = 240; + } + + /** + * This method records all MediaStreams. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.record(); + */ + this.record = function() { + // github/muaz-khan/MultiStreamsMixer + mixer = new MultiStreamsMixer(arrayOfMediaStreams); + + if (getAllVideoTracks().length) { + mixer.frameInterval = options.frameInterval || 10; + mixer.width = options.video.width || 360; + mixer.height = options.video.height || 240; + mixer.startDrawingFrames(); + } + + if (options.previewStream && typeof options.previewStream === 'function') { + options.previewStream(mixer.getMixedStream()); + } + + // record using MediaRecorder API + mediaRecorder = new MediaStreamRecorder(mixer.getMixedStream(), options); + mediaRecorder.record(); + }; + + function getAllVideoTracks() { + var tracks = []; + arrayOfMediaStreams.forEach(function(stream) { + getTracks(stream, 'video').forEach(function(track) { + tracks.push(track); + }); + }); + return tracks; + } + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + if (!mediaRecorder) { + return; + } + + mediaRecorder.stop(function(blob) { + self.blob = blob; + + callback(blob); + + self.clearRecordedData(); + }); + }; + + /** + * This method pauses the recording process. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + if (mediaRecorder) { + mediaRecorder.pause(); + } + }; + + /** + * This method resumes the recording process. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + if (mediaRecorder) { + mediaRecorder.resume(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (mediaRecorder) { + mediaRecorder.clearRecordedData(); + mediaRecorder = null; + } + + if (mixer) { + mixer.releaseStreams(); + mixer = null; + } + }; + + /** + * Add extra media-streams to existing recordings. + * @method + * @memberof MultiStreamRecorder + * @param {MediaStreams} mediaStreams - Array of MediaStreams + * @example + * recorder.addStreams([newAudioStream, newVideoStream]); + */ + this.addStreams = function(streams) { + if (!streams) { + throw 'First parameter is required.'; + } + + if (!(streams instanceof Array)) { + streams = [streams]; + } + + arrayOfMediaStreams.concat(streams); + + if (!mediaRecorder || !mixer) { + return; + } + + mixer.appendStreams(streams); + }; + + /** + * Reset videos during live recording. Replace old videos e.g. replace cameras with full-screen. + * @method + * @memberof MultiStreamRecorder + * @param {MediaStreams} mediaStreams - Array of MediaStreams + * @example + * recorder.resetVideoStreams([newVideo1, newVideo2]); + */ + this.resetVideoStreams = function(streams) { + if (!mixer) { + return; + } + + if (streams && !(streams instanceof Array)) { + streams = [streams]; + } + + mixer.resetVideoStreams(streams); + }; + + // for debugging + this.name = 'MultiStreamRecorder'; + this.toString = function() { + return this.name; + }; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.MultiStreamRecorder = MultiStreamRecorder; +} + +// _____________________ +// RecordRTC.promises.js + +/** + * RecordRTCPromisesHandler adds promises support in {@link RecordRTC}. Try a {@link https://github.com/muaz-khan/RecordRTC/blob/master/simple-demos/RecordRTCPromisesHandler.html|demo here} + * @summary Promises for {@link RecordRTC} + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef RecordRTCPromisesHandler + * @class + * @example + * var recorder = new RecordRTCPromisesHandler(mediaStream, options); + * recorder.startRecording() + * .then(successCB) + * .catch(errorCB); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. + * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} + * @throws Will throw an error if "new" keyword is not used to initiate "RecordRTCPromisesHandler". Also throws error if first argument "MediaStream" is missing. + * @requires {@link RecordRTC} + */ + +function RecordRTCPromisesHandler(mediaStream, options) { + if (!this) { + throw 'Use "new RecordRTCPromisesHandler()"'; + } + + if (typeof mediaStream === 'undefined') { + throw 'First argument "MediaStream" is required.'; + } + + var self = this; + + /** + * @property {Blob} blob - Access/reach the native {@link RecordRTC} object. + * @memberof RecordRTCPromisesHandler + * @example + * var internal = recorder.recordRTC.getInternalRecorder(); + * alert(internal instanceof MediaStreamRecorder); + */ + self.recordRTC = new RecordRTC(mediaStream, options); + + /** + * This method records MediaStream. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.startRecording() + * .then(successCB) + * .catch(errorCB); + */ + this.startRecording = function() { + return new Promise(function(resolve, reject) { + try { + self.recordRTC.startRecording(); + resolve(); + } catch (e) { + reject(e); + } + }); + }; + + /** + * This method stops the recording. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * var blob = recorder.getBlob(); + * }).catch(errorCB); + */ + this.stopRecording = function() { + return new Promise(function(resolve, reject) { + try { + self.recordRTC.stopRecording(function(url) { + self.blob = self.recordRTC.getBlob(); + + if (!self.blob || !self.blob.size) { + reject('Empty blob.', self.blob); + return; + } + + resolve(url); + }); + } catch (e) { + reject(e); + } + }); + }; + + /** + * This method returns data-url for the recorded blob. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * recorder.getDataURL().then(function(dataURL) { + * window.open(dataURL); + * }).catch(errorCB);; + * }).catch(errorCB); + */ + this.getDataURL = function(callback) { + return new Promise(function(resolve, reject) { + try { + self.recordRTC.getDataURL(function(dataURL) { + resolve(dataURL); + }); + } catch (e) { + reject(e); + } + }); + }; + + /** + * This method returns the recorded blob. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * recorder.getBlob().then(function(blob) {}) + * }).catch(errorCB); + */ + this.getBlob = function() { + return new Promise(function(resolve, reject) { + try { + resolve(self.recordRTC.getBlob()); + } catch (e) { + reject(e); + } + }); + }; + + /** + * @property {Blob} blob - Recorded data as "Blob" object. + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * var blob = recorder.getBlob(); + * }).catch(errorCB); + */ + this.blob = null; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.RecordRTCPromisesHandler = RecordRTCPromisesHandler; +} + +// ______________________ +// WebAssemblyRecorder.js + +/** + * WebAssemblyRecorder lets you create webm videos in JavaScript via WebAssembly. The library consumes raw RGBA32 buffers (4 bytes per pixel) and turns them into a webm video with the given framerate and quality. This makes it compatible out-of-the-box with ImageData from a CANVAS. With realtime mode you can also use webm-wasm for streaming webm videos. + * @summary Video recording feature in Chrome, Firefox and maybe Edge. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef WebAssemblyRecorder + * @class + * @example + * var recorder = new WebAssemblyRecorder(mediaStream); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {webAssemblyPath:'webm-wasm.wasm',workerPath: 'webm-worker.js', frameRate: 30, width: 1920, height: 1080, bitrate: 1024} + */ +function WebAssemblyRecorder(stream, config) { + // based on: github.com/GoogleChromeLabs/webm-wasm + + if (typeof ReadableStream === 'undefined' || typeof WritableStream === 'undefined') { + // because it fixes readable/writable streams issues + console.error('Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js'); + } + + config = config || {}; + + config.width = config.width || 640; + config.height = config.height || 480; + config.frameRate = config.frameRate || 30; + config.bitrate = config.bitrate || 1200; + + function createBufferURL(buffer, type) { + return URL.createObjectURL(new Blob([buffer], { + type: type || '' + })); + } + + function cameraStream() { + return new ReadableStream({ + start: function(controller) { + var cvs = document.createElement('canvas'); + var video = document.createElement('video'); + video.srcObject = stream; + video.onplaying = function() { + cvs.width = config.width; + cvs.height = config.height; + var ctx = cvs.getContext('2d'); + var frameTimeout = 1000 / config.frameRate; + setTimeout(function f() { + ctx.drawImage(video, 0, 0); + controller.enqueue( + ctx.getImageData(0, 0, config.width, config.height) + ); + setTimeout(f, frameTimeout); + }, frameTimeout); + }; + video.play(); + } + }); + } + + var worker; + + function startRecording(stream, buffer) { + if (!config.workerPath && !buffer) { + // is it safe to use @latest ? + fetch( + 'https://unpkg.com/webm-wasm@latest/dist/webm-worker.js' + ).then(function(r) { + r.arrayBuffer().then(function(buffer) { + startRecording(stream, buffer); + }); + }); + return; + } + + if (!config.workerPath && buffer instanceof ArrayBuffer) { + var blob = new Blob([buffer], { + type: 'text/javascript' + }); + config.workerPath = URL.createObjectURL(blob); + } + + if (!config.workerPath) { + console.error('workerPath parameter is missing.'); + } + + worker = new Worker(config.workerPath); + + worker.postMessage(config.webAssemblyPath || 'https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm'); + worker.addEventListener('message', function(event) { + if (event.data === 'READY') { + worker.postMessage({ + width: config.width, + height: config.height, + bitrate: config.bitrate || 1200, + timebaseDen: config.frameRate || 30, + realtime: true + }); + + cameraStream().pipeTo(new WritableStream({ + write: function(image) { + if (!worker) { + return; + } + + worker.postMessage(image.data.buffer, [image.data.buffer]); + } + })); + } else if (!!event.data) { + if (!isPaused) { + arrayOfBuffers.push(event.data); + } + } + }); + } + + /** + * This method records video. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.record(); + */ + this.record = function() { + arrayOfBuffers = []; + isPaused = false; + this.blob = null; + startRecording(stream); + + if (typeof config.initCallback === 'function') { + config.initCallback(); + } + }; + + var isPaused; + + /** + * This method pauses the recording process. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPaused = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPaused = false; + }; + + function terminate() { + if (!worker) { + return; + } + + worker.postMessage(null); + worker.terminate(); + worker = null; + } + + var arrayOfBuffers = []; + + /** + * This method stops recording video. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + terminate(); + + this.blob = new Blob(arrayOfBuffers, { + type: 'video/webm' + }); + + callback(this.blob); + }; + + // for debugging + this.name = 'WebAssemblyRecorder'; + this.toString = function() { + return this.name; + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + arrayOfBuffers = []; + isPaused = false; + this.blob = null; + + // todo: if recording-ON then STOP it first + }; + + /** + * @property {Blob} blob - The recorded blob object. + * @memberof WebAssemblyRecorder + * @example + * recorder.stop(function(){ + * var blob = recorder.blob; + * }); + */ + this.blob = null; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.WebAssemblyRecorder = WebAssemblyRecorder; +} diff --git a/src/libkysdk-system-javascript-http/html/js/config-manager.js b/src/libkysdk-system-javascript-http/html/js/config-manager.js new file mode 100755 index 0000000..3fe7ef3 --- /dev/null +++ b/src/libkysdk-system-javascript-http/html/js/config-manager.js @@ -0,0 +1,109 @@ +{ +output=function(message){ + var output = document.getElementById("StateResult"); + output.innerHTML = output.innerHTML + message + "\n"; +}, +getJsonFromInfo=function(info){ + if(info == ""){ + return "{}"; + } + var info_list = info.split("&"); + var info_json = "{"; + for(var i=0; i)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="
t
",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj; +return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="
",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&>(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:b.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l) +}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b(" + + + diff --git a/src/libkysdk-system-javascript-websocket/html/index_page.html b/src/libkysdk-system-javascript-websocket/html/index_page.html new file mode 100644 index 0000000..25ec500 --- /dev/null +++ b/src/libkysdk-system-javascript-websocket/html/index_page.html @@ -0,0 +1,33 @@ + + + + + + SelfCheck + + + + + + + + + + + +
+ + +
+

Loading...

+ +
+ +
+ + + + + diff --git a/src/libkysdk-system-javascript-websocket/html/js/RecordRTC.js b/src/libkysdk-system-javascript-websocket/html/js/RecordRTC.js new file mode 100755 index 0000000..1bccef5 --- /dev/null +++ b/src/libkysdk-system-javascript-websocket/html/js/RecordRTC.js @@ -0,0 +1,5856 @@ +'use strict'; + +// Last time updated: 2019-02-06 11:32:40 AM UTC + +// ________________ +// RecordRTC v5.5.4 + +// Open-Sourced: https://github.com/muaz-khan/RecordRTC + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +// ____________ +// RecordRTC.js + +/** + * {@link https://github.com/muaz-khan/RecordRTC|RecordRTC} is a WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows. + * @summary Record audio, video or screen inside the browser. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef RecordRTC + * @class + * @example + * var recorder = RecordRTC(mediaStream or [arrayOfMediaStream], { + * type: 'video', // audio or video or gif or canvas + * recorderType: MediaStreamRecorder || CanvasRecorder || StereoAudioRecorder || Etc + * }); + * recorder.startRecording(); + * @see For further information: + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. + * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, desiredSampRate: 16000, video: HTMLVideoElement, etc.} + */ + +function RecordRTC(mediaStream, config) { + if (!mediaStream) { + throw 'First parameter is required.'; + } + + config = config || { + type: 'video' + }; + + config = new RecordRTCConfiguration(mediaStream, config); + + // a reference to user's recordRTC object + var self = this; + + function startRecording(config2) { + if (!!config2) { + // allow users to set options using startRecording method + // config2 is similar to main "config" object (second parameter over RecordRTC constructor) + config = new RecordRTCConfiguration(mediaStream, config2); + } + + if (!config.disableLogs) { + console.log('started recording ' + config.type + ' stream.'); + } + + if (mediaRecorder) { + mediaRecorder.clearRecordedData(); + mediaRecorder.record(); + + setState('recording'); + + if (self.recordingDuration) { + handleRecordingDuration(); + } + return self; + } + + initRecorder(function() { + if (self.recordingDuration) { + handleRecordingDuration(); + } + }); + + return self; + } + + function initRecorder(initCallback) { + if (initCallback) { + config.initCallback = function() { + initCallback(); + initCallback = config.initCallback = null; // recorder.initRecorder should be call-backed once. + }; + } + + var Recorder = new GetRecorderType(mediaStream, config); + + mediaRecorder = new Recorder(mediaStream, config); + mediaRecorder.record(); + + setState('recording'); + + if (!config.disableLogs) { + console.log('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type); + } + } + + function stopRecording(callback) { + callback = callback || function() {}; + + if (!mediaRecorder) { + warningLog(); + return; + } + + if (self.state === 'paused') { + self.resumeRecording(); + + setTimeout(function() { + stopRecording(callback); + }, 1); + return; + } + + if (self.state !== 'recording' && !config.disableLogs) { + console.warn('Recording state should be: "recording", however current state is: ', self.state); + } + + if (!config.disableLogs) { + console.log('Stopped recording ' + config.type + ' stream.'); + } + + if (config.type !== 'gif') { + mediaRecorder.stop(_callback); + } else { + mediaRecorder.stop(); + _callback(); + } + + setState('stopped'); + + function _callback(__blob) { + if (!mediaRecorder) { + if (typeof callback.call === 'function') { + callback.call(self, ''); + } else { + callback(''); + } + return; + } + + Object.keys(mediaRecorder).forEach(function(key) { + if (typeof mediaRecorder[key] === 'function') { + return; + } + + self[key] = mediaRecorder[key]; + }); + + var blob = mediaRecorder.blob; + + if (!blob) { + if (__blob) { + mediaRecorder.blob = blob = __blob; + } else { + throw 'Recording failed.'; + } + } + + if (blob && !config.disableLogs) { + console.log(blob.type, '->', bytesToSize(blob.size)); + } + + if (callback) { + var url; + + try { + url = URL.createObjectURL(blob); + } catch (e) {} + + if (typeof callback.call === 'function') { + callback.call(self, url); + } else { + callback(url); + } + } + + if (!config.autoWriteToDisk) { + return; + } + + getDataURL(function(dataURL) { + var parameter = {}; + parameter[config.type + 'Blob'] = dataURL; + DiskStorage.Store(parameter); + }); + } + } + + function pauseRecording() { + if (!mediaRecorder) { + warningLog(); + return; + } + + if (self.state !== 'recording') { + if (!config.disableLogs) { + console.warn('Unable to pause the recording. Recording state: ', self.state); + } + return; + } + + setState('paused'); + + mediaRecorder.pause(); + + if (!config.disableLogs) { + console.log('Paused recording.'); + } + } + + function resumeRecording() { + if (!mediaRecorder) { + warningLog(); + return; + } + + if (self.state !== 'paused') { + if (!config.disableLogs) { + console.warn('Unable to resume the recording. Recording state: ', self.state); + } + return; + } + + setState('recording'); + + // not all libs have this method yet + mediaRecorder.resume(); + + if (!config.disableLogs) { + console.log('Resumed recording.'); + } + } + + function readFile(_blob) { + postMessage(new FileReaderSync().readAsDataURL(_blob)); + } + + function getDataURL(callback, _mediaRecorder) { + if (!callback) { + throw 'Pass a callback function over getDataURL.'; + } + + var blob = _mediaRecorder ? _mediaRecorder.blob : (mediaRecorder || {}).blob; + + if (!blob) { + if (!config.disableLogs) { + console.warn('Blob encoder did not finish its job yet.'); + } + + setTimeout(function() { + getDataURL(callback, _mediaRecorder); + }, 1000); + return; + } + + if (typeof Worker !== 'undefined' && !navigator.mozGetUserMedia) { + var webWorker = processInWebWorker(readFile); + + webWorker.onmessage = function(event) { + callback(event.data); + }; + + webWorker.postMessage(blob); + } else { + var reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onload = function(event) { + callback(event.target.result); + }; + } + + function processInWebWorker(_function) { + try { + var blob = URL.createObjectURL(new Blob([_function.toString(), + 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(blob); + URL.revokeObjectURL(blob); + return worker; + } catch (e) {} + } + } + + function handleRecordingDuration(counter) { + counter = counter || 0; + + if (self.state === 'paused') { + setTimeout(function() { + handleRecordingDuration(counter); + }, 1000); + return; + } + + if (self.state === 'stopped') { + return; + } + + if (counter >= self.recordingDuration) { + stopRecording(self.onRecordingStopped); + return; + } + + counter += 1000; // 1-second + + setTimeout(function() { + handleRecordingDuration(counter); + }, 1000); + } + + function setState(state) { + if (!self) { + return; + } + + self.state = state; + + if (typeof self.onStateChanged.call === 'function') { + self.onStateChanged.call(self, state); + } else { + self.onStateChanged(state); + } + } + + var WARNING = 'It seems that recorder is destroyed or "startRecording" is not invoked for ' + config.type + ' recorder.'; + + function warningLog() { + if (config.disableLogs === true) { + return; + } + + console.warn(WARNING); + } + + var mediaRecorder; + + var returnObject = { + /** + * This method starts the recording. + * @method + * @memberof RecordRTC + * @instance + * @example + * var recorder = RecordRTC(mediaStream, { + * type: 'video' + * }); + * recorder.startRecording(); + */ + startRecording: startRecording, + + /** + * This method stops the recording. It is strongly recommended to get "blob" or "URI" inside the callback to make sure all recorders finished their job. + * @param {function} callback - Callback to get the recorded blob. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * // use either "this" or "recorder" object; both are identical + * video.src = this.toURL(); + * var blob = this.getBlob(); + * }); + */ + stopRecording: stopRecording, + + /** + * This method pauses the recording. You can resume recording using "resumeRecording" method. + * @method + * @memberof RecordRTC + * @instance + * @todo Firefox is unable to pause the recording. Fix it. + * @example + * recorder.pauseRecording(); // pause the recording + * recorder.resumeRecording(); // resume again + */ + pauseRecording: pauseRecording, + + /** + * This method resumes the recording. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.pauseRecording(); // first of all, pause the recording + * recorder.resumeRecording(); // now resume it + */ + resumeRecording: resumeRecording, + + /** + * This method initializes the recording. + * @method + * @memberof RecordRTC + * @instance + * @todo This method should be deprecated. + * @example + * recorder.initRecorder(); + */ + initRecorder: initRecorder, + + /** + * Ask RecordRTC to auto-stop the recording after 5 minutes. + * @method + * @memberof RecordRTC + * @instance + * @example + * var fiveMinutes = 5 * 1000 * 60; + * recorder.setRecordingDuration(fiveMinutes, function() { + * var blob = this.getBlob(); + * video.src = this.toURL(); + * }); + * + * // or otherwise + * recorder.setRecordingDuration(fiveMinutes).onRecordingStopped(function() { + * var blob = this.getBlob(); + * video.src = this.toURL(); + * }); + */ + setRecordingDuration: function(recordingDuration, callback) { + if (typeof recordingDuration === 'undefined') { + throw 'recordingDuration is required.'; + } + + if (typeof recordingDuration !== 'number') { + throw 'recordingDuration must be a number.'; + } + + self.recordingDuration = recordingDuration; + self.onRecordingStopped = callback || function() {}; + + return { + onRecordingStopped: function(callback) { + self.onRecordingStopped = callback; + } + }; + }, + + /** + * This method can be used to clear/reset all the recorded data. + * @method + * @memberof RecordRTC + * @instance + * @todo Figure out the difference between "reset" and "clearRecordedData" methods. + * @example + * recorder.clearRecordedData(); + */ + clearRecordedData: function() { + if (!mediaRecorder) { + warningLog(); + return; + } + + mediaRecorder.clearRecordedData(); + + if (!config.disableLogs) { + console.log('Cleared old recorded data.'); + } + }, + + /** + * Get the recorded blob. Use this method inside the "stopRecording" callback. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * var blob = this.getBlob(); + * + * var file = new File([blob], 'filename.webm', { + * type: 'video/webm' + * }); + * + * var formData = new FormData(); + * formData.append('file', file); // upload "File" object rather than a "Blob" + * uploadToServer(formData); + * }); + * @returns {Blob} Returns recorded data as "Blob" object. + */ + getBlob: function() { + if (!mediaRecorder) { + warningLog(); + return; + } + + return mediaRecorder.blob; + }, + + /** + * Get data-URI instead of Blob. + * @param {function} callback - Callback to get the Data-URI. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * recorder.getDataURL(function(dataURI) { + * video.src = dataURI; + * }); + * }); + */ + getDataURL: getDataURL, + + /** + * Get virtual/temporary URL. Usage of this URL is limited to current tab. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * video.src = this.toURL(); + * }); + * @returns {String} Returns a virtual/temporary URL for the recorded "Blob". + */ + toURL: function() { + if (!mediaRecorder) { + warningLog(); + return; + } + + return URL.createObjectURL(mediaRecorder.blob); + }, + + /** + * Get internal recording object (i.e. internal module) e.g. MutliStreamRecorder, MediaStreamRecorder, StereoAudioRecorder or WhammyRecorder etc. + * @method + * @memberof RecordRTC + * @instance + * @example + * var internal = recorder.getInternalRecorder(); + * if(internal instanceof MultiStreamRecorder) { + * internal.addStreams([newAudioStream]); + * internal.resetVideoStreams([screenStream]); + * } + * @returns {Object} Returns internal recording object. + */ + getInternalRecorder: function() { + return mediaRecorder; + }, + + /** + * Invoke save-as dialog to save the recorded blob into your disk. + * @param {string} fileName - Set your own file name. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.stopRecording(function() { + * this.save('file-name'); + * + * // or manually: + * invokeSaveAsDialog(this.getBlob(), 'filename.webm'); + * }); + */ + save: function(fileName) { + if (!mediaRecorder) { + warningLog(); + return; + } + + invokeSaveAsDialog(mediaRecorder.blob, fileName); + }, + + /** + * This method gets a blob from indexed-DB storage. + * @param {function} callback - Callback to get the recorded blob. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.getFromDisk(function(dataURL) { + * video.src = dataURL; + * }); + */ + getFromDisk: function(callback) { + if (!mediaRecorder) { + warningLog(); + return; + } + + RecordRTC.getFromDisk(config.type, callback); + }, + + /** + * This method appends an array of webp images to the recorded video-blob. It takes an "array" object. + * @type {Array.} + * @param {Array} arrayOfWebPImages - Array of webp images. + * @method + * @memberof RecordRTC + * @instance + * @todo This method should be deprecated. + * @example + * var arrayOfWebPImages = []; + * arrayOfWebPImages.push({ + * duration: index, + * image: 'data:image/webp;base64,...' + * }); + * recorder.setAdvertisementArray(arrayOfWebPImages); + */ + setAdvertisementArray: function(arrayOfWebPImages) { + config.advertisement = []; + + var length = arrayOfWebPImages.length; + for (var i = 0; i < length; i++) { + config.advertisement.push({ + duration: i, + image: arrayOfWebPImages[i] + }); + } + }, + + /** + * It is equivalent to "recorder.getBlob()" method. Usage of "getBlob" is recommended, though. + * @property {Blob} blob - Recorded Blob can be accessed using this property. + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * var blob = this.blob; + * + * // below one is recommended + * var blob = this.getBlob(); + * }); + */ + blob: null, + + /** + * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. + * @property {number} bufferSize - Buffer-size used to encode the WAV container + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * alert('Recorder used this buffer-size: ' + this.bufferSize); + * }); + */ + bufferSize: 0, + + /** + * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. + * @property {number} sampleRate - Sample-rates used to encode the WAV container + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * alert('Recorder used these sample-rates: ' + this.sampleRate); + * }); + */ + sampleRate: 0, + + /** + * {recorderType:StereoAudioRecorder} returns ArrayBuffer object. + * @property {ArrayBuffer} buffer - Audio ArrayBuffer, supported only in Chrome. + * @memberof RecordRTC + * @instance + * @readonly + * @example + * recorder.stopRecording(function() { + * var arrayBuffer = this.buffer; + * alert(arrayBuffer.byteLength); + * }); + */ + buffer: null, + + /** + * This method resets the recorder. So that you can reuse single recorder instance many times. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.reset(); + * recorder.startRecording(); + */ + reset: function() { + if (mediaRecorder && typeof mediaRecorder.clearRecordedData === 'function') { + mediaRecorder.clearRecordedData(); + } + mediaRecorder = null; + setState('inactive'); + self.blob = null; + }, + + /** + * This method is called whenever recorder's state changes. Use this as an "event". + * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. + * @method + * @memberof RecordRTC + * @instance + * @example + * recorder.onStateChanged = function(state) { + * console.log('Recorder state: ', state); + * }; + */ + onStateChanged: function(state) { + if (!config.disableLogs) { + console.log('Recorder state changed:', state); + } + }, + + /** + * A recorder can have inactive, recording, paused or stopped states. + * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. + * @memberof RecordRTC + * @static + * @readonly + * @example + * // this looper function will keep you updated about the recorder's states. + * (function looper() { + * document.querySelector('h1').innerHTML = 'Recorder\'s state is: ' + recorder.state; + * if(recorder.state === 'stopped') return; // ignore+stop + * setTimeout(looper, 1000); // update after every 3-seconds + * })(); + * recorder.startRecording(); + */ + state: 'inactive', + + /** + * Get recorder's readonly state. + * @method + * @memberof RecordRTC + * @example + * var state = recorder.getState(); + * @returns {String} Returns recording state. + */ + getState: function() { + return self.state; + }, + + /** + * Destroy RecordRTC instance. Clear all recorders and objects. + * @method + * @memberof RecordRTC + * @example + * recorder.destroy(); + */ + destroy: function() { + var disableLogsCache = config.disableLogs; + + config = { + disableLogs: true + }; + self.reset(); + setState('destroyed'); + returnObject = self = null; + + if (Storage.AudioContextConstructor) { + Storage.AudioContextConstructor.close(); + Storage.AudioContextConstructor = null; + } + + config.disableLogs = disableLogsCache; + + if (!config.disableLogs) { + console.warn('RecordRTC is destroyed.'); + } + }, + + /** + * RecordRTC version number + * @property {String} version - Release version number. + * @memberof RecordRTC + * @static + * @readonly + * @example + * alert(recorder.version); + */ + version: '5.5.4' + }; + + if (!this) { + self = returnObject; + return returnObject; + } + + // if someone wants to use RecordRTC with the "new" keyword. + for (var prop in returnObject) { + this[prop] = returnObject[prop]; + } + + self = this; + + return returnObject; +} + +RecordRTC.version = '5.5.4'; + +if (typeof module !== 'undefined' /* && !!module.exports*/ ) { + module.exports = RecordRTC; +} + +if (typeof define === 'function' && define.amd) { + define('RecordRTC', [], function() { + return RecordRTC; + }); +} + +RecordRTC.getFromDisk = function(type, callback) { + if (!callback) { + throw 'callback is mandatory.'; + } + + console.log('Getting recorded ' + (type === 'all' ? 'blobs' : type + ' blob ') + ' from disk!'); + DiskStorage.Fetch(function(dataURL, _type) { + if (type !== 'all' && _type === type + 'Blob' && callback) { + callback(dataURL); + } + + if (type === 'all' && callback) { + callback(dataURL, _type.replace('Blob', '')); + } + }); +}; + +/** + * This method can be used to store recorded blobs into IndexedDB storage. + * @param {object} options - {audio: Blob, video: Blob, gif: Blob} + * @method + * @memberof RecordRTC + * @example + * RecordRTC.writeToDisk({ + * audio: audioBlob, + * video: videoBlob, + * gif : gifBlob + * }); + */ +RecordRTC.writeToDisk = function(options) { + console.log('Writing recorded blob(s) to disk!'); + options = options || {}; + if (options.audio && options.video && options.gif) { + options.audio.getDataURL(function(audioDataURL) { + options.video.getDataURL(function(videoDataURL) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL, + videoBlob: videoDataURL, + gifBlob: gifDataURL + }); + }); + }); + }); + } else if (options.audio && options.video) { + options.audio.getDataURL(function(audioDataURL) { + options.video.getDataURL(function(videoDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL, + videoBlob: videoDataURL + }); + }); + }); + } else if (options.audio && options.gif) { + options.audio.getDataURL(function(audioDataURL) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL, + gifBlob: gifDataURL + }); + }); + }); + } else if (options.video && options.gif) { + options.video.getDataURL(function(videoDataURL) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + videoBlob: videoDataURL, + gifBlob: gifDataURL + }); + }); + }); + } else if (options.audio) { + options.audio.getDataURL(function(audioDataURL) { + DiskStorage.Store({ + audioBlob: audioDataURL + }); + }); + } else if (options.video) { + options.video.getDataURL(function(videoDataURL) { + DiskStorage.Store({ + videoBlob: videoDataURL + }); + }); + } else if (options.gif) { + options.gif.getDataURL(function(gifDataURL) { + DiskStorage.Store({ + gifBlob: gifDataURL + }); + }); + } +}; + +// __________________________ +// RecordRTC-Configuration.js + +/** + * {@link RecordRTCConfiguration} is an inner/private helper for {@link RecordRTC}. + * @summary It configures the 2nd parameter passed over {@link RecordRTC} and returns a valid "config" object. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef RecordRTCConfiguration + * @class + * @example + * var options = RecordRTCConfiguration(mediaStream, options); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, getNativeBlob:true, etc.} + */ + +function RecordRTCConfiguration(mediaStream, config) { + if (!config.recorderType && !config.type) { + if (!!config.audio && !!config.video) { + config.type = 'video'; + } else if (!!config.audio && !config.video) { + config.type = 'audio'; + } + } + + if (config.recorderType && !config.type) { + if (config.recorderType === WhammyRecorder || config.recorderType === CanvasRecorder || (typeof WebAssemblyRecorder !== 'undefined' && config.recorderType === WebAssemblyRecorder)) { + config.type = 'video'; + } else if (config.recorderType === GifRecorder) { + config.type = 'gif'; + } else if (config.recorderType === StereoAudioRecorder) { + config.type = 'audio'; + } else if (config.recorderType === MediaStreamRecorder) { + if (getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { + config.type = 'video'; + } else if (!getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { + config.type = 'video'; + } else if (getTracks(mediaStream, 'audio').length && !getTracks(mediaStream, 'video').length) { + config.type = 'audio'; + } else { + // config.type = 'UnKnown'; + } + } + } + + if (typeof MediaStreamRecorder !== 'undefined' && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { + if (!config.mimeType) { + config.mimeType = 'video/webm'; + } + + if (!config.type) { + config.type = config.mimeType.split('/')[0]; + } + + if (!config.bitsPerSecond) { + // config.bitsPerSecond = 128000; + } + } + + // consider default type=audio + if (!config.type) { + if (config.mimeType) { + config.type = config.mimeType.split('/')[0]; + } + if (!config.type) { + config.type = 'audio'; + } + } + + return config; +} + +// __________________ +// GetRecorderType.js + +/** + * {@link GetRecorderType} is an inner/private helper for {@link RecordRTC}. + * @summary It returns best recorder-type available for your browser. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef GetRecorderType + * @class + * @example + * var RecorderType = GetRecorderType(options); + * var recorder = new RecorderType(options); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} + */ + +function GetRecorderType(mediaStream, config) { + var recorder; + + // StereoAudioRecorder can work with all three: Edge, Firefox and Chrome + // todo: detect if it is Edge, then auto use: StereoAudioRecorder + if (isChrome || isEdge || isOpera) { + // Media Stream Recording API has not been implemented in chrome yet; + // That's why using WebAudio API to record stereo audio in WAV format + recorder = StereoAudioRecorder; + } + + if (typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype && !isChrome) { + recorder = MediaStreamRecorder; + } + + // video recorder (in WebM format) + if (config.type === 'video' && (isChrome || isOpera)) { + recorder = WhammyRecorder; + + if (typeof WebAssemblyRecorder !== 'undefined' && typeof ReadableStream !== 'undefined') { + recorder = WebAssemblyRecorder; + } + } + + // video recorder (in Gif format) + if (config.type === 'gif') { + recorder = GifRecorder; + } + + // html2canvas recording! + if (config.type === 'canvas') { + recorder = CanvasRecorder; + } + + if (isMediaRecorderCompatible() && recorder !== CanvasRecorder && recorder !== GifRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { + if (getTracks(mediaStream, 'video').length || getTracks(mediaStream, 'audio').length) { + // audio-only recording + if (config.type === 'audio') { + if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('audio/webm')) { + recorder = MediaStreamRecorder; + } + // else recorder = StereoAudioRecorder; + } else { + // video or screen tracks + if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('video/webm')) { + recorder = MediaStreamRecorder; + } + } + } + } + + if (mediaStream instanceof Array && mediaStream.length) { + recorder = MultiStreamRecorder; + } + + if (config.recorderType) { + recorder = config.recorderType; + } + + if (!config.disableLogs && !!recorder && !!recorder.name) { + console.log('Using recorderType:', recorder.name || recorder.constructor.name); + } + + if (!recorder && isSafari) { + recorder = MediaStreamRecorder; + } + + return recorder; +} + +// _____________ +// MRecordRTC.js + +/** + * MRecordRTC runs on top of {@link RecordRTC} to bring multiple recordings in a single place, by providing simple API. + * @summary MRecordRTC stands for "Multiple-RecordRTC". + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef MRecordRTC + * @class + * @example + * var recorder = new MRecordRTC(); + * recorder.addStream(MediaStream); + * recorder.mediaType = { + * audio: true, // or StereoAudioRecorder or MediaStreamRecorder + * video: true, // or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder + * gif: true // or GifRecorder + * }; + * // mimeType is optional and should be set only in advance cases. + * recorder.mimeType = { + * audio: 'audio/wav', + * video: 'video/webm', + * gif: 'image/gif' + * }; + * recorder.startRecording(); + * @see For further information: + * @see {@link https://github.com/muaz-khan/RecordRTC/tree/master/MRecordRTC|MRecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @requires {@link RecordRTC} + */ + +function MRecordRTC(mediaStream) { + + /** + * This method attaches MediaStream object to {@link MRecordRTC}. + * @param {MediaStream} mediaStream - A MediaStream object, either fetched using getUserMedia API, or generated using captureStreamUntilEnded or WebAudio API. + * @method + * @memberof MRecordRTC + * @example + * recorder.addStream(MediaStream); + */ + this.addStream = function(_mediaStream) { + if (_mediaStream) { + mediaStream = _mediaStream; + } + }; + + /** + * This property can be used to set the recording type e.g. audio, or video, or gif, or canvas. + * @property {object} mediaType - {audio: true, video: true, gif: true} + * @memberof MRecordRTC + * @example + * var recorder = new MRecordRTC(); + * recorder.mediaType = { + * audio: true, // TRUE or StereoAudioRecorder or MediaStreamRecorder + * video: true, // TRUE or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder + * gif : true // TRUE or GifRecorder + * }; + */ + this.mediaType = { + audio: true, + video: true + }; + + /** + * This method starts recording. + * @method + * @memberof MRecordRTC + * @example + * recorder.startRecording(); + */ + this.startRecording = function() { + var mediaType = this.mediaType; + var recorderType; + var mimeType = this.mimeType || { + audio: null, + video: null, + gif: null + }; + + if (typeof mediaType.audio !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'audio').length) { + mediaType.audio = false; + } + + if (typeof mediaType.video !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { + mediaType.video = false; + } + + if (typeof mediaType.gif !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { + mediaType.gif = false; + } + + if (!mediaType.audio && !mediaType.video && !mediaType.gif) { + throw 'MediaStream must have either audio or video tracks.'; + } + + if (!!mediaType.audio) { + recorderType = null; + if (typeof mediaType.audio === 'function') { + recorderType = mediaType.audio; + } + + this.audioRecorder = new RecordRTC(mediaStream, { + type: 'audio', + bufferSize: this.bufferSize, + sampleRate: this.sampleRate, + numberOfAudioChannels: this.numberOfAudioChannels || 2, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.audio, + timeSlice: this.timeSlice, + onTimeStamp: this.onTimeStamp + }); + + if (!mediaType.video) { + this.audioRecorder.startRecording(); + } + } + + if (!!mediaType.video) { + recorderType = null; + if (typeof mediaType.video === 'function') { + recorderType = mediaType.video; + } + + var newStream = mediaStream; + + if (isMediaRecorderCompatible() && !!mediaType.audio && typeof mediaType.audio === 'function') { + var videoTrack = getTracks(mediaStream, 'video')[0]; + + if (isFirefox) { + newStream = new MediaStream(); + newStream.addTrack(videoTrack); + + if (recorderType && recorderType === WhammyRecorder) { + // Firefox does NOT supports webp-encoding yet + // But Firefox do supports WebAssemblyRecorder + recorderType = MediaStreamRecorder; + } + } else { + newStream = new MediaStream(); + newStream.addTrack(videoTrack); + } + } + + this.videoRecorder = new RecordRTC(newStream, { + type: 'video', + video: this.video, + canvas: this.canvas, + frameInterval: this.frameInterval || 10, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.video, + timeSlice: this.timeSlice, + onTimeStamp: this.onTimeStamp, + workerPath: this.workerPath, + webAssemblyPath: this.webAssemblyPath, + frameRate: this.frameRate, // used by WebAssemblyRecorder; values: usually 30; accepts any. + bitrate: this.bitrate // used by WebAssemblyRecorder; values: 0 to 1000+ + }); + + if (!mediaType.audio) { + this.videoRecorder.startRecording(); + } + } + + if (!!mediaType.audio && !!mediaType.video) { + var self = this; + + var isSingleRecorder = isMediaRecorderCompatible() === true; + + if (mediaType.audio instanceof StereoAudioRecorder && !!mediaType.video) { + isSingleRecorder = false; + } else if (mediaType.audio !== true && mediaType.video !== true && mediaType.audio !== mediaType.video) { + isSingleRecorder = false; + } + + if (isSingleRecorder === true) { + self.audioRecorder = null; + self.videoRecorder.startRecording(); + } else { + self.videoRecorder.initRecorder(function() { + self.audioRecorder.initRecorder(function() { + // Both recorders are ready to record things accurately + self.videoRecorder.startRecording(); + self.audioRecorder.startRecording(); + }); + }); + } + } + + if (!!mediaType.gif) { + recorderType = null; + if (typeof mediaType.gif === 'function') { + recorderType = mediaType.gif; + } + this.gifRecorder = new RecordRTC(mediaStream, { + type: 'gif', + frameRate: this.frameRate || 200, + quality: this.quality || 10, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.gif + }); + this.gifRecorder.startRecording(); + } + }; + + /** + * This method stops recording. + * @param {function} callback - Callback function is invoked when all encoders finished their jobs. + * @method + * @memberof MRecordRTC + * @example + * recorder.stopRecording(function(recording){ + * var audioBlob = recording.audio; + * var videoBlob = recording.video; + * var gifBlob = recording.gif; + * }); + */ + this.stopRecording = function(callback) { + callback = callback || function() {}; + + if (this.audioRecorder) { + this.audioRecorder.stopRecording(function(blobURL) { + callback(blobURL, 'audio'); + }); + } + + if (this.videoRecorder) { + this.videoRecorder.stopRecording(function(blobURL) { + callback(blobURL, 'video'); + }); + } + + if (this.gifRecorder) { + this.gifRecorder.stopRecording(function(blobURL) { + callback(blobURL, 'gif'); + }); + } + }; + + /** + * This method pauses recording. + * @method + * @memberof MRecordRTC + * @example + * recorder.pauseRecording(); + */ + this.pauseRecording = function() { + if (this.audioRecorder) { + this.audioRecorder.pauseRecording(); + } + + if (this.videoRecorder) { + this.videoRecorder.pauseRecording(); + } + + if (this.gifRecorder) { + this.gifRecorder.pauseRecording(); + } + }; + + /** + * This method resumes recording. + * @method + * @memberof MRecordRTC + * @example + * recorder.resumeRecording(); + */ + this.resumeRecording = function() { + if (this.audioRecorder) { + this.audioRecorder.resumeRecording(); + } + + if (this.videoRecorder) { + this.videoRecorder.resumeRecording(); + } + + if (this.gifRecorder) { + this.gifRecorder.resumeRecording(); + } + }; + + /** + * This method can be used to manually get all recorded blobs. + * @param {function} callback - All recorded blobs are passed back to the "callback" function. + * @method + * @memberof MRecordRTC + * @example + * recorder.getBlob(function(recording){ + * var audioBlob = recording.audio; + * var videoBlob = recording.video; + * var gifBlob = recording.gif; + * }); + * // or + * var audioBlob = recorder.getBlob().audio; + * var videoBlob = recorder.getBlob().video; + */ + this.getBlob = function(callback) { + var output = {}; + + if (this.audioRecorder) { + output.audio = this.audioRecorder.getBlob(); + } + + if (this.videoRecorder) { + output.video = this.videoRecorder.getBlob(); + } + + if (this.gifRecorder) { + output.gif = this.gifRecorder.getBlob(); + } + + if (callback) { + callback(output); + } + + return output; + }; + + /** + * Destroy all recorder instances. + * @method + * @memberof MRecordRTC + * @example + * recorder.destroy(); + */ + this.destroy = function() { + if (this.audioRecorder) { + this.audioRecorder.destroy(); + this.audioRecorder = null; + } + + if (this.videoRecorder) { + this.videoRecorder.destroy(); + this.videoRecorder = null; + } + + if (this.gifRecorder) { + this.gifRecorder.destroy(); + this.gifRecorder = null; + } + }; + + /** + * This method can be used to manually get all recorded blobs' DataURLs. + * @param {function} callback - All recorded blobs' DataURLs are passed back to the "callback" function. + * @method + * @memberof MRecordRTC + * @example + * recorder.getDataURL(function(recording){ + * var audioDataURL = recording.audio; + * var videoDataURL = recording.video; + * var gifDataURL = recording.gif; + * }); + */ + this.getDataURL = function(callback) { + this.getBlob(function(blob) { + if (blob.audio && blob.video) { + getDataURL(blob.audio, function(_audioDataURL) { + getDataURL(blob.video, function(_videoDataURL) { + callback({ + audio: _audioDataURL, + video: _videoDataURL + }); + }); + }); + } else if (blob.audio) { + getDataURL(blob.audio, function(_audioDataURL) { + callback({ + audio: _audioDataURL + }); + }); + } else if (blob.video) { + getDataURL(blob.video, function(_videoDataURL) { + callback({ + video: _videoDataURL + }); + }); + } + }); + + function getDataURL(blob, callback00) { + if (typeof Worker !== 'undefined') { + var webWorker = processInWebWorker(function readFile(_blob) { + postMessage(new FileReaderSync().readAsDataURL(_blob)); + }); + + webWorker.onmessage = function(event) { + callback00(event.data); + }; + + webWorker.postMessage(blob); + } else { + var reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onload = function(event) { + callback00(event.target.result); + }; + } + } + + function processInWebWorker(_function) { + var blob = URL.createObjectURL(new Blob([_function.toString(), + 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(blob); + var url; + if (typeof URL !== 'undefined') { + url = URL; + } else if (typeof webkitURL !== 'undefined') { + url = webkitURL; + } else { + throw 'Neither URL nor webkitURL detected.'; + } + url.revokeObjectURL(blob); + return worker; + } + }; + + /** + * This method can be used to ask {@link MRecordRTC} to write all recorded blobs into IndexedDB storage. + * @method + * @memberof MRecordRTC + * @example + * recorder.writeToDisk(); + */ + this.writeToDisk = function() { + RecordRTC.writeToDisk({ + audio: this.audioRecorder, + video: this.videoRecorder, + gif: this.gifRecorder + }); + }; + + /** + * This method can be used to invoke a save-as dialog for all recorded blobs. + * @param {object} args - {audio: 'audio-name', video: 'video-name', gif: 'gif-name'} + * @method + * @memberof MRecordRTC + * @example + * recorder.save({ + * audio: 'audio-file-name', + * video: 'video-file-name', + * gif : 'gif-file-name' + * }); + */ + this.save = function(args) { + args = args || { + audio: true, + video: true, + gif: true + }; + + if (!!args.audio && this.audioRecorder) { + this.audioRecorder.save(typeof args.audio === 'string' ? args.audio : ''); + } + + if (!!args.video && this.videoRecorder) { + this.videoRecorder.save(typeof args.video === 'string' ? args.video : ''); + } + if (!!args.gif && this.gifRecorder) { + this.gifRecorder.save(typeof args.gif === 'string' ? args.gif : ''); + } + }; +} + +/** + * This method can be used to get all recorded blobs from IndexedDB storage. + * @param {string} type - 'all' or 'audio' or 'video' or 'gif' + * @param {function} callback - Callback function to get all stored blobs. + * @method + * @memberof MRecordRTC + * @example + * MRecordRTC.getFromDisk('all', function(dataURL, type){ + * if(type === 'audio') { } + * if(type === 'video') { } + * if(type === 'gif') { } + * }); + */ +MRecordRTC.getFromDisk = RecordRTC.getFromDisk; + +/** + * This method can be used to store recorded blobs into IndexedDB storage. + * @param {object} options - {audio: Blob, video: Blob, gif: Blob} + * @method + * @memberof MRecordRTC + * @example + * MRecordRTC.writeToDisk({ + * audio: audioBlob, + * video: videoBlob, + * gif : gifBlob + * }); + */ +MRecordRTC.writeToDisk = RecordRTC.writeToDisk; + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.MRecordRTC = MRecordRTC; +} + +var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; + +(function(that) { + if (!that) { + return; + } + + if (typeof window !== 'undefined') { + return; + } + + if (typeof global === 'undefined') { + return; + } + + global.navigator = { + userAgent: browserFakeUserAgent, + getUserMedia: function() {} + }; + + if (!global.console) { + global.console = {}; + } + + if (typeof global.console.log === 'undefined' || typeof global.console.error === 'undefined') { + global.console.error = global.console.log = global.console.log || function() { + console.log(arguments); + }; + } + + if (typeof document === 'undefined') { + /*global document:true */ + that.document = {}; + + document.createElement = document.captureStream = document.mozCaptureStream = function() { + var obj = { + getContext: function() { + return obj; + }, + play: function() {}, + pause: function() {}, + drawImage: function() {}, + toDataURL: function() { + return ''; + } + }; + return obj; + }; + + that.HTMLVideoElement = function() {}; + } + + if (typeof location === 'undefined') { + /*global location:true */ + that.location = { + protocol: 'file:', + href: '', + hash: '' + }; + } + + if (typeof screen === 'undefined') { + /*global screen:true */ + that.screen = { + width: 0, + height: 0 + }; + } + + if (typeof URL === 'undefined') { + /*global screen:true */ + that.URL = { + createObjectURL: function() { + return ''; + }, + revokeObjectURL: function() { + return ''; + } + }; + } + + /*global window:true */ + that.window = global; +})(typeof global !== 'undefined' ? global : null); + +// _____________________________ +// Cross-Browser-Declarations.js + +// animation-frame used in WebM recording + +/*jshint -W079 */ +var requestAnimationFrame = window.requestAnimationFrame; +if (typeof requestAnimationFrame === 'undefined') { + if (typeof webkitRequestAnimationFrame !== 'undefined') { + /*global requestAnimationFrame:true */ + requestAnimationFrame = webkitRequestAnimationFrame; + } else if (typeof mozRequestAnimationFrame !== 'undefined') { + /*global requestAnimationFrame:true */ + requestAnimationFrame = mozRequestAnimationFrame; + } else if (typeof msRequestAnimationFrame !== 'undefined') { + /*global requestAnimationFrame:true */ + requestAnimationFrame = msRequestAnimationFrame; + } else if (typeof requestAnimationFrame === 'undefined') { + // via: https://gist.github.com/paulirish/1579671 + var lastTime = 0; + + /*global requestAnimationFrame:true */ + requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = setTimeout(function() { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } +} + +/*jshint -W079 */ +var cancelAnimationFrame = window.cancelAnimationFrame; +if (typeof cancelAnimationFrame === 'undefined') { + if (typeof webkitCancelAnimationFrame !== 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = webkitCancelAnimationFrame; + } else if (typeof mozCancelAnimationFrame !== 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = mozCancelAnimationFrame; + } else if (typeof msCancelAnimationFrame !== 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = msCancelAnimationFrame; + } else if (typeof cancelAnimationFrame === 'undefined') { + /*global cancelAnimationFrame:true */ + cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + } +} + +// WebAudio API representer +var AudioContext = window.AudioContext; + +if (typeof AudioContext === 'undefined') { + if (typeof webkitAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = webkitAudioContext; + } + + if (typeof mozAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = mozAudioContext; + } +} + +/*jshint -W079 */ +var URL = window.URL; + +if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { + /*global URL:true */ + URL = webkitURL; +} + +if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? + if (typeof navigator.webkitGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + + if (typeof navigator.mozGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.mozGetUserMedia; + } +} + +var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveBlob || !!navigator.msSaveOrOpenBlob); +var isOpera = !!window.opera || navigator.userAgent.indexOf('OPR/') !== -1; +var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 && ('netscape' in window) && / rv:/.test(navigator.userAgent); +var isChrome = (!isOpera && !isEdge && !!navigator.webkitGetUserMedia) || isElectron() || navigator.userAgent.toLowerCase().indexOf('chrome/') !== -1; + +var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + +if (isSafari && !isChrome && navigator.userAgent.indexOf('CriOS') !== -1) { + isSafari = false; + isChrome = true; +} + +var MediaStream = window.MediaStream; + +if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { + MediaStream = webkitMediaStream; +} + +/*global MediaStream:true */ +if (typeof MediaStream !== 'undefined') { + // override "stop" method for all browsers + if (typeof MediaStream.prototype.stop === 'undefined') { + MediaStream.prototype.stop = function() { + this.getTracks().forEach(function(track) { + track.stop(); + }); + }; + } +} + +// below function via: http://goo.gl/B3ae8c +/** + * Return human-readable file size. + * @param {number} bytes - Pass bytes and get formatted string. + * @returns {string} - formatted string + * @example + * bytesToSize(1024*1024*5) === '5 GB' + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ +function bytesToSize(bytes) { + var k = 1000; + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) { + return '0 Bytes'; + } + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); + return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; +} + +/** + * @param {Blob} file - File or Blob object. This parameter is required. + * @param {string} fileName - Optional file name e.g. "Recorded-Video.webm" + * @example + * invokeSaveAsDialog(blob or file, [optional] fileName); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ +function invokeSaveAsDialog(file, fileName) { + if (!file) { + throw 'Blob object is required.'; + } + + if (!file.type) { + try { + file.type = 'video/webm'; + } catch (e) {} + } + + var fileExtension = (file.type || 'video/webm').split('/')[1]; + + if (fileName && fileName.indexOf('.') !== -1) { + var splitted = fileName.split('.'); + fileName = splitted[0]; + fileExtension = splitted[1]; + } + + var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension; + + if (typeof navigator.msSaveOrOpenBlob !== 'undefined') { + return navigator.msSaveOrOpenBlob(file, fileFullName); + } else if (typeof navigator.msSaveBlob !== 'undefined') { + return navigator.msSaveBlob(file, fileFullName); + } + + var hyperlink = document.createElement('a'); + hyperlink.href = URL.createObjectURL(file); + hyperlink.download = fileFullName; + + hyperlink.style = 'display:none;opacity:0;color:transparent;'; + (document.body || document.documentElement).appendChild(hyperlink); + + if (typeof hyperlink.click === 'function') { + hyperlink.click(); + } else { + hyperlink.target = '_blank'; + hyperlink.dispatchEvent(new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true + })); + } + + URL.revokeObjectURL(hyperlink.href); +} + +/** + * from: https://github.com/cheton/is-electron/blob/master/index.js + **/ +function isElectron() { + // Renderer process + if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') { + return true; + } + + // Main process + if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) { + return true; + } + + // Detect the user agent when the `nodeIntegration` option is set to true + if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) { + return true; + } + + return false; +} + +function getTracks(stream, kind) { + if (!stream || !stream.getTracks) { + return []; + } + + return stream.getTracks().filter(function(t) { + return t.kind === (kind || 'audio'); + }); +} + +function setSrcObject(stream, element) { + if ('srcObject' in element) { + element.srcObject = stream; + } else if ('mozSrcObject' in element) { + element.mozSrcObject = stream; + } else { + element.srcObject = stream; + } +} + +/** + * @param {Blob} file - File or Blob object. + * @param {function} callback - Callback function. + * @example + * getSeekableBlob(blob or file, callback); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ +function getSeekableBlob(inputBlob, callback) { + // EBML.js copyrights goes to: https://github.com/legokichi/ts-ebml + if (typeof EBML === 'undefined') { + throw new Error('Please link: https://cdn.webrtc-experiment.com/EBML.js'); + } + + var reader = new EBML.Reader(); + var decoder = new EBML.Decoder(); + var tools = EBML.tools; + + var fileReader = new FileReader(); + fileReader.onload = function(e) { + var ebmlElms = decoder.decode(this.result); + ebmlElms.forEach(function(element) { + reader.read(element); + }); + reader.stop(); + var refinedMetadataBuf = tools.makeMetadataSeekable(reader.metadatas, reader.duration, reader.cues); + var body = this.result.slice(reader.metadataSize); + var newBlob = new Blob([refinedMetadataBuf, body], { + type: 'video/webm' + }); + + callback(newBlob); + }; + fileReader.readAsArrayBuffer(inputBlob); +} + +// __________ (used to handle stuff like http://goo.gl/xmE5eg) issue #129 +// Storage.js + +/** + * Storage is a standalone object used by {@link RecordRTC} to store reusable objects e.g. "new AudioContext". + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @example + * Storage.AudioContext === webkitAudioContext + * @property {webkitAudioContext} AudioContext - Keeps a reference to AudioContext object. + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ + +var Storage = {}; + +if (typeof AudioContext !== 'undefined') { + Storage.AudioContext = AudioContext; +} else if (typeof webkitAudioContext !== 'undefined') { + Storage.AudioContext = webkitAudioContext; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.Storage = Storage; +} + +function isMediaRecorderCompatible() { + if (isFirefox || isSafari || isEdge) { + return true; + } + + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + var fullVersion = '' + parseFloat(navigator.appVersion); + var majorVersion = parseInt(navigator.appVersion, 10); + var nameOffset, verOffset, ix; + + if (isChrome || isOpera) { + verOffset = nAgt.indexOf('Chrome'); + fullVersion = nAgt.substring(verOffset + 7); + } + + // trim the fullVersion string at semicolon/space if present + if ((ix = fullVersion.indexOf(';')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + if ((ix = fullVersion.indexOf(' ')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + majorVersion = parseInt('' + fullVersion, 10); + + if (isNaN(majorVersion)) { + fullVersion = '' + parseFloat(navigator.appVersion); + majorVersion = parseInt(navigator.appVersion, 10); + } + + return majorVersion >= 49; +} + +// ______________________ +// MediaStreamRecorder.js + +/** + * MediaStreamRecorder is an abstraction layer for {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. It is used by {@link RecordRTC} to record MediaStream(s) in both Chrome and Firefox. + * @summary Runs top over {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link https://github.com/muaz-khan|Muaz Khan} + * @typedef MediaStreamRecorder + * @class + * @example + * var config = { + * mimeType: 'video/webm', // vp8, vp9, h264, mkv, opus/vorbis + * audioBitsPerSecond : 256 * 8 * 1024, + * videoBitsPerSecond : 256 * 8 * 1024, + * bitsPerSecond: 256 * 8 * 1024, // if this is provided, skip above two + * checkForInactiveTracks: true, + * timeSlice: 1000, // concatenate intervals based blobs + * ondataavailable: function() {} // get intervals based blobs + * } + * var recorder = new MediaStreamRecorder(mediaStream, config); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * + * // or + * var blob = recorder.blob; + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {disableLogs:true, initCallback: function, mimeType: "video/webm", timeSlice: 1000} + * @throws Will throw an error if first argument "MediaStream" is missing. Also throws error if "MediaRecorder API" are not supported by the browser. + */ + +function MediaStreamRecorder(mediaStream, config) { + var self = this; + + if (typeof mediaStream === 'undefined') { + throw 'First argument "MediaStream" is required.'; + } + + if (typeof MediaRecorder === 'undefined') { + throw 'Your browser does not supports Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.'; + } + + config = config || { + // bitsPerSecond: 256 * 8 * 1024, + mimeType: 'video/webm' + }; + + if (config.type === 'audio') { + if (getTracks(mediaStream, 'video').length && getTracks(mediaStream, 'audio').length) { + var stream; + if (!!navigator.mozGetUserMedia) { + stream = new MediaStream(); + stream.addTrack(getTracks(mediaStream, 'audio')[0]); + } else { + // webkitMediaStream + stream = new MediaStream(getTracks(mediaStream, 'audio')); + } + mediaStream = stream; + } + + if (!config.mimeType || config.mimeType.toString().toLowerCase().indexOf('audio') === -1) { + config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg'; + } + + if (config.mimeType && config.mimeType.toString().toLowerCase() !== 'audio/ogg' && !!navigator.mozGetUserMedia) { + // forcing better codecs on Firefox (via #166) + config.mimeType = 'audio/ogg'; + } + } + + var arrayOfBlobs = []; + + /** + * This method returns array of blobs. Use only with "timeSlice". Its useful to preview recording anytime, without using the "stop" method. + * @method + * @memberof MediaStreamRecorder + * @example + * var arrayOfBlobs = recorder.getArrayOfBlobs(); + * @returns {Array} Returns array of recorded blobs. + */ + this.getArrayOfBlobs = function() { + return arrayOfBlobs; + }; + + /** + * This method records MediaStream. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.record(); + */ + this.record = function() { + // set defaults + self.blob = null; + self.clearRecordedData(); + self.timestamps = []; + allStates = []; + arrayOfBlobs = []; + + var recorderHints = config; + + if (!config.disableLogs) { + console.log('Passing following config over MediaRecorder API.', recorderHints); + } + + if (mediaRecorder) { + // mandatory to make sure Firefox doesn't fails to record streams 3-4 times without reloading the page. + mediaRecorder = null; + } + + if (isChrome && !isMediaRecorderCompatible()) { + // to support video-only recording on stable + recorderHints = 'video/vp8'; + } + + if (typeof MediaRecorder.isTypeSupported === 'function' && recorderHints.mimeType) { + if (!MediaRecorder.isTypeSupported(recorderHints.mimeType)) { + if (!config.disableLogs) { + console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); + } + + recorderHints.mimeType = config.type === 'audio' ? 'audio/webm' : 'video/webm'; + } + } + + // using MediaRecorder API here + try { + mediaRecorder = new MediaRecorder(mediaStream, recorderHints); + + // reset + config.mimeType = recorderHints.mimeType; + } catch (e) { + // chrome-based fallback + mediaRecorder = new MediaRecorder(mediaStream); + } + + // old hack? + if (recorderHints.mimeType && !MediaRecorder.isTypeSupported && 'canRecordMimeType' in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === false) { + if (!config.disableLogs) { + console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); + } + } + + // Dispatching OnDataAvailable Handler + mediaRecorder.ondataavailable = function(e) { + if (e.data) { + allStates.push('ondataavailable: ' + bytesToSize(e.data.size)); + } + + if (typeof config.timeSlice === 'number') { + if (e.data && e.data.size && e.data.size > 100) { + arrayOfBlobs.push(e.data); + updateTimeStamp(); + + if (typeof config.ondataavailable === 'function') { + // intervals based blobs + var blob = config.getNativeBlob ? e.data : new Blob([e.data], { + type: getMimeType(recorderHints) + }); + config.ondataavailable(blob); + } + } + return; + } + + if (!e.data || !e.data.size || e.data.size < 100 || self.blob) { + // make sure that stopRecording always getting fired + // even if there is invalid data + if (self.recordingCallback) { + self.recordingCallback(new Blob([], { + type: getMimeType(recorderHints) + })); + self.recordingCallback = null; + } + return; + } + + self.blob = config.getNativeBlob ? e.data : new Blob([e.data], { + type: getMimeType(recorderHints) + }); + + if (self.recordingCallback) { + self.recordingCallback(self.blob); + self.recordingCallback = null; + } + }; + + mediaRecorder.onstart = function() { + allStates.push('started'); + }; + + mediaRecorder.onpause = function() { + allStates.push('paused'); + }; + + mediaRecorder.onresume = function() { + allStates.push('resumed'); + }; + + mediaRecorder.onstop = function() { + allStates.push('stopped'); + }; + + mediaRecorder.onerror = function(error) { + if (!error) { + return; + } + + if (!error.name) { + error.name = 'UnknownError'; + } + + allStates.push('error: ' + error); + + if (!config.disableLogs) { + // via: https://w3c.github.io/mediacapture-record/MediaRecorder.html#exception-summary + if (error.name.toString().toLowerCase().indexOf('invalidstate') !== -1) { + console.error('The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.', error); + } else if (error.name.toString().toLowerCase().indexOf('notsupported') !== -1) { + console.error('MIME type (', recorderHints.mimeType, ') is not supported.', error); + } else if (error.name.toString().toLowerCase().indexOf('security') !== -1) { + console.error('MediaRecorder security error', error); + } + + // older code below + else if (error.name === 'OutOfMemory') { + console.error('The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.', error); + } else if (error.name === 'IllegalStreamModification') { + console.error('A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.', error); + } else if (error.name === 'OtherRecordingError') { + console.error('Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.', error); + } else if (error.name === 'GenericError') { + console.error('The UA cannot provide the codec or recording option that has been requested.', error); + } else { + console.error('MediaRecorder Error', error); + } + } + + (function(looper) { + if (!self.manuallyStopped && mediaRecorder && mediaRecorder.state === 'inactive') { + delete config.timeslice; + + // 10 minutes, enough? + mediaRecorder.start(10 * 60 * 1000); + return; + } + + setTimeout(looper, 1000); + })(); + + if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') { + mediaRecorder.stop(); + } + }; + + if (typeof config.timeSlice === 'number') { + updateTimeStamp(); + mediaRecorder.start(config.timeSlice); + } else { + // default is 60 minutes; enough? + // use config => {timeSlice: 1000} otherwise + + mediaRecorder.start(3.6e+6); + } + + if (config.initCallback) { + config.initCallback(); // old code + } + }; + + /** + * @property {Array} timestamps - Array of time stamps + * @memberof MediaStreamRecorder + * @example + * console.log(recorder.timestamps); + */ + this.timestamps = []; + + function updateTimeStamp() { + self.timestamps.push(new Date().getTime()); + + if (typeof config.onTimeStamp === 'function') { + config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps); + } + } + + function getMimeType(secondObject) { + if (mediaRecorder && mediaRecorder.mimeType) { + return mediaRecorder.mimeType; + } + + return secondObject.mimeType || 'video/webm'; + } + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + self.manuallyStopped = true; // used inside the mediaRecorder.onerror + + if (!mediaRecorder) { + return; + } + + this.recordingCallback = callback; + + if (mediaRecorder.state === 'recording') { + mediaRecorder.stop(); + } + + if (typeof config.timeSlice === 'number') { + setTimeout(function() { + self.blob = new Blob(arrayOfBlobs, { + type: getMimeType(config) + }); + + self.recordingCallback(self.blob); + }, 100); + } + }; + + /** + * This method pauses the recording process. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + if (!mediaRecorder) { + return; + } + + if (mediaRecorder.state === 'recording') { + mediaRecorder.pause(); + } + }; + + /** + * This method resumes the recording process. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + if (!mediaRecorder) { + return; + } + + if (mediaRecorder.state === 'paused') { + mediaRecorder.resume(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof MediaStreamRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (mediaRecorder && mediaRecorder.state === 'recording') { + self.stop(clearRecordedDataCB); + } + + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + arrayOfBlobs = []; + mediaRecorder = null; + self.timestamps = []; + } + + // Reference to "MediaRecorder" object + var mediaRecorder; + + /** + * Access to native MediaRecorder API + * @method + * @memberof MediaStreamRecorder + * @instance + * @example + * var internal = recorder.getInternalRecorder(); + * internal.ondataavailable = function() {}; // override + * internal.stream, internal.onpause, internal.onstop, etc. + * @returns {Object} Returns internal recording object. + */ + this.getInternalRecorder = function() { + return mediaRecorder; + }; + + function isMediaStreamActive() { + if ('active' in mediaStream) { + if (!mediaStream.active) { + return false; + } + } else if ('ended' in mediaStream) { // old hack + if (mediaStream.ended) { + return false; + } + } + return true; + } + + /** + * @property {Blob} blob - Recorded data as "Blob" object. + * @memberof MediaStreamRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + this.blob = null; + + + /** + * Get MediaRecorder readonly state. + * @method + * @memberof MediaStreamRecorder + * @example + * var state = recorder.getState(); + * @returns {String} Returns recording state. + */ + this.getState = function() { + if (!mediaRecorder) { + return 'inactive'; + } + + return mediaRecorder.state || 'inactive'; + }; + + // list of all recording states + var allStates = []; + + /** + * Get MediaRecorder all recording states. + * @method + * @memberof MediaStreamRecorder + * @example + * var state = recorder.getAllStates(); + * @returns {Array} Returns all recording states + */ + this.getAllStates = function() { + return allStates; + }; + + // if any Track within the MediaStream is muted or not enabled at any time, + // the browser will only record black frames + // or silence since that is the content produced by the Track + // so we need to stopRecording as soon as any single track ends. + if (typeof config.checkForInactiveTracks === 'undefined') { + config.checkForInactiveTracks = false; // disable to minimize CPU usage + } + + var self = this; + + // this method checks if media stream is stopped + // or if any track is ended. + (function looper() { + if (!mediaRecorder || config.checkForInactiveTracks === false) { + return; + } + + if (isMediaStreamActive() === false) { + if (!config.disableLogs) { + console.log('MediaStream seems stopped.'); + } + self.stop(); + return; + } + + setTimeout(looper, 1000); // check every second + })(); + + // for debugging + this.name = 'MediaStreamRecorder'; + this.toString = function() { + return this.name; + }; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.MediaStreamRecorder = MediaStreamRecorder; +} + +// source code from: http://typedarray.org/wp-content/projects/WebAudioRecorder/script.js +// https://github.com/mattdiamond/Recorderjs#license-mit +// ______________________ +// StereoAudioRecorder.js + +/** + * StereoAudioRecorder is a standalone class used by {@link RecordRTC} to bring "stereo" audio-recording in chrome. + * @summary JavaScript standalone object for stereo audio recording. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef StereoAudioRecorder + * @class + * @example + * var recorder = new StereoAudioRecorder(MediaStream, { + * sampleRate: 44100, + * bufferSize: 4096 + * }); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {sampleRate: 44100, bufferSize: 4096, numberOfAudioChannels: 1, etc.} + */ + +function StereoAudioRecorder(mediaStream, config) { + if (!getTracks(mediaStream, 'audio').length) { + throw 'Your stream has no audio tracks.'; + } + + config = config || {}; + + var self = this; + + // variables + var leftchannel = []; + var rightchannel = []; + var recording = false; + var recordingLength = 0; + var jsAudioNode; + + var numberOfAudioChannels = 2; + + /** + * Set sample rates such as 8K or 16K. Reference: http://stackoverflow.com/a/28977136/552182 + * @property {number} desiredSampRate - Desired Bits per sample * 1000 + * @memberof StereoAudioRecorder + * @instance + * @example + * var recorder = StereoAudioRecorder(mediaStream, { + * desiredSampRate: 16 * 1000 // bits-per-sample * 1000 + * }); + */ + var desiredSampRate = config.desiredSampRate; + + // backward compatibility + if (config.leftChannel === true) { + numberOfAudioChannels = 1; + } + + if (config.numberOfAudioChannels === 1) { + numberOfAudioChannels = 1; + } + + if (!numberOfAudioChannels || numberOfAudioChannels < 1) { + numberOfAudioChannels = 2; + } + + if (!config.disableLogs) { + console.log('StereoAudioRecorder is set to record number of channels: ' + numberOfAudioChannels); + } + + // if any Track within the MediaStream is muted or not enabled at any time, + // the browser will only record black frames + // or silence since that is the content produced by the Track + // so we need to stopRecording as soon as any single track ends. + if (typeof config.checkForInactiveTracks === 'undefined') { + config.checkForInactiveTracks = true; + } + + function isMediaStreamActive() { + if (config.checkForInactiveTracks === false) { + // always return "true" + return true; + } + + if ('active' in mediaStream) { + if (!mediaStream.active) { + return false; + } + } else if ('ended' in mediaStream) { // old hack + if (mediaStream.ended) { + return false; + } + } + return true; + } + + /** + * This method records MediaStream. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.record(); + */ + this.record = function() { + if (isMediaStreamActive() === false) { + throw 'Please make sure MediaStream is active.'; + } + + resetVariables(); + + isAudioProcessStarted = isPaused = false; + recording = true; + + if (typeof config.timeSlice !== 'undefined') { + looper(); + } + }; + + function mergeLeftRightBuffers(config, callback) { + function mergeAudioBuffers(config, cb) { + var numberOfAudioChannels = config.numberOfAudioChannels; + + // todo: "slice(0)" --- is it causes loop? Should be removed? + var leftBuffers = config.leftBuffers.slice(0); + var rightBuffers = config.rightBuffers.slice(0); + var sampleRate = config.sampleRate; + var internalInterleavedLength = config.internalInterleavedLength; + var desiredSampRate = config.desiredSampRate; + + if (numberOfAudioChannels === 2) { + leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); + rightBuffers = mergeBuffers(rightBuffers, internalInterleavedLength); + + if (desiredSampRate) { + leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); + rightBuffers = interpolateArray(rightBuffers, desiredSampRate, sampleRate); + } + } + + if (numberOfAudioChannels === 1) { + leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); + + if (desiredSampRate) { + leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); + } + } + + // set sample rate as desired sample rate + if (desiredSampRate) { + sampleRate = desiredSampRate; + } + + // for changing the sampling rate, reference: + // http://stackoverflow.com/a/28977136/552182 + function interpolateArray(data, newSampleRate, oldSampleRate) { + var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate)); + var newData = []; + var springFactor = Number((data.length - 1) / (fitCount - 1)); + newData[0] = data[0]; + for (var i = 1; i < fitCount - 1; i++) { + var tmp = i * springFactor; + var before = Number(Math.floor(tmp)).toFixed(); + var after = Number(Math.ceil(tmp)).toFixed(); + var atPoint = tmp - before; + newData[i] = linearInterpolate(data[before], data[after], atPoint); + } + newData[fitCount - 1] = data[data.length - 1]; + return newData; + } + + function linearInterpolate(before, after, atPoint) { + return before + (after - before) * atPoint; + } + + function mergeBuffers(channelBuffer, rLength) { + var result = new Float64Array(rLength); + var offset = 0; + var lng = channelBuffer.length; + + for (var i = 0; i < lng; i++) { + var buffer = channelBuffer[i]; + result.set(buffer, offset); + offset += buffer.length; + } + + return result; + } + + function interleave(leftChannel, rightChannel) { + var length = leftChannel.length + rightChannel.length; + + var result = new Float64Array(length); + + var inputIndex = 0; + + for (var index = 0; index < length;) { + result[index++] = leftChannel[inputIndex]; + result[index++] = rightChannel[inputIndex]; + inputIndex++; + } + return result; + } + + function writeUTFBytes(view, offset, string) { + var lng = string.length; + for (var i = 0; i < lng; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } + } + + // interleave both channels together + var interleaved; + + if (numberOfAudioChannels === 2) { + interleaved = interleave(leftBuffers, rightBuffers); + } + + if (numberOfAudioChannels === 1) { + interleaved = leftBuffers; + } + + var interleavedLength = interleaved.length; + + // create wav file + var resultingBufferLength = 44 + interleavedLength * 2; + + var buffer = new ArrayBuffer(resultingBufferLength); + + var view = new DataView(buffer); + + // RIFF chunk descriptor/identifier + writeUTFBytes(view, 0, 'RIFF'); + + // RIFF chunk length + // changed "44" to "36" via #401 + view.setUint32(4, 36 + interleavedLength * 2, true); + + // RIFF type + writeUTFBytes(view, 8, 'WAVE'); + + // format chunk identifier + // FMT sub-chunk + writeUTFBytes(view, 12, 'fmt '); + + // format chunk length + view.setUint32(16, 16, true); + + // sample format (raw) + view.setUint16(20, 1, true); + + // stereo (2 channels) + view.setUint16(22, numberOfAudioChannels, true); + + // sample rate + view.setUint32(24, sampleRate, true); + + // byte rate (sample rate * block align) + view.setUint32(28, sampleRate * 2, true); + + // block align (channel count * bytes per sample) + view.setUint16(32, numberOfAudioChannels * 2, true); + + // bits per sample + view.setUint16(34, 16, true); + + // data sub-chunk + // data chunk identifier + writeUTFBytes(view, 36, 'data'); + + // data chunk length + view.setUint32(40, interleavedLength * 2, true); + + // write the PCM samples + var lng = interleavedLength; + var index = 44; + var volume = 1; + for (var i = 0; i < lng; i++) { + view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); + index += 2; + } + + if (cb) { + return cb({ + buffer: buffer, + view: view + }); + } + + postMessage({ + buffer: buffer, + view: view + }); + } + + if (config.noWorker) { + mergeAudioBuffers(config, function(data) { + callback(data.buffer, data.view); + }); + return; + } + + + var webWorker = processInWebWorker(mergeAudioBuffers); + + webWorker.onmessage = function(event) { + callback(event.data.buffer, event.data.view); + + // release memory + URL.revokeObjectURL(webWorker.workerURL); + + // kill webworker (or Chrome will kill your page after ~25 calls) + webWorker.terminate(); + }; + + webWorker.postMessage(config); + } + + function processInWebWorker(_function) { + var workerURL = URL.createObjectURL(new Blob([_function.toString(), + ';this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(workerURL); + worker.workerURL = workerURL; + return worker; + } + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + // stop recording + recording = false; + + mergeLeftRightBuffers({ + desiredSampRate: desiredSampRate, + sampleRate: sampleRate, + numberOfAudioChannels: numberOfAudioChannels, + internalInterleavedLength: recordingLength, + leftBuffers: leftchannel, + rightBuffers: numberOfAudioChannels === 1 ? [] : rightchannel, + noWorker: config.noWorker + }, function(buffer, view) { + /** + * @property {Blob} blob - The recorded blob object. + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(){ + * var blob = recorder.blob; + * }); + */ + self.blob = new Blob([view], { + type: 'audio/wav' + }); + + /** + * @property {ArrayBuffer} buffer - The recorded buffer object. + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(){ + * var buffer = recorder.buffer; + * }); + */ + self.buffer = new ArrayBuffer(view.buffer.byteLength); + + /** + * @property {DataView} view - The recorded data-view object. + * @memberof StereoAudioRecorder + * @example + * recorder.stop(function(){ + * var view = recorder.view; + * }); + */ + self.view = view; + + self.sampleRate = desiredSampRate || sampleRate; + self.bufferSize = bufferSize; + + // recorded audio length + self.length = recordingLength; + + isAudioProcessStarted = false; + + if (callback) { + callback(self.blob); + } + }); + }; + + if (typeof Storage === 'undefined') { + var Storage = { + AudioContextConstructor: null, + AudioContext: window.AudioContext || window.webkitAudioContext + }; + } + + if (!Storage.AudioContextConstructor) { + Storage.AudioContextConstructor = new Storage.AudioContext(); + } + + var context = Storage.AudioContextConstructor; + + // creates an audio node from the microphone incoming stream + var audioInput = context.createMediaStreamSource(mediaStream); + + var legalBufferValues = [0, 256, 512, 1024, 2048, 4096, 8192, 16384]; + + /** + * From the spec: This value controls how frequently the audioprocess event is + * dispatched and how many sample-frames need to be processed each call. + * Lower values for buffer size will result in a lower (better) latency. + * Higher values will be necessary to avoid audio breakup and glitches + * The size of the buffer (in sample-frames) which needs to + * be processed each time onprocessaudio is called. + * Legal values are (256, 512, 1024, 2048, 4096, 8192, 16384). + * @property {number} bufferSize - Buffer-size for how frequently the audioprocess event is dispatched. + * @memberof StereoAudioRecorder + * @example + * recorder = new StereoAudioRecorder(mediaStream, { + * bufferSize: 4096 + * }); + */ + + // "0" means, let chrome decide the most accurate buffer-size for current platform. + var bufferSize = typeof config.bufferSize === 'undefined' ? 4096 : config.bufferSize; + + if (legalBufferValues.indexOf(bufferSize) === -1) { + if (!config.disableLogs) { + console.log('Legal values for buffer-size are ' + JSON.stringify(legalBufferValues, null, '\t')); + } + } + + if (context.createJavaScriptNode) { + jsAudioNode = context.createJavaScriptNode(bufferSize, numberOfAudioChannels, numberOfAudioChannels); + } else if (context.createScriptProcessor) { + jsAudioNode = context.createScriptProcessor(bufferSize, numberOfAudioChannels, numberOfAudioChannels); + } else { + throw 'WebAudio API has no support on this browser.'; + } + + // connect the stream to the script processor + audioInput.connect(jsAudioNode); + + if (!config.bufferSize) { + bufferSize = jsAudioNode.bufferSize; // device buffer-size + } + + /** + * The sample rate (in sample-frames per second) at which the + * AudioContext handles audio. It is assumed that all AudioNodes + * in the context run at this rate. In making this assumption, + * sample-rate converters or "varispeed" processors are not supported + * in real-time processing. + * The sampleRate parameter describes the sample-rate of the + * linear PCM audio data in the buffer in sample-frames per second. + * An implementation must support sample-rates in at least + * the range 22050 to 96000. + * @property {number} sampleRate - Buffer-size for how frequently the audioprocess event is dispatched. + * @memberof StereoAudioRecorder + * @example + * recorder = new StereoAudioRecorder(mediaStream, { + * sampleRate: 44100 + * }); + */ + var sampleRate = typeof config.sampleRate !== 'undefined' ? config.sampleRate : context.sampleRate || 44100; + + if (sampleRate < 22050 || sampleRate > 96000) { + // Ref: http://stackoverflow.com/a/26303918/552182 + if (!config.disableLogs) { + console.log('sample-rate must be under range 22050 and 96000.'); + } + } + + if (!config.disableLogs) { + if (config.desiredSampRate) { + console.log('Desired sample-rate: ' + config.desiredSampRate); + } + } + + var isPaused = false; + /** + * This method pauses the recording process. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPaused = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + if (isMediaStreamActive() === false) { + throw 'Please make sure MediaStream is active.'; + } + + if (!recording) { + if (!config.disableLogs) { + console.log('Seems recording has been restarted.'); + } + this.record(); + return; + } + + isPaused = false; + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof StereoAudioRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + config.checkForInactiveTracks = false; + + if (recording) { + this.stop(clearRecordedDataCB); + } + + clearRecordedDataCB(); + }; + + function resetVariables() { + leftchannel = []; + rightchannel = []; + recordingLength = 0; + isAudioProcessStarted = false; + recording = false; + isPaused = false; + context = null; + + self.leftchannel = leftchannel; + self.rightchannel = rightchannel; + self.numberOfAudioChannels = numberOfAudioChannels; + self.desiredSampRate = desiredSampRate; + self.sampleRate = sampleRate; + self.recordingLength = recordingLength; + + intervalsBasedBuffers = { + left: [], + right: [], + recordingLength: 0 + }; + } + + function clearRecordedDataCB() { + if (jsAudioNode) { + jsAudioNode.onaudioprocess = null; + jsAudioNode.disconnect(); + jsAudioNode = null; + } + + if (audioInput) { + audioInput.disconnect(); + audioInput = null; + } + + resetVariables(); + } + + // for debugging + this.name = 'StereoAudioRecorder'; + this.toString = function() { + return this.name; + }; + + var isAudioProcessStarted = false; + + function onAudioProcessDataAvailable(e) { + if (isPaused) { + return; + } + + if (isMediaStreamActive() === false) { + if (!config.disableLogs) { + console.log('MediaStream seems stopped.'); + } + jsAudioNode.disconnect(); + recording = false; + } + + if (!recording) { + if (audioInput) { + audioInput.disconnect(); + audioInput = null; + } + return; + } + + /** + * This method is called on "onaudioprocess" event's first invocation. + * @method {function} onAudioProcessStarted + * @memberof StereoAudioRecorder + * @example + * recorder.onAudioProcessStarted: function() { }; + */ + if (!isAudioProcessStarted) { + isAudioProcessStarted = true; + if (config.onAudioProcessStarted) { + config.onAudioProcessStarted(); + } + + if (config.initCallback) { + config.initCallback(); + } + } + + var left = e.inputBuffer.getChannelData(0); + + // we clone the samples + var chLeft = new Float32Array(left); + leftchannel.push(chLeft); + + if (numberOfAudioChannels === 2) { + var right = e.inputBuffer.getChannelData(1); + var chRight = new Float32Array(right); + rightchannel.push(chRight); + } + + recordingLength += bufferSize; + + // export raw PCM + self.recordingLength = recordingLength; + + if (typeof config.timeSlice !== 'undefined') { + intervalsBasedBuffers.recordingLength += bufferSize; + intervalsBasedBuffers.left.push(chLeft); + + if (numberOfAudioChannels === 2) { + intervalsBasedBuffers.right.push(chRight); + } + } + } + + jsAudioNode.onaudioprocess = onAudioProcessDataAvailable; + + // to prevent self audio to be connected with speakers + if (context.createMediaStreamDestination) { + jsAudioNode.connect(context.createMediaStreamDestination()); + } else { + jsAudioNode.connect(context.destination); + } + + // export raw PCM + this.leftchannel = leftchannel; + this.rightchannel = rightchannel; + this.numberOfAudioChannels = numberOfAudioChannels; + this.desiredSampRate = desiredSampRate; + this.sampleRate = sampleRate; + self.recordingLength = recordingLength; + + // helper for intervals based blobs + var intervalsBasedBuffers = { + left: [], + right: [], + recordingLength: 0 + }; + + // this looper is used to support intervals based blobs (via timeSlice+ondataavailable) + function looper() { + if (!recording || typeof config.ondataavailable !== 'function' || typeof config.timeSlice === 'undefined') { + return; + } + + if (intervalsBasedBuffers.left.length) { + mergeLeftRightBuffers({ + desiredSampRate: desiredSampRate, + sampleRate: sampleRate, + numberOfAudioChannels: numberOfAudioChannels, + internalInterleavedLength: intervalsBasedBuffers.recordingLength, + leftBuffers: intervalsBasedBuffers.left, + rightBuffers: numberOfAudioChannels === 1 ? [] : intervalsBasedBuffers.right + }, function(buffer, view) { + var blob = new Blob([view], { + type: 'audio/wav' + }); + config.ondataavailable(blob); + + setTimeout(looper, config.timeSlice); + }); + + intervalsBasedBuffers = { + left: [], + right: [], + recordingLength: 0 + }; + } else { + setTimeout(looper, config.timeSlice); + } + } +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.StereoAudioRecorder = StereoAudioRecorder; +} + +// _________________ +// CanvasRecorder.js + +/** + * CanvasRecorder is a standalone class used by {@link RecordRTC} to bring HTML5-Canvas recording into video WebM. It uses HTML2Canvas library and runs top over {@link Whammy}. + * @summary HTML2Canvas recording into video WebM. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef CanvasRecorder + * @class + * @example + * var recorder = new CanvasRecorder(htmlElement, { disableLogs: true, useWhammyRecorder: true }); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {HTMLElement} htmlElement - querySelector/getElementById/getElementsByTagName[0]/etc. + * @param {object} config - {disableLogs:true, initCallback: function} + */ + +function CanvasRecorder(htmlElement, config) { + if (typeof html2canvas === 'undefined') { + throw 'Please link: https://cdn.webrtc-experiment.com/screenshot.js'; + } + + config = config || {}; + if (!config.frameInterval) { + config.frameInterval = 10; + } + + // via DetectRTC.js + var isCanvasSupportsStreamCapturing = false; + ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) { + if (item in document.createElement('canvas')) { + isCanvasSupportsStreamCapturing = true; + } + }); + + var _isChrome = (!!window.webkitRTCPeerConnection || !!window.webkitGetUserMedia) && !!window.chrome; + + var chromeVersion = 50; + var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); + if (_isChrome && matchArray && matchArray[2]) { + chromeVersion = parseInt(matchArray[2], 10); + } + + if (_isChrome && chromeVersion < 52) { + isCanvasSupportsStreamCapturing = false; + } + + if (config.useWhammyRecorder) { + isCanvasSupportsStreamCapturing = false; + } + + var globalCanvas, mediaStreamRecorder; + + if (isCanvasSupportsStreamCapturing) { + if (!config.disableLogs) { + console.log('Your browser supports both MediRecorder API and canvas.captureStream!'); + } + + if (htmlElement instanceof HTMLCanvasElement) { + globalCanvas = htmlElement; + } else if (htmlElement instanceof CanvasRenderingContext2D) { + globalCanvas = htmlElement.canvas; + } else { + throw 'Please pass either HTMLCanvasElement or CanvasRenderingContext2D.'; + } + } else if (!!navigator.mozGetUserMedia) { + if (!config.disableLogs) { + console.error('Canvas recording is NOT supported in Firefox.'); + } + } + + var isRecording; + + /** + * This method records Canvas. + * @method + * @memberof CanvasRecorder + * @example + * recorder.record(); + */ + this.record = function() { + isRecording = true; + + if (isCanvasSupportsStreamCapturing && !config.useWhammyRecorder) { + // CanvasCaptureMediaStream + var canvasMediaStream; + if ('captureStream' in globalCanvas) { + canvasMediaStream = globalCanvas.captureStream(25); // 25 FPS + } else if ('mozCaptureStream' in globalCanvas) { + canvasMediaStream = globalCanvas.mozCaptureStream(25); + } else if ('webkitCaptureStream' in globalCanvas) { + canvasMediaStream = globalCanvas.webkitCaptureStream(25); + } + + try { + var mdStream = new MediaStream(); + mdStream.addTrack(getTracks(canvasMediaStream, 'video')[0]); + canvasMediaStream = mdStream; + } catch (e) {} + + if (!canvasMediaStream) { + throw 'captureStream API are NOT available.'; + } + + // Note: Jan 18, 2016 status is that, + // Firefox MediaRecorder API can't record CanvasCaptureMediaStream object. + mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, { + mimeType: config.mimeType || 'video/webm' + }); + mediaStreamRecorder.record(); + } else { + whammy.frames = []; + lastTime = new Date().getTime(); + drawCanvasFrame(); + } + + if (config.initCallback) { + config.initCallback(); + } + }; + + this.getWebPImages = function(callback) { + if (htmlElement.nodeName.toLowerCase() !== 'canvas') { + callback(); + return; + } + + var framesLength = whammy.frames.length; + whammy.frames.forEach(function(frame, idx) { + var framesRemaining = framesLength - idx; + if (!config.disableLogs) { + console.log(framesRemaining + '/' + framesLength + ' frames remaining'); + } + + if (config.onEncodingCallback) { + config.onEncodingCallback(framesRemaining, framesLength); + } + + var webp = frame.image.toDataURL('image/webp', 1); + whammy.frames[idx].image = webp; + }); + + if (!config.disableLogs) { + console.log('Generating WebM'); + } + + callback(); + }; + + /** + * This method stops recording Canvas. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof CanvasRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + isRecording = false; + + var that = this; + + if (isCanvasSupportsStreamCapturing && mediaStreamRecorder) { + mediaStreamRecorder.stop(callback); + return; + } + + this.getWebPImages(function() { + /** + * @property {Blob} blob - Recorded frames in video/webm blob. + * @memberof CanvasRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + whammy.compile(function(blob) { + if (!config.disableLogs) { + console.log('Recording finished!'); + } + + that.blob = blob; + + if (that.blob.forEach) { + that.blob = new Blob([], { + type: 'video/webm' + }); + } + + if (callback) { + callback(that.blob); + } + + whammy.frames = []; + }); + }); + }; + + var isPausedRecording = false; + + /** + * This method pauses the recording process. + * @method + * @memberof CanvasRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPausedRecording = true; + + if (mediaStreamRecorder instanceof MediaStreamRecorder) { + mediaStreamRecorder.pause(); + return; + } + }; + + /** + * This method resumes the recording process. + * @method + * @memberof CanvasRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPausedRecording = false; + + if (mediaStreamRecorder instanceof MediaStreamRecorder) { + mediaStreamRecorder.resume(); + return; + } + + if (!isRecording) { + this.record(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof CanvasRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (isRecording) { + this.stop(clearRecordedDataCB); + } + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + whammy.frames = []; + isRecording = false; + isPausedRecording = false; + } + + // for debugging + this.name = 'CanvasRecorder'; + this.toString = function() { + return this.name; + }; + + function cloneCanvas() { + //create a new canvas + var newCanvas = document.createElement('canvas'); + var context = newCanvas.getContext('2d'); + + //set dimensions + newCanvas.width = htmlElement.width; + newCanvas.height = htmlElement.height; + + //apply the old canvas to the new one + context.drawImage(htmlElement, 0, 0); + + //return the new canvas + return newCanvas; + } + + function drawCanvasFrame() { + if (isPausedRecording) { + lastTime = new Date().getTime(); + return setTimeout(drawCanvasFrame, 500); + } + + if (htmlElement.nodeName.toLowerCase() === 'canvas') { + var duration = new Date().getTime() - lastTime; + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); + + whammy.frames.push({ + image: cloneCanvas(), + duration: duration + }); + + if (isRecording) { + setTimeout(drawCanvasFrame, config.frameInterval); + } + return; + } + + html2canvas(htmlElement, { + grabMouse: typeof config.showMousePointer === 'undefined' || config.showMousePointer, + onrendered: function(canvas) { + var duration = new Date().getTime() - lastTime; + if (!duration) { + return setTimeout(drawCanvasFrame, config.frameInterval); + } + + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); + + whammy.frames.push({ + image: canvas.toDataURL('image/webp', 1), + duration: duration + }); + + if (isRecording) { + setTimeout(drawCanvasFrame, config.frameInterval); + } + } + }); + } + + var lastTime = new Date().getTime(); + + var whammy = new Whammy.Video(100); +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.CanvasRecorder = CanvasRecorder; +} + +// _________________ +// WhammyRecorder.js + +/** + * WhammyRecorder is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It runs top over {@link Whammy}. + * @summary Video recording feature in Chrome. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef WhammyRecorder + * @class + * @example + * var recorder = new WhammyRecorder(mediaStream); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {disableLogs: true, initCallback: function, video: HTMLVideoElement, etc.} + */ + +function WhammyRecorder(mediaStream, config) { + + config = config || {}; + + if (!config.frameInterval) { + config.frameInterval = 10; + } + + if (!config.disableLogs) { + console.log('Using frames-interval:', config.frameInterval); + } + + /** + * This method records video. + * @method + * @memberof WhammyRecorder + * @example + * recorder.record(); + */ + this.record = function() { + if (!config.width) { + config.width = 320; + } + + if (!config.height) { + config.height = 240; + } + + if (!config.video) { + config.video = { + width: config.width, + height: config.height + }; + } + + if (!config.canvas) { + config.canvas = { + width: config.width, + height: config.height + }; + } + + canvas.width = config.canvas.width || 320; + canvas.height = config.canvas.height || 240; + + context = canvas.getContext('2d'); + + // setting defaults + if (config.video && config.video instanceof HTMLVideoElement) { + video = config.video.cloneNode(); + + if (config.initCallback) { + config.initCallback(); + } + } else { + video = document.createElement('video'); + + setSrcObject(mediaStream, video); + + video.onloadedmetadata = function() { // "onloadedmetadata" may NOT work in FF? + if (config.initCallback) { + config.initCallback(); + } + }; + + video.width = config.video.width; + video.height = config.video.height; + } + + video.muted = true; + video.play(); + + lastTime = new Date().getTime(); + whammy = new Whammy.Video(); + + if (!config.disableLogs) { + console.log('canvas resolutions', canvas.width, '*', canvas.height); + console.log('video width/height', video.width || canvas.width, '*', video.height || canvas.height); + } + + drawFrames(config.frameInterval); + }; + + /** + * Draw and push frames to Whammy + * @param {integer} frameInterval - set minimum interval (in milliseconds) between each time we push a frame to Whammy + */ + function drawFrames(frameInterval) { + frameInterval = typeof frameInterval !== 'undefined' ? frameInterval : 10; + + var duration = new Date().getTime() - lastTime; + if (!duration) { + return setTimeout(drawFrames, frameInterval, frameInterval); + } + + if (isPausedRecording) { + lastTime = new Date().getTime(); + return setTimeout(drawFrames, 100); + } + + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); + + if (video.paused) { + // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 + // Tweak for Android Chrome + video.play(); + } + + context.drawImage(video, 0, 0, canvas.width, canvas.height); + whammy.frames.push({ + duration: duration, + image: canvas.toDataURL('image/webp') + }); + + if (!isStopDrawing) { + setTimeout(drawFrames, frameInterval, frameInterval); + } + } + + function asyncLoop(o) { + var i = -1, + length = o.length; + + (function loop() { + i++; + if (i === length) { + o.callback(); + return; + } + + // "setTimeout" added by Jim McLeod + setTimeout(function() { + o.functionToLoop(loop, i); + }, 1); + })(); + } + + + /** + * remove black frames from the beginning to the specified frame + * @param {Array} _frames - array of frames to be checked + * @param {number} _framesToCheck - number of frame until check will be executed (-1 - will drop all frames until frame not matched will be found) + * @param {number} _pixTolerance - 0 - very strict (only black pixel color) ; 1 - all + * @param {number} _frameTolerance - 0 - very strict (only black frame color) ; 1 - all + * @returns {Array} - array of frames + */ + // pull#293 by @volodalexey + function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance, callback) { + var localCanvas = document.createElement('canvas'); + localCanvas.width = canvas.width; + localCanvas.height = canvas.height; + var context2d = localCanvas.getContext('2d'); + var resultFrames = []; + + var checkUntilNotBlack = _framesToCheck === -1; + var endCheckFrame = (_framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length) ? + _framesToCheck : _frames.length; + var sampleColor = { + r: 0, + g: 0, + b: 0 + }; + var maxColorDifference = Math.sqrt( + Math.pow(255, 2) + + Math.pow(255, 2) + + Math.pow(255, 2) + ); + var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0; + var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0; + var doNotCheckNext = false; + + asyncLoop({ + length: endCheckFrame, + functionToLoop: function(loop, f) { + var matchPixCount, endPixCheck, maxPixCount; + + var finishImage = function() { + if (!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance) { + // console.log('removed black frame : ' + f + ' ; frame duration ' + _frames[f].duration); + } else { + // console.log('frame is passed : ' + f); + if (checkUntilNotBlack) { + doNotCheckNext = true; + } + resultFrames.push(_frames[f]); + } + loop(); + }; + + if (!doNotCheckNext) { + var image = new Image(); + image.onload = function() { + context2d.drawImage(image, 0, 0, canvas.width, canvas.height); + var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height); + matchPixCount = 0; + endPixCheck = imageData.data.length; + maxPixCount = imageData.data.length / 4; + + for (var pix = 0; pix < endPixCheck; pix += 4) { + var currentColor = { + r: imageData.data[pix], + g: imageData.data[pix + 1], + b: imageData.data[pix + 2] + }; + var colorDifference = Math.sqrt( + Math.pow(currentColor.r - sampleColor.r, 2) + + Math.pow(currentColor.g - sampleColor.g, 2) + + Math.pow(currentColor.b - sampleColor.b, 2) + ); + // difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2) + if (colorDifference <= maxColorDifference * pixTolerance) { + matchPixCount++; + } + } + finishImage(); + }; + image.src = _frames[f].image; + } else { + finishImage(); + } + }, + callback: function() { + resultFrames = resultFrames.concat(_frames.slice(endCheckFrame)); + + if (resultFrames.length <= 0) { + // at least one last frame should be available for next manipulation + // if total duration of all frames will be < 1000 than ffmpeg doesn't work well... + resultFrames.push(_frames[_frames.length - 1]); + } + callback(resultFrames); + } + }); + } + + var isStopDrawing = false; + + /** + * This method stops recording video. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof WhammyRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + isStopDrawing = true; + + var _this = this; + // analyse of all frames takes some time! + setTimeout(function() { + // e.g. dropBlackFrames(frames, 10, 1, 1) - will cut all 10 frames + // e.g. dropBlackFrames(frames, 10, 0.5, 0.5) - will analyse 10 frames + // e.g. dropBlackFrames(frames, 10) === dropBlackFrames(frames, 10, 0, 0) - will analyse 10 frames with strict black color + dropBlackFrames(whammy.frames, -1, null, null, function(frames) { + whammy.frames = frames; + + // to display advertisement images! + if (config.advertisement && config.advertisement.length) { + whammy.frames = config.advertisement.concat(whammy.frames); + } + + /** + * @property {Blob} blob - Recorded frames in video/webm blob. + * @memberof WhammyRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + whammy.compile(function(blob) { + _this.blob = blob; + + if (_this.blob.forEach) { + _this.blob = new Blob([], { + type: 'video/webm' + }); + } + + if (callback) { + callback(_this.blob); + } + }); + }); + }, 10); + }; + + var isPausedRecording = false; + + /** + * This method pauses the recording process. + * @method + * @memberof WhammyRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPausedRecording = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof WhammyRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPausedRecording = false; + + if (isStopDrawing) { + this.record(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof WhammyRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (!isStopDrawing) { + this.stop(clearRecordedDataCB); + } + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + whammy.frames = []; + isStopDrawing = true; + isPausedRecording = false; + } + + // for debugging + this.name = 'WhammyRecorder'; + this.toString = function() { + return this.name; + }; + + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + + var video; + var lastTime; + var whammy; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.WhammyRecorder = WhammyRecorder; +} + +// https://github.com/antimatter15/whammy/blob/master/LICENSE +// _________ +// Whammy.js + +// todo: Firefox now supports webp for webm containers! +// their MediaRecorder implementation works well! +// should we provide an option to record via Whammy.js or MediaRecorder API is a better solution? + +/** + * Whammy is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It is written by {@link https://github.com/antimatter15|antimatter15} + * @summary A real time javascript webm encoder based on a canvas hack. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef Whammy + * @class + * @example + * var recorder = new Whammy().Video(15); + * recorder.add(context || canvas || dataURL); + * var output = recorder.compile(); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ + +var Whammy = (function() { + // a more abstract-ish API + + function WhammyVideo(duration) { + this.frames = []; + this.duration = duration || 1; + this.quality = 0.8; + } + + /** + * Pass Canvas or Context or image/webp(string) to {@link Whammy} encoder. + * @method + * @memberof Whammy + * @example + * recorder = new Whammy().Video(0.8, 100); + * recorder.add(canvas || context || 'image/webp'); + * @param {string} frame - Canvas || Context || image/webp + * @param {number} duration - Stick a duration (in milliseconds) + */ + WhammyVideo.prototype.add = function(frame, duration) { + if ('canvas' in frame) { //CanvasRenderingContext2D + frame = frame.canvas; + } + + if ('toDataURL' in frame) { + frame = frame.toDataURL('image/webp', this.quality); + } + + if (!(/^data:image\/webp;base64,/ig).test(frame)) { + throw 'Input must be formatted properly as a base64 encoded DataURI of type image/webp'; + } + this.frames.push({ + image: frame, + duration: duration || this.duration + }); + }; + + function processInWebWorker(_function) { + var blob = URL.createObjectURL(new Blob([_function.toString(), + 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' + ], { + type: 'application/javascript' + })); + + var worker = new Worker(blob); + URL.revokeObjectURL(blob); + return worker; + } + + function whammyInWebWorker(frames) { + function ArrayToWebM(frames) { + var info = checkFrames(frames); + if (!info) { + return []; + } + + var clusterMaxDuration = 30000; + + var EBML = [{ + 'id': 0x1a45dfa3, // EBML + 'data': [{ + 'data': 1, + 'id': 0x4286 // EBMLVersion + }, { + 'data': 1, + 'id': 0x42f7 // EBMLReadVersion + }, { + 'data': 4, + 'id': 0x42f2 // EBMLMaxIDLength + }, { + 'data': 8, + 'id': 0x42f3 // EBMLMaxSizeLength + }, { + 'data': 'webm', + 'id': 0x4282 // DocType + }, { + 'data': 2, + 'id': 0x4287 // DocTypeVersion + }, { + 'data': 2, + 'id': 0x4285 // DocTypeReadVersion + }] + }, { + 'id': 0x18538067, // Segment + 'data': [{ + 'id': 0x1549a966, // Info + 'data': [{ + 'data': 1e6, //do things in millisecs (num of nanosecs for duration scale) + 'id': 0x2ad7b1 // TimecodeScale + }, { + 'data': 'whammy', + 'id': 0x4d80 // MuxingApp + }, { + 'data': 'whammy', + 'id': 0x5741 // WritingApp + }, { + 'data': doubleToString(info.duration), + 'id': 0x4489 // Duration + }] + }, { + 'id': 0x1654ae6b, // Tracks + 'data': [{ + 'id': 0xae, // TrackEntry + 'data': [{ + 'data': 1, + 'id': 0xd7 // TrackNumber + }, { + 'data': 1, + 'id': 0x73c5 // TrackUID + }, { + 'data': 0, + 'id': 0x9c // FlagLacing + }, { + 'data': 'und', + 'id': 0x22b59c // Language + }, { + 'data': 'V_VP8', + 'id': 0x86 // CodecID + }, { + 'data': 'VP8', + 'id': 0x258688 // CodecName + }, { + 'data': 1, + 'id': 0x83 // TrackType + }, { + 'id': 0xe0, // Video + 'data': [{ + 'data': info.width, + 'id': 0xb0 // PixelWidth + }, { + 'data': info.height, + 'id': 0xba // PixelHeight + }] + }] + }] + }] + }]; + + //Generate clusters (max duration) + var frameNumber = 0; + var clusterTimecode = 0; + while (frameNumber < frames.length) { + + var clusterFrames = []; + var clusterDuration = 0; + do { + clusterFrames.push(frames[frameNumber]); + clusterDuration += frames[frameNumber].duration; + frameNumber++; + } while (frameNumber < frames.length && clusterDuration < clusterMaxDuration); + + var clusterCounter = 0; + var cluster = { + 'id': 0x1f43b675, // Cluster + 'data': getClusterData(clusterTimecode, clusterCounter, clusterFrames) + }; //Add cluster to segment + EBML[1].data.push(cluster); + clusterTimecode += clusterDuration; + } + + return generateEBML(EBML); + } + + function getClusterData(clusterTimecode, clusterCounter, clusterFrames) { + return [{ + 'data': clusterTimecode, + 'id': 0xe7 // Timecode + }].concat(clusterFrames.map(function(webp) { + var block = makeSimpleBlock({ + discardable: 0, + frame: webp.data.slice(4), + invisible: 0, + keyframe: 1, + lacing: 0, + trackNum: 1, + timecode: Math.round(clusterCounter) + }); + clusterCounter += webp.duration; + return { + data: block, + id: 0xa3 + }; + })); + } + + // sums the lengths of all the frames and gets the duration + + function checkFrames(frames) { + if (!frames[0]) { + postMessage({ + error: 'Something went wrong. Maybe WebP format is not supported in the current browser.' + }); + return; + } + + var width = frames[0].width, + height = frames[0].height, + duration = frames[0].duration; + + for (var i = 1; i < frames.length; i++) { + duration += frames[i].duration; + } + return { + duration: duration, + width: width, + height: height + }; + } + + function numToBuffer(num) { + var parts = []; + while (num > 0) { + parts.push(num & 0xff); + num = num >> 8; + } + return new Uint8Array(parts.reverse()); + } + + function strToBuffer(str) { + return new Uint8Array(str.split('').map(function(e) { + return e.charCodeAt(0); + })); + } + + function bitsToBuffer(bits) { + var data = []; + var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; + bits = pad + bits; + for (var i = 0; i < bits.length; i += 8) { + data.push(parseInt(bits.substr(i, 8), 2)); + } + return new Uint8Array(data); + } + + function generateEBML(json) { + var ebml = []; + for (var i = 0; i < json.length; i++) { + var data = json[i].data; + + if (typeof data === 'object') { + data = generateEBML(data); + } + + if (typeof data === 'number') { + data = bitsToBuffer(data.toString(2)); + } + + if (typeof data === 'string') { + data = strToBuffer(data); + } + + var len = data.size || data.byteLength || data.length; + var zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8); + var sizeToString = len.toString(2); + var padded = (new Array((zeroes * 7 + 7 + 1) - sizeToString.length)).join('0') + sizeToString; + var size = (new Array(zeroes)).join('0') + '1' + padded; + + ebml.push(numToBuffer(json[i].id)); + ebml.push(bitsToBuffer(size)); + ebml.push(data); + } + + return new Blob(ebml, { + type: 'video/webm' + }); + } + + function toBinStrOld(bits) { + var data = ''; + var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; + bits = pad + bits; + for (var i = 0; i < bits.length; i += 8) { + data += String.fromCharCode(parseInt(bits.substr(i, 8), 2)); + } + return data; + } + + function makeSimpleBlock(data) { + var flags = 0; + + if (data.keyframe) { + flags |= 128; + } + + if (data.invisible) { + flags |= 8; + } + + if (data.lacing) { + flags |= (data.lacing << 1); + } + + if (data.discardable) { + flags |= 1; + } + + if (data.trackNum > 127) { + throw 'TrackNumber > 127 not supported'; + } + + var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e) { + return String.fromCharCode(e); + }).join('') + data.frame; + + return out; + } + + function parseWebP(riff) { + var VP8 = riff.RIFF[0].WEBP[0]; + + var frameStart = VP8.indexOf('\x9d\x01\x2a'); // A VP8 keyframe starts with the 0x9d012a header + for (var i = 0, c = []; i < 4; i++) { + c[i] = VP8.charCodeAt(frameStart + 3 + i); + } + + var width, height, tmp; + + //the code below is literally copied verbatim from the bitstream spec + tmp = (c[1] << 8) | c[0]; + width = tmp & 0x3FFF; + tmp = (c[3] << 8) | c[2]; + height = tmp & 0x3FFF; + return { + width: width, + height: height, + data: VP8, + riff: riff + }; + } + + function getStrLength(string, offset) { + return parseInt(string.substr(offset + 4, 4).split('').map(function(i) { + var unpadded = i.charCodeAt(0).toString(2); + return (new Array(8 - unpadded.length + 1)).join('0') + unpadded; + }).join(''), 2); + } + + function parseRIFF(string) { + var offset = 0; + var chunks = {}; + + while (offset < string.length) { + var id = string.substr(offset, 4); + var len = getStrLength(string, offset); + var data = string.substr(offset + 4 + 4, len); + offset += 4 + 4 + len; + chunks[id] = chunks[id] || []; + + if (id === 'RIFF' || id === 'LIST') { + chunks[id].push(parseRIFF(data)); + } else { + chunks[id].push(data); + } + } + return chunks; + } + + function doubleToString(num) { + return [].slice.call( + new Uint8Array((new Float64Array([num])).buffer), 0).map(function(e) { + return String.fromCharCode(e); + }).reverse().join(''); + } + + var webm = new ArrayToWebM(frames.map(function(frame) { + var webp = parseWebP(parseRIFF(atob(frame.image.slice(23)))); + webp.duration = frame.duration; + return webp; + })); + + postMessage(webm); + } + + /** + * Encodes frames in WebM container. It uses WebWorkinvoke to invoke 'ArrayToWebM' method. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof Whammy + * @example + * recorder = new Whammy().Video(0.8, 100); + * recorder.compile(function(blob) { + * // blob.size - blob.type + * }); + */ + WhammyVideo.prototype.compile = function(callback) { + var webWorker = processInWebWorker(whammyInWebWorker); + + webWorker.onmessage = function(event) { + if (event.data.error) { + console.error(event.data.error); + return; + } + callback(event.data); + }; + + webWorker.postMessage(this.frames); + }; + + return { + /** + * A more abstract-ish API. + * @method + * @memberof Whammy + * @example + * recorder = new Whammy().Video(0.8, 100); + * @param {?number} speed - 0.8 + * @param {?number} quality - 100 + */ + Video: WhammyVideo + }; +})(); + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.Whammy = Whammy; +} + +// ______________ (indexed-db) +// DiskStorage.js + +/** + * DiskStorage is a standalone object used by {@link RecordRTC} to store recorded blobs in IndexedDB storage. + * @summary Writing blobs into IndexedDB. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @example + * DiskStorage.Store({ + * audioBlob: yourAudioBlob, + * videoBlob: yourVideoBlob, + * gifBlob : yourGifBlob + * }); + * DiskStorage.Fetch(function(dataURL, type) { + * if(type === 'audioBlob') { } + * if(type === 'videoBlob') { } + * if(type === 'gifBlob') { } + * }); + * // DiskStorage.dataStoreName = 'recordRTC'; + * // DiskStorage.onError = function(error) { }; + * @property {function} init - This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. + * @property {function} Fetch - This method fetches stored blobs from IndexedDB. + * @property {function} Store - This method stores blobs in IndexedDB. + * @property {function} onError - This function is invoked for any known/unknown error. + * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + */ + + +var DiskStorage = { + /** + * This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.init(); + */ + init: function() { + var self = this; + + if (typeof indexedDB === 'undefined' || typeof indexedDB.open === 'undefined') { + console.error('IndexedDB API are not available in this browser.'); + return; + } + + var dbVersion = 1; + var dbName = this.dbName || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''), + db; + var request = indexedDB.open(dbName, dbVersion); + + function createObjectStore(dataBase) { + dataBase.createObjectStore(self.dataStoreName); + } + + function putInDB() { + var transaction = db.transaction([self.dataStoreName], 'readwrite'); + + if (self.videoBlob) { + transaction.objectStore(self.dataStoreName).put(self.videoBlob, 'videoBlob'); + } + + if (self.gifBlob) { + transaction.objectStore(self.dataStoreName).put(self.gifBlob, 'gifBlob'); + } + + if (self.audioBlob) { + transaction.objectStore(self.dataStoreName).put(self.audioBlob, 'audioBlob'); + } + + function getFromStore(portionName) { + transaction.objectStore(self.dataStoreName).get(portionName).onsuccess = function(event) { + if (self.callback) { + self.callback(event.target.result, portionName); + } + }; + } + + getFromStore('audioBlob'); + getFromStore('videoBlob'); + getFromStore('gifBlob'); + } + + request.onerror = self.onError; + + request.onsuccess = function() { + db = request.result; + db.onerror = self.onError; + + if (db.setVersion) { + if (db.version !== dbVersion) { + var setVersion = db.setVersion(dbVersion); + setVersion.onsuccess = function() { + createObjectStore(db); + putInDB(); + }; + } else { + putInDB(); + } + } else { + putInDB(); + } + }; + request.onupgradeneeded = function(event) { + createObjectStore(event.target.result); + }; + }, + /** + * This method fetches stored blobs from IndexedDB. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.Fetch(function(dataURL, type) { + * if(type === 'audioBlob') { } + * if(type === 'videoBlob') { } + * if(type === 'gifBlob') { } + * }); + */ + Fetch: function(callback) { + this.callback = callback; + this.init(); + + return this; + }, + /** + * This method stores blobs in IndexedDB. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.Store({ + * audioBlob: yourAudioBlob, + * videoBlob: yourVideoBlob, + * gifBlob : yourGifBlob + * }); + */ + Store: function(config) { + this.audioBlob = config.audioBlob; + this.videoBlob = config.videoBlob; + this.gifBlob = config.gifBlob; + + this.init(); + + return this; + }, + /** + * This function is invoked for any known/unknown error. + * @method + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.onError = function(error){ + * alerot( JSON.stringify(error) ); + * }; + */ + onError: function(error) { + console.error(JSON.stringify(error, null, '\t')); + }, + + /** + * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. + * @memberof DiskStorage + * @internal + * @example + * DiskStorage.dataStoreName = 'recordRTC'; + */ + dataStoreName: 'recordRTC', + dbName: null +}; + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.DiskStorage = DiskStorage; +} + +// ______________ +// GifRecorder.js + +/** + * GifRecorder is standalone calss used by {@link RecordRTC} to record video or canvas into animated gif. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef GifRecorder + * @class + * @example + * var recorder = new GifRecorder(mediaStream || canvas || context, { onGifPreview: function, onGifRecordingStarted: function, width: 1280, height: 720, frameRate: 200, quality: 10 }); + * recorder.record(); + * recorder.stop(function(blob) { + * img.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object or HTMLCanvasElement or CanvasRenderingContext2D. + * @param {object} config - {disableLogs:true, initCallback: function, width: 320, height: 240, frameRate: 200, quality: 10} + */ + +function GifRecorder(mediaStream, config) { + if (typeof GIFEncoder === 'undefined') { + var script = document.createElement('script'); + script.src = 'https://cdn.webrtc-experiment.com/gif-recorder.js'; + (document.body || document.documentElement).appendChild(script); + } + + config = config || {}; + + var isHTMLObject = mediaStream instanceof CanvasRenderingContext2D || mediaStream instanceof HTMLCanvasElement; + + /** + * This method records MediaStream. + * @method + * @memberof GifRecorder + * @example + * recorder.record(); + */ + this.record = function() { + if (typeof GIFEncoder === 'undefined') { + setTimeout(self.record, 1000); + return; + } + + if (!isLoadedMetaData) { + setTimeout(self.record, 1000); + return; + } + + if (!isHTMLObject) { + if (!config.width) { + config.width = video.offsetWidth || 320; + } + + if (!config.height) { + config.height = video.offsetHeight || 240; + } + + if (!config.video) { + config.video = { + width: config.width, + height: config.height + }; + } + + if (!config.canvas) { + config.canvas = { + width: config.width, + height: config.height + }; + } + + canvas.width = config.canvas.width || 320; + canvas.height = config.canvas.height || 240; + + video.width = config.video.width || 320; + video.height = config.video.height || 240; + } + + // external library to record as GIF images + gifEncoder = new GIFEncoder(); + + // void setRepeat(int iter) + // Sets the number of times the set of GIF frames should be played. + // Default is 1; 0 means play indefinitely. + gifEncoder.setRepeat(0); + + // void setFrameRate(Number fps) + // Sets frame rate in frames per second. + // Equivalent to setDelay(1000/fps). + // Using "setDelay" instead of "setFrameRate" + gifEncoder.setDelay(config.frameRate || 200); + + // void setQuality(int quality) + // Sets quality of color quantization (conversion of images to the + // maximum 256 colors allowed by the GIF specification). + // Lower values (minimum = 1) produce better colors, + // but slow processing significantly. 10 is the default, + // and produces good color mapping at reasonable speeds. + // Values greater than 20 do not yield significant improvements in speed. + gifEncoder.setQuality(config.quality || 10); + + // Boolean start() + // This writes the GIF Header and returns false if it fails. + gifEncoder.start(); + + if (typeof config.onGifRecordingStarted === 'function') { + config.onGifRecordingStarted(); + } + + startTime = Date.now(); + + function drawVideoFrame(time) { + if (self.clearedRecordedData === true) { + return; + } + + if (isPausedRecording) { + return setTimeout(function() { + drawVideoFrame(time); + }, 100); + } + + lastAnimationFrame = requestAnimationFrame(drawVideoFrame); + + if (typeof lastFrameTime === undefined) { + lastFrameTime = time; + } + + // ~10 fps + if (time - lastFrameTime < 90) { + return; + } + + if (!isHTMLObject && video.paused) { + // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 + // Tweak for Android Chrome + video.play(); + } + + if (!isHTMLObject) { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + } + + if (config.onGifPreview) { + config.onGifPreview(canvas.toDataURL('image/png')); + } + + gifEncoder.addFrame(context); + lastFrameTime = time; + } + + lastAnimationFrame = requestAnimationFrame(drawVideoFrame); + + if (config.initCallback) { + config.initCallback(); + } + }; + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof GifRecorder + * @example + * recorder.stop(function(blob) { + * img.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + callback = callback || function() {}; + + if (lastAnimationFrame) { + cancelAnimationFrame(lastAnimationFrame); + } + + endTime = Date.now(); + + /** + * @property {Blob} blob - The recorded blob object. + * @memberof GifRecorder + * @example + * recorder.stop(function(){ + * var blob = recorder.blob; + * }); + */ + this.blob = new Blob([new Uint8Array(gifEncoder.stream().bin)], { + type: 'image/gif' + }); + + callback(this.blob); + + // bug: find a way to clear old recorded blobs + gifEncoder.stream().bin = []; + }; + + var isPausedRecording = false; + + /** + * This method pauses the recording process. + * @method + * @memberof GifRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPausedRecording = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof GifRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPausedRecording = false; + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof GifRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + self.clearedRecordedData = true; + clearRecordedDataCB(); + }; + + function clearRecordedDataCB() { + if (gifEncoder) { + gifEncoder.stream().bin = []; + } + } + + // for debugging + this.name = 'GifRecorder'; + this.toString = function() { + return this.name; + }; + + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + + if (isHTMLObject) { + if (mediaStream instanceof CanvasRenderingContext2D) { + context = mediaStream; + canvas = context.canvas; + } else if (mediaStream instanceof HTMLCanvasElement) { + context = mediaStream.getContext('2d'); + canvas = mediaStream; + } + } + + var isLoadedMetaData = true; + + if (!isHTMLObject) { + var video = document.createElement('video'); + video.muted = true; + video.autoplay = true; + + isLoadedMetaData = false; + video.onloadedmetadata = function() { + isLoadedMetaData = true; + }; + + setSrcObject(mediaStream, video); + + video.play(); + } + + var lastAnimationFrame = null; + var startTime, endTime, lastFrameTime; + + var gifEncoder; + + var self = this; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.GifRecorder = GifRecorder; +} + +// Last time updated: 2018-12-22 9:13:29 AM UTC + +// ________________________ +// MultiStreamsMixer v1.0.7 + +// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +function MultiStreamsMixer(arrayOfMediaStreams) { + + // requires: chrome://flags/#enable-experimental-web-platform-features + + var videos = []; + var isStopDrawingFrames = false; + + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + canvas.style = 'opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;'; + (document.body || document.documentElement).appendChild(canvas); + + this.disableLogs = false; + this.frameInterval = 10; + + this.width = 360; + this.height = 240; + + // use gain node to prevent echo + this.useGainNode = true; + + var self = this; + + // _____________________________ + // Cross-Browser-Declarations.js + + // WebAudio API representer + var AudioContext = window.AudioContext; + + if (typeof AudioContext === 'undefined') { + if (typeof webkitAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = webkitAudioContext; + } + + if (typeof mozAudioContext !== 'undefined') { + /*global AudioContext:true */ + AudioContext = mozAudioContext; + } + } + + /*jshint -W079 */ + var URL = window.URL; + + if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { + /*global URL:true */ + URL = webkitURL; + } + + if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? + if (typeof navigator.webkitGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + + if (typeof navigator.mozGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.mozGetUserMedia; + } + } + + var MediaStream = window.MediaStream; + + if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { + MediaStream = webkitMediaStream; + } + + /*global MediaStream:true */ + if (typeof MediaStream !== 'undefined') { + // override "stop" method for all browsers + if (typeof MediaStream.prototype.stop === 'undefined') { + MediaStream.prototype.stop = function() { + this.getTracks().forEach(function(track) { + track.stop(); + }); + }; + } + } + + var Storage = {}; + + if (typeof AudioContext !== 'undefined') { + Storage.AudioContext = AudioContext; + } else if (typeof webkitAudioContext !== 'undefined') { + Storage.AudioContext = webkitAudioContext; + } + + function setSrcObject(stream, element) { + if ('srcObject' in element) { + element.srcObject = stream; + } else if ('mozSrcObject' in element) { + element.mozSrcObject = stream; + } else { + element.srcObject = stream; + } + } + + this.startDrawingFrames = function() { + drawVideosToCanvas(); + }; + + function drawVideosToCanvas() { + if (isStopDrawingFrames) { + return; + } + + var videosLength = videos.length; + + var fullcanvas = false; + var remaining = []; + videos.forEach(function(video) { + if (!video.stream) { + video.stream = {}; + } + + if (video.stream.fullcanvas) { + fullcanvas = video; + } else { + remaining.push(video); + } + }); + + if (fullcanvas) { + canvas.width = fullcanvas.stream.width; + canvas.height = fullcanvas.stream.height; + } else if (remaining.length) { + canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width; + + var height = 1; + if (videosLength === 3 || videosLength === 4) { + height = 2; + } + if (videosLength === 5 || videosLength === 6) { + height = 3; + } + if (videosLength === 7 || videosLength === 8) { + height = 4; + } + if (videosLength === 9 || videosLength === 10) { + height = 5; + } + canvas.height = remaining[0].height * height; + } else { + canvas.width = self.width || 360; + canvas.height = self.height || 240; + } + + if (fullcanvas && fullcanvas instanceof HTMLVideoElement) { + drawImage(fullcanvas); + } + + remaining.forEach(function(video, idx) { + drawImage(video, idx); + }); + + setTimeout(drawVideosToCanvas, self.frameInterval); + } + + function drawImage(video, idx) { + if (isStopDrawingFrames) { + return; + } + + var x = 0; + var y = 0; + var width = video.width; + var height = video.height; + + if (idx === 1) { + x = video.width; + } + + if (idx === 2) { + y = video.height; + } + + if (idx === 3) { + x = video.width; + y = video.height; + } + + if (idx === 4) { + y = video.height * 2; + } + + if (idx === 5) { + x = video.width; + y = video.height * 2; + } + + if (idx === 6) { + y = video.height * 3; + } + + if (idx === 7) { + x = video.width; + y = video.height * 3; + } + + if (typeof video.stream.left !== 'undefined') { + x = video.stream.left; + } + + if (typeof video.stream.top !== 'undefined') { + y = video.stream.top; + } + + if (typeof video.stream.width !== 'undefined') { + width = video.stream.width; + } + + if (typeof video.stream.height !== 'undefined') { + height = video.stream.height; + } + + context.drawImage(video, x, y, width, height); + + if (typeof video.stream.onRender === 'function') { + video.stream.onRender(context, x, y, width, height, idx); + } + } + + function getMixedStream() { + isStopDrawingFrames = false; + var mixedVideoStream = getMixedVideoStream(); + + var mixedAudioStream = getMixedAudioStream(); + if (mixedAudioStream) { + mixedAudioStream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).forEach(function(track) { + mixedVideoStream.addTrack(track); + }); + } + + var fullcanvas; + arrayOfMediaStreams.forEach(function(stream) { + if (stream.fullcanvas) { + fullcanvas = true; + } + }); + + return mixedVideoStream; + } + + function getMixedVideoStream() { + resetVideoStreams(); + + var capturedStream; + + if ('captureStream' in canvas) { + capturedStream = canvas.captureStream(); + } else if ('mozCaptureStream' in canvas) { + capturedStream = canvas.mozCaptureStream(); + } else if (!self.disableLogs) { + console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features'); + } + + var videoStream = new MediaStream(); + + capturedStream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).forEach(function(track) { + videoStream.addTrack(track); + }); + + canvas.stream = videoStream; + + return videoStream; + } + + function getMixedAudioStream() { + // via: @pehrsons + if (!Storage.AudioContextConstructor) { + Storage.AudioContextConstructor = new Storage.AudioContext(); + } + + self.audioContext = Storage.AudioContextConstructor; + + self.audioSources = []; + + if (self.useGainNode === true) { + self.gainNode = self.audioContext.createGain(); + self.gainNode.connect(self.audioContext.destination); + self.gainNode.gain.value = 0; // don't hear self + } + + var audioTracksLength = 0; + arrayOfMediaStreams.forEach(function(stream) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length) { + return; + } + + audioTracksLength++; + + var audioSource = self.audioContext.createMediaStreamSource(stream); + + if (self.useGainNode === true) { + audioSource.connect(self.gainNode); + } + + self.audioSources.push(audioSource); + }); + + if (!audioTracksLength) { + return; + } + + self.audioDestination = self.audioContext.createMediaStreamDestination(); + self.audioSources.forEach(function(audioSource) { + audioSource.connect(self.audioDestination); + }); + return self.audioDestination.stream; + } + + function getVideo(stream) { + var video = document.createElement('video'); + + setSrcObject(stream, video); + + video.muted = true; + video.volume = 0; + + video.width = stream.width || self.width || 360; + video.height = stream.height || self.height || 240; + + video.play(); + + return video; + } + + this.appendStreams = function(streams) { + if (!streams) { + throw 'First parameter is required.'; + } + + if (!(streams instanceof Array)) { + streams = [streams]; + } + + arrayOfMediaStreams.concat(streams); + + streams.forEach(function(stream) { + if (stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { + var video = getVideo(stream); + video.stream = stream; + videos.push(video); + } + + if (stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length && self.audioContext) { + var audioSource = self.audioContext.createMediaStreamSource(stream); + audioSource.connect(self.audioDestination); + self.audioSources.push(audioSource); + } + }); + }; + + this.releaseStreams = function() { + videos = []; + isStopDrawingFrames = true; + + if (self.gainNode) { + self.gainNode.disconnect(); + self.gainNode = null; + } + + if (self.audioSources.length) { + self.audioSources.forEach(function(source) { + source.disconnect(); + }); + self.audioSources = []; + } + + if (self.audioDestination) { + self.audioDestination.disconnect(); + self.audioDestination = null; + } + + if (self.audioContext) { + self.audioContext.close(); + } + + self.audioContext = null; + + context.clearRect(0, 0, canvas.width, canvas.height); + + if (canvas.stream) { + canvas.stream.stop(); + canvas.stream = null; + } + }; + + this.resetVideoStreams = function(streams) { + if (streams && !(streams instanceof Array)) { + streams = [streams]; + } + + resetVideoStreams(streams); + }; + + function resetVideoStreams(streams) { + videos = []; + streams = streams || arrayOfMediaStreams; + + // via: @adrian-ber + streams.forEach(function(stream) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { + return; + } + + var video = getVideo(stream); + video.stream = stream; + videos.push(video); + }); + } + + // for debugging + this.name = 'MultiStreamsMixer'; + this.toString = function() { + return this.name; + }; + + this.getMixedStream = getMixedStream; + +} + +// ______________________ +// MultiStreamRecorder.js + +/* + * Video conference recording, using captureStream API along with WebAudio and Canvas2D API. + */ + +/** + * MultiStreamRecorder can record multiple videos in single container. + * @summary Multi-videos recorder. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef MultiStreamRecorder + * @class + * @example + * var options = { + * mimeType: 'video/webm' + * } + * var recorder = new MultiStreamRecorder(ArrayOfMediaStreams, options); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * + * // or + * var blob = recorder.blob; + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStreams} mediaStreams - Array of MediaStreams. + * @param {object} config - {disableLogs:true, frameInterval: 1, mimeType: "video/webm"} + */ + +function MultiStreamRecorder(arrayOfMediaStreams, options) { + arrayOfMediaStreams = arrayOfMediaStreams || []; + var self = this; + + var mixer; + var mediaRecorder; + + options = options || { + mimeType: 'video/webm', + video: { + width: 360, + height: 240 + } + }; + + if (!options.frameInterval) { + options.frameInterval = 10; + } + + if (!options.video) { + options.video = {}; + } + + if (!options.video.width) { + options.video.width = 360; + } + + if (!options.video.height) { + options.video.height = 240; + } + + /** + * This method records all MediaStreams. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.record(); + */ + this.record = function() { + // github/muaz-khan/MultiStreamsMixer + mixer = new MultiStreamsMixer(arrayOfMediaStreams); + + if (getAllVideoTracks().length) { + mixer.frameInterval = options.frameInterval || 10; + mixer.width = options.video.width || 360; + mixer.height = options.video.height || 240; + mixer.startDrawingFrames(); + } + + if (options.previewStream && typeof options.previewStream === 'function') { + options.previewStream(mixer.getMixedStream()); + } + + // record using MediaRecorder API + mediaRecorder = new MediaStreamRecorder(mixer.getMixedStream(), options); + mediaRecorder.record(); + }; + + function getAllVideoTracks() { + var tracks = []; + arrayOfMediaStreams.forEach(function(stream) { + getTracks(stream, 'video').forEach(function(track) { + tracks.push(track); + }); + }); + return tracks; + } + + /** + * This method stops recording MediaStream. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + if (!mediaRecorder) { + return; + } + + mediaRecorder.stop(function(blob) { + self.blob = blob; + + callback(blob); + + self.clearRecordedData(); + }); + }; + + /** + * This method pauses the recording process. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + if (mediaRecorder) { + mediaRecorder.pause(); + } + }; + + /** + * This method resumes the recording process. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + if (mediaRecorder) { + mediaRecorder.resume(); + } + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof MultiStreamRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + if (mediaRecorder) { + mediaRecorder.clearRecordedData(); + mediaRecorder = null; + } + + if (mixer) { + mixer.releaseStreams(); + mixer = null; + } + }; + + /** + * Add extra media-streams to existing recordings. + * @method + * @memberof MultiStreamRecorder + * @param {MediaStreams} mediaStreams - Array of MediaStreams + * @example + * recorder.addStreams([newAudioStream, newVideoStream]); + */ + this.addStreams = function(streams) { + if (!streams) { + throw 'First parameter is required.'; + } + + if (!(streams instanceof Array)) { + streams = [streams]; + } + + arrayOfMediaStreams.concat(streams); + + if (!mediaRecorder || !mixer) { + return; + } + + mixer.appendStreams(streams); + }; + + /** + * Reset videos during live recording. Replace old videos e.g. replace cameras with full-screen. + * @method + * @memberof MultiStreamRecorder + * @param {MediaStreams} mediaStreams - Array of MediaStreams + * @example + * recorder.resetVideoStreams([newVideo1, newVideo2]); + */ + this.resetVideoStreams = function(streams) { + if (!mixer) { + return; + } + + if (streams && !(streams instanceof Array)) { + streams = [streams]; + } + + mixer.resetVideoStreams(streams); + }; + + // for debugging + this.name = 'MultiStreamRecorder'; + this.toString = function() { + return this.name; + }; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.MultiStreamRecorder = MultiStreamRecorder; +} + +// _____________________ +// RecordRTC.promises.js + +/** + * RecordRTCPromisesHandler adds promises support in {@link RecordRTC}. Try a {@link https://github.com/muaz-khan/RecordRTC/blob/master/simple-demos/RecordRTCPromisesHandler.html|demo here} + * @summary Promises for {@link RecordRTC} + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef RecordRTCPromisesHandler + * @class + * @example + * var recorder = new RecordRTCPromisesHandler(mediaStream, options); + * recorder.startRecording() + * .then(successCB) + * .catch(errorCB); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. + * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} + * @throws Will throw an error if "new" keyword is not used to initiate "RecordRTCPromisesHandler". Also throws error if first argument "MediaStream" is missing. + * @requires {@link RecordRTC} + */ + +function RecordRTCPromisesHandler(mediaStream, options) { + if (!this) { + throw 'Use "new RecordRTCPromisesHandler()"'; + } + + if (typeof mediaStream === 'undefined') { + throw 'First argument "MediaStream" is required.'; + } + + var self = this; + + /** + * @property {Blob} blob - Access/reach the native {@link RecordRTC} object. + * @memberof RecordRTCPromisesHandler + * @example + * var internal = recorder.recordRTC.getInternalRecorder(); + * alert(internal instanceof MediaStreamRecorder); + */ + self.recordRTC = new RecordRTC(mediaStream, options); + + /** + * This method records MediaStream. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.startRecording() + * .then(successCB) + * .catch(errorCB); + */ + this.startRecording = function() { + return new Promise(function(resolve, reject) { + try { + self.recordRTC.startRecording(); + resolve(); + } catch (e) { + reject(e); + } + }); + }; + + /** + * This method stops the recording. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * var blob = recorder.getBlob(); + * }).catch(errorCB); + */ + this.stopRecording = function() { + return new Promise(function(resolve, reject) { + try { + self.recordRTC.stopRecording(function(url) { + self.blob = self.recordRTC.getBlob(); + + if (!self.blob || !self.blob.size) { + reject('Empty blob.', self.blob); + return; + } + + resolve(url); + }); + } catch (e) { + reject(e); + } + }); + }; + + /** + * This method returns data-url for the recorded blob. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * recorder.getDataURL().then(function(dataURL) { + * window.open(dataURL); + * }).catch(errorCB);; + * }).catch(errorCB); + */ + this.getDataURL = function(callback) { + return new Promise(function(resolve, reject) { + try { + self.recordRTC.getDataURL(function(dataURL) { + resolve(dataURL); + }); + } catch (e) { + reject(e); + } + }); + }; + + /** + * This method returns the recorded blob. + * @method + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * recorder.getBlob().then(function(blob) {}) + * }).catch(errorCB); + */ + this.getBlob = function() { + return new Promise(function(resolve, reject) { + try { + resolve(self.recordRTC.getBlob()); + } catch (e) { + reject(e); + } + }); + }; + + /** + * @property {Blob} blob - Recorded data as "Blob" object. + * @memberof RecordRTCPromisesHandler + * @example + * recorder.stopRecording().then(function() { + * var blob = recorder.getBlob(); + * }).catch(errorCB); + */ + this.blob = null; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.RecordRTCPromisesHandler = RecordRTCPromisesHandler; +} + +// ______________________ +// WebAssemblyRecorder.js + +/** + * WebAssemblyRecorder lets you create webm videos in JavaScript via WebAssembly. The library consumes raw RGBA32 buffers (4 bytes per pixel) and turns them into a webm video with the given framerate and quality. This makes it compatible out-of-the-box with ImageData from a CANVAS. With realtime mode you can also use webm-wasm for streaming webm videos. + * @summary Video recording feature in Chrome, Firefox and maybe Edge. + * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} + * @author {@link http://www.MuazKhan.com|Muaz Khan} + * @typedef WebAssemblyRecorder + * @class + * @example + * var recorder = new WebAssemblyRecorder(mediaStream); + * recorder.record(); + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} + * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. + * @param {object} config - {webAssemblyPath:'webm-wasm.wasm',workerPath: 'webm-worker.js', frameRate: 30, width: 1920, height: 1080, bitrate: 1024} + */ +function WebAssemblyRecorder(stream, config) { + // based on: github.com/GoogleChromeLabs/webm-wasm + + if (typeof ReadableStream === 'undefined' || typeof WritableStream === 'undefined') { + // because it fixes readable/writable streams issues + console.error('Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js'); + } + + config = config || {}; + + config.width = config.width || 640; + config.height = config.height || 480; + config.frameRate = config.frameRate || 30; + config.bitrate = config.bitrate || 1200; + + function createBufferURL(buffer, type) { + return URL.createObjectURL(new Blob([buffer], { + type: type || '' + })); + } + + function cameraStream() { + return new ReadableStream({ + start: function(controller) { + var cvs = document.createElement('canvas'); + var video = document.createElement('video'); + video.srcObject = stream; + video.onplaying = function() { + cvs.width = config.width; + cvs.height = config.height; + var ctx = cvs.getContext('2d'); + var frameTimeout = 1000 / config.frameRate; + setTimeout(function f() { + ctx.drawImage(video, 0, 0); + controller.enqueue( + ctx.getImageData(0, 0, config.width, config.height) + ); + setTimeout(f, frameTimeout); + }, frameTimeout); + }; + video.play(); + } + }); + } + + var worker; + + function startRecording(stream, buffer) { + if (!config.workerPath && !buffer) { + // is it safe to use @latest ? + fetch( + 'https://unpkg.com/webm-wasm@latest/dist/webm-worker.js' + ).then(function(r) { + r.arrayBuffer().then(function(buffer) { + startRecording(stream, buffer); + }); + }); + return; + } + + if (!config.workerPath && buffer instanceof ArrayBuffer) { + var blob = new Blob([buffer], { + type: 'text/javascript' + }); + config.workerPath = URL.createObjectURL(blob); + } + + if (!config.workerPath) { + console.error('workerPath parameter is missing.'); + } + + worker = new Worker(config.workerPath); + + worker.postMessage(config.webAssemblyPath || 'https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm'); + worker.addEventListener('message', function(event) { + if (event.data === 'READY') { + worker.postMessage({ + width: config.width, + height: config.height, + bitrate: config.bitrate || 1200, + timebaseDen: config.frameRate || 30, + realtime: true + }); + + cameraStream().pipeTo(new WritableStream({ + write: function(image) { + if (!worker) { + return; + } + + worker.postMessage(image.data.buffer, [image.data.buffer]); + } + })); + } else if (!!event.data) { + if (!isPaused) { + arrayOfBuffers.push(event.data); + } + } + }); + } + + /** + * This method records video. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.record(); + */ + this.record = function() { + arrayOfBuffers = []; + isPaused = false; + this.blob = null; + startRecording(stream); + + if (typeof config.initCallback === 'function') { + config.initCallback(); + } + }; + + var isPaused; + + /** + * This method pauses the recording process. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.pause(); + */ + this.pause = function() { + isPaused = true; + }; + + /** + * This method resumes the recording process. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.resume(); + */ + this.resume = function() { + isPaused = false; + }; + + function terminate() { + if (!worker) { + return; + } + + worker.postMessage(null); + worker.terminate(); + worker = null; + } + + var arrayOfBuffers = []; + + /** + * This method stops recording video. + * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.stop(function(blob) { + * video.src = URL.createObjectURL(blob); + * }); + */ + this.stop = function(callback) { + terminate(); + + this.blob = new Blob(arrayOfBuffers, { + type: 'video/webm' + }); + + callback(this.blob); + }; + + // for debugging + this.name = 'WebAssemblyRecorder'; + this.toString = function() { + return this.name; + }; + + /** + * This method resets currently recorded data. + * @method + * @memberof WebAssemblyRecorder + * @example + * recorder.clearRecordedData(); + */ + this.clearRecordedData = function() { + arrayOfBuffers = []; + isPaused = false; + this.blob = null; + + // todo: if recording-ON then STOP it first + }; + + /** + * @property {Blob} blob - The recorded blob object. + * @memberof WebAssemblyRecorder + * @example + * recorder.stop(function(){ + * var blob = recorder.blob; + * }); + */ + this.blob = null; +} + +if (typeof RecordRTC !== 'undefined') { + RecordRTC.WebAssemblyRecorder = WebAssemblyRecorder; +} diff --git a/src/libkysdk-system-javascript-websocket/html/js/config-manager.js b/src/libkysdk-system-javascript-websocket/html/js/config-manager.js new file mode 100755 index 0000000..3fe7ef3 --- /dev/null +++ b/src/libkysdk-system-javascript-websocket/html/js/config-manager.js @@ -0,0 +1,109 @@ +{ +output=function(message){ + var output = document.getElementById("StateResult"); + output.innerHTML = output.innerHTML + message + "\n"; +}, +getJsonFromInfo=function(info){ + if(info == ""){ + return "{}"; + } + var info_list = info.split("&"); + var info_json = "{"; + for(var i=0; i)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="
t
",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj; +return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="
",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&>(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:b.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l) +}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("