Merge pull request #89 from jcorporation/decoupling

Merge decoupling branch into devel
This commit is contained in:
Jürgen Mang 2019-01-28 23:07:23 +01:00 committed by GitHub
commit 18edef3ab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 3669 additions and 2280 deletions

View File

@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 2.6)
project (mympd C) project (mympd C)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
set(CPACK_PACKAGE_VERSION_MAJOR "4") set(CPACK_PACKAGE_VERSION_MAJOR "5")
set(CPACK_PACKAGE_VERSION_MINOR "8") set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0") set(CPACK_PACKAGE_VERSION_PATCH "0")
if(CMAKE_BUILD_TYPE MATCHES RELEASE) if(CMAKE_BUILD_TYPE MATCHES RELEASE)
@ -14,32 +14,60 @@ else()
set(DEBUG "ON") set(DEBUG "ON")
endif() endif()
find_package(LibMPDClient REQUIRED) if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
MESSAGE("++ 64 bit architecture")
set(PKGARCH64 "ON")
else()
MESSAGE("++ 32 bit architecture")
set(PKGARCH64 "OFF")
endif()
configure_file(src/config.h.in ${PROJECT_BINARY_DIR}/config.h) find_package(LibMPDClient REQUIRED)
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_INCLUDE_DIR}) find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
configure_file(src/global.h.in ${PROJECT_BINARY_DIR}/global.h)
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})
include(CheckCSourceCompiles) include(CheckCSourceCompiles)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_ENABLE_SSL -D MG_ENABLE_IPV6 -D MG_DISABLE_MQTT -D MG_DISABLE_MQTT_BROKER -D MG_DISABLE_DNS_SERVER -D MG_DISABLE_COAP -D MG_DISABLE_HTTP_CGI -D MG_DISABLE_HTTP_SSI -D MG_DISABLE_HTTP_WEBDAV") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -O1 -Wall -fstack-protector -D_FORTIFY_SOURCE=2 -pie -fPIE -DMG_ENABLE_SSL")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -D_FORTIFY_SOURCE=2 -fstack-protector -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=shift -fsanitize=integer-divide-by-zero -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null -fsanitize=return -fsanitize=signed-integer-overflow -fsanitize=bounds -fsanitize=bounds-strict -fsanitize=alignment -fsanitize=object-size -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fsanitize=nonnull-attribute -fsanitize=returns-nonnull-attribute -fsanitize=bool -fsanitize=enum -fsanitize=vptr -static-libasan")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -fsanitize=address \
-fsanitize=undefined -fsanitize=shift -fsanitize=integer-divide-by-zero -fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=null -fsanitize=return -fsanitize=signed-integer-overflow -fsanitize=bounds -fsanitize=bounds-strict \
-fsanitize=alignment -fsanitize=object-size -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow \
-fsanitize=nonnull-attribute -fsanitize=returns-nonnull-attribute -fsanitize=bool -fsanitize=enum -fsanitize=vptr -static-libasan")
#for use with valgrind, disable above 2 set commands and enable the following set commands
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -DMG_ENABLE_SSL")
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb")
#compiler flags for mympd src
file(GLOB MYMPD_SRC_FILES "src/*.c")
set_property(SOURCE ${MYMPD_SRC_FILES} PROPERTY COMPILE_FLAGS "-Wextra -pedantic -Wformat=2 -Wno-unused-parameter -Wshadow -Wwrite-strings -Wstrict-prototypes -Wold-style-definition -Wredundant-decls -Wnested-externs -Wmissing-include-dirs")
#compiler flags for mongoose.c
set_property(SOURCE dist/src/mongoose/mongoose.c PROPERTY COMPILE_FLAGS "-Wno-format-truncation")
set (CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro -Wl,-z,now")
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_SSL)
set(SOURCES set(SOURCES
src/mympd.c src/main.c
src/global.c
src/mpd_client.c src/mpd_client.c
src/web_server.c
src/mympd_api.c
src/list.c src/list.c
src/validate.c src/tiny_queue.c
dist/src/mongoose/mongoose.c dist/src/mongoose/mongoose.c
dist/src/frozen/frozen.c dist/src/frozen/frozen.c
dist/src/inih/ini.c dist/src/inih/ini.c
) )
add_executable(mympd ${SOURCES}) add_executable(mympd ${SOURCES})
target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${OPENSSL_LIBRARIES}) target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
install(TARGETS mympd DESTINATION bin) install(TARGETS mympd DESTINATION bin)
install(FILES contrib/mympd.1 DESTINATION share/man/man1) install(FILES contrib/mympd.1 DESTINATION share/man/man1)

43
Dockerfile Normal file
View File

@ -0,0 +1,43 @@
FROM library/debian:9 as build
RUN apt-get update
RUN apt-get install git meson ninja-build gcc cmake libssl-dev -y
RUN apt-get install openjdk-8-jre-headless perl -y
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN mkdir /libmpdclient-dist
RUN git clone https://github.com/MusicPlayerDaemon/libmpdclient.git
WORKDIR /libmpdclient
RUN meson . output
RUN ninja -C output
RUN ninja -C output install
RUN mesonconf output -Dprefix=/libmpdclient-dist
RUN ninja -C output
RUN ninja -C output install
WORKDIR /
RUN tar -czvf /libmpdclient-master.tar.gz -C /libmpdclient-dist .
COPY . /myMPD/
ENV MYMPD_INSTALL_PREFIX=/myMPD-dist/usr
RUN mkdir -p $MYMPD_INSTALL_PREFIX
WORKDIR /myMPD
RUN ./mkrelease.sh
WORKDIR /
RUN tar -czvf /mympd.tar.gz -C /myMPD-dist .
FROM library/debian:9-slim
ENV MYMPD_COVERIMAGENAME=folder.jpg
ENV MYMPD_MPDHOST=127.0.0.1
ENV MYMPD_MPDPORT=6600
ENV MYMPD_SSL=true
ENV MYMPD_LOGLEVEL=1
RUN apt-get update && apt-get install libssl-dev openssl -y
COPY --from=build /libmpdclient-master.tar.gz /
COPY --from=build /mympd.tar.gz /
COPY --from=build /myMPD/debian/postinst /
WORKDIR /
RUN tar xfv libmpdclient-master.tar.gz -C /
RUN tar xfv mympd.tar.gz -C /
RUN rm libmpdclient-master.tar.gz
RUN rm mympd.tar.gz
COPY contrib/docker/init.sh /
RUN chmod +x /init.sh
ENTRYPOINT ["/init.sh"]

View File

@ -4,11 +4,7 @@
pkgname=mympd pkgname=mympd
_pkgname=myMPD _pkgname=myMPD
<<<<<<< HEAD
pkgver=4.8.0
=======
pkgver=5.0.0 pkgver=5.0.0
>>>>>>> cd588ab72e10ee934a8ac58f4add1dc9268ed79f
pkgrel=1 pkgrel=1
pkgdesc="myMPD is a standalone and mobile friendly web mpdclient." pkgdesc="myMPD is a standalone and mobile friendly web mpdclient."
arch=('x86_64' 'armv7h' 'aarch64') arch=('x86_64' 'armv7h' 'aarch64')

View File

@ -22,6 +22,7 @@ This fork provides a reworked ui based on Bootstrap 4, a modernized backend and
- Playlist management - Playlist management
- Advanced search (requires mpd >= 0.21.x and libmpdclient >= 2.17) - Advanced search (requires mpd >= 0.21.x and libmpdclient >= 2.17)
- Jukebox mode (add's random songs / albums from database or playlists to queue) - Jukebox mode (add's random songs / albums from database or playlists to queue)
- AutoPlay - add song to (empty) queue and mpd starts playing
- Smart playlists and saved searches - Smart playlists and saved searches
- Play statistics and song voting (requires mpd stickers) - Play statistics and song voting (requires mpd stickers)
- Local coverart support (Albums and Streams) - Local coverart support (Albums and Streams)
@ -65,15 +66,16 @@ Unix Build Instructions
----------------------- -----------------------
1. Install dependencies: cmake, libmpdclient (dev), OpenSSL (dev) and Java are available from all major distributions. 1. Install dependencies: cmake, libmpdclient (dev), OpenSSL (dev) and Java are available from all major distributions.
2. Build and install: ```cd /path/to/src; ./mkrelease.sh```. 2. Build and install: ```cd /path/to/src; ./mkrelease.sh``` (toplevel directory of myMPD tarball).
3. Link your mpd music directory to ```/usr/share/mympd/htdocs/library``` and put ```folder.jpg``` files in your album directories (mkrelease.sh tries to do this for you). 3. Link your mpd music directory to ```/usr/share/mympd/htdocs/library``` and put ```folder.jpg``` files in your album directories (mkrelease.sh tries to do this for you).
4. Configure your mpd with http stream output to use the local player. 4. Configure your mpd with http stream output to use the local player.
Run Flags Run
--------- ---------
``` ```
Usage: ./mympd /etc/mypd/mympd.conf Usage: ./mympd /etc/mypd/mympd.conf
``` ```
The ```./mkrelease.sh``` script tries to install a systemd service file. If this failes you can copy the ```mympd.service``` file from ```/usr/share/mympd/``` to appropriate distribution specific location.
SSL SSL
--- ---
@ -85,6 +87,6 @@ SSL
Copyright Copyright
--------- ---------
ympd: 2013-2014 <andy@ndyk.de> myMPD: 2018-2019 <mail@jcgames.de>
myMPD: 2018 <mail@jcgames.de> ympd: 2013-2014 <andy@ndyk.de>

View File

@ -64,6 +64,7 @@ post_upgrade() {
[ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode [ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode
[ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist [ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist
[ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength [ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength
[ -f /var/lib/mympd/state/autoPlay ] || echo -n "false" > /var/lib/mympd/state/autoPlay
[ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage [ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage
[ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb [ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb
[ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase [ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase

11
contrib/docker/init.sh Normal file
View File

@ -0,0 +1,11 @@
#!/bin/sh
/postinst
sed -i "s#mpdhost = 127.0.0.1#mpdhost = $MYMPD_MPDHOST#g" /etc/mympd/mympd.conf
sed -i "s#mpdport = 6600#mpdport = $MYMPD_MPDPORT#g" /etc/mympd/mympd.conf
sed -i "s#ssl = true#ssl = $MYMPD_SSL#g" /etc/mympd/mympd.conf
sed -i "s#coverimagename = folder.jpg#coverimagename = $MYMPD_COVERIMAGENAME#g" /etc/mympd/mympd.conf
sed -i "s#loglevel = 1#loglevel = $MYMPD_LOGLEVEL#g" /etc/mympd/mympd.conf
mympd /etc/mympd/mympd.conf

View File

@ -4,7 +4,7 @@
# (c) 2018 Juergen Mang <mail@jcgames.de> # (c) 2018 Juergen Mang <mail@jcgames.de>
Name: myMPD Name: myMPD
Version: 4.8.0 Version: 5.0.0
Release: 0 Release: 0
License: GPL-2.0 License: GPL-2.0
Group: Productivity/Multimedia/Sound/Players Group: Productivity/Multimedia/Sound/Players
@ -79,6 +79,7 @@ done
[ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode [ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode
[ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist [ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist
[ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength [ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength
[ -f /var/lib/mympd/state/autoPlay ] || echo -n "false" > /var/lib/mympd/state/autoPlay
[ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage [ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage
[ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb [ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb
[ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase [ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase

View File

@ -1,17 +1,16 @@
#myMPD config file #myMPD config file
#Loglevel [mpd]
#0 = quiet
#1 = default
#2 = verbose
#3 = debug
loglevel = 1
#Connection to mpd #Connection to mpd
mpdhost = 127.0.0.1 mpdhost = 127.0.0.1
mpdport = 6600 mpdport = 6600
#mpdpass = #mpdpass =
#Port for mpd http stream
streamport = 8000
[webserver]
#Webserver options #Webserver options
webport = 80 webport = 80
@ -21,15 +20,21 @@ sslport = 443
sslcert = /etc/mympd/ssl/server.pem sslcert = /etc/mympd/ssl/server.pem
sslkey = /etc/mympd/ssl/server.key sslkey = /etc/mympd/ssl/server.key
[mympd]
#Loglevel
#0 = quiet
#1 = default
#2 = verbose
#3 = debug
loglevel = 1
#myMPD user #myMPD user
user = mympd user = mympd
#Enable local player, needs streamport or streamurl setting #Enable local player, needs streamport or streamurl setting
localplayer = true localplayer = true
#Port for mpd http stream
streamport = 8000
#Manual streamurl, overwrites streamport #Manual streamurl, overwrites streamport
#streamurl = http://mpdhost:8000 #streamurl = http://mpdhost:8000
@ -67,3 +72,7 @@ max_elements_per_page = 100
#Number of songs to keep in last played list #Number of songs to keep in last played list
last_played_count = 20 last_played_count = 20
[theme]
backgroundcolor: #888

5
debian/changelog vendored
View File

@ -1,5 +1,4 @@
mympd (4.8.0-1) stable; urgency=medium mympd (5.0.0-1) stable; urgency=medium
* Release from master * Release from master
-- Juergen Mang <mail@jcgames.de> Fri, 07 Dec 2018 17:12:12 +0000 -- Juergen Mang <mail@jcgames.de> Fri, 04 Jan 2019 09:01:07 +0000

3
debian/postinst vendored
View File

@ -6,8 +6,6 @@ getent group mympd > /dev/null
getent passwd mympd > /dev/null getent passwd mympd > /dev/null
[ "$?" = "2" ] && useradd -r mympd -g mympd -d /var/lib/mympd -s /usr/sbin/nologin [ "$?" = "2" ] && useradd -r mympd -g mympd -d /var/lib/mympd -s /usr/sbin/nologin
echo "Trying to link musicdir to library" echo "Trying to link musicdir to library"
if [ -f /etc/mpd.conf ] if [ -f /etc/mpd.conf ]
then then
@ -59,6 +57,7 @@ fi
[ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode [ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode
[ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist [ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist
[ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength [ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength
[ -f /var/lib/mympd/state/autoPlay ] || echo -n "false" > /var/lib/mympd/state/autoPlay
[ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage [ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage
[ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb [ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb
[ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase [ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase

View File

@ -1 +1,346 @@
:root{--mympd-coverimagesize:240px}html{position:relative;min-height:100%}body{padding-top:50px;padding-bottom:50px;background-color:#888}main{padding-top:10px}footer{position:absolute;bottom:0}button{overflow:hidden}.cardBodyPlayback{padding-bottom:0}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}[data-col=Pos],[data-col=Type],[data-col=Track],[data-col=Action]{width:30px}small{color:#aaa}.card{min-height:300px}.cardFooterPlayback{padding:0}.album-cover{background-size:cover;background-image:url("/assets/coverimage-loading.png");border:1px solid black;border-radius:5px;overflow:hidden;width:var(--mympd-coverimagesize);max-width:100%;height:var(--mympd-coverimagesize);background-color:#eee;float:left;margin-right:1.25rem;margin-bottom:1.25rem}.album-desc{min-width:240px;float:left;padding-bottom:1.25rem}.hide{display:none!important}.pull-right{float:right!important}.card-toolbar{margin-bottom:10px}.card-toolbar>div,.card-toolbar>form{margin-bottom:5px}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(/assets/MaterialIcons-Regular.woff2) format('woff2'),url(/assets/MaterialIcons-Regular.woff) format('woff'),url(/assets/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:18px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;vertical-align:top;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons-left{font-size:1rem;margin-left:-1em;vertical-align:middle}.material-icons-small{font-size:16px}.material-icons-small-left{font-size:1rem;margin-left:-1em}.color-darkgrey,.color-darkgrey:hover{color:#6c757d!important}#btn-outputs-block>button{margin-bottom:10px}#btn-outputs-block>button:last-child{margin-bottom:0}.card-body{overflow-x:hidden}.progressBarPlay{font-size:22px}#counter{cursor:text}#volumeBar{margin-top:2px;width:160px}.title-icon{float:left;margin-right:5px;font-size:1.8rem}.header-logo{font-size:2rem;float:left;margin-right:5px}.letters>button{width:28px;height:28px}.col-md{max-width:250px;min-width:250px}a.card-img-left{overflow:hidden;display:block;width:var(--mympd-coverimagesize);height:var(--mympd-coverimagesize);border-radius:5px;background-size:cover;background-image:url(/assets/coverimage-loading.png);margin-bottom:5px;cursor:pointer}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!important}button.active-fg-green{color:#28a745!important}button.active-fg-red{color:#bd2130!important}div#alertBox{position:fixed;top:50px;right:10px;width:80%;max-width:400px;z-index:1000;opacity:0;visibility:visible;transition:opacity .5s ease-in}div.alertBoxActive{opacity:1!important;visibility:visible!important;transition:opacity .5s ease-in}.popover-content{padding-top:4px;padding-bottom:4px}.opacity05{opacity:.5}caption{caption-side:top;font-size:120%;font-weight:bold;color:black}.dragover>td{border-top:2px solid #28a745}.dragover-th{border-left:2px solid #28a745}[draggable]{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element;cursor:pointer}@keyframes changewidth{0%{margin-left:-20px}to{margin-left:100%}}#updateDBprogress{width:20px}.updateDBprogressAnimate{animation-duration:2s;animation-name:changewidth;animation-iteration-count:infinite}.modal-body{overflow-x:hidden}.modal-body .album-cover{float:none}#BrowseDatabaseAlbumListCaption{margin-left:15px;margin-right:15px;width:100%}#BrowseDatabaseAlbumListCaption h2{display:inline}#BrowseDatabaseAlbumListCaption small{padding-top:.8rem}#menu-dbupdate{padding-left:1rem}div.key{border:1px solid #bbb;background-color:#eee;border-radius:2px;width:20px;heigth:20px;text-align:center}ol#searchCrumb{padding:.5rem}.nodropdown::after{content:none} :root {
--mympd-coverimagesize: 240px;
--mympd-bacgkroundcolor: #888;
}
html {
position: relative;
min-height: 100%;
}
body {
padding-top: 4rem;
padding-bottom: 4rem;
background-color: var(--mympd-backgroundcolor, #888);
}
main {
padding-top: 1rem;
}
footer {
position: absolute;
bottom: 0;
}
button {
overflow: hidden;
}
.cardBodyPlayback {
padding-bottom:0px;
}
#BrowseBreadrumb {
overflow: auto;
white-space: nowrap;
}
#BrowseBreadcrumb > li > a {
cursor: pointer;
}
@media only screen and (max-width: 576px) {
.header-logo {
display:none !important;
}
}
.clickable {
cursor: pointer;
}
[data-col=Pos], [data-col=Type], [data-col=Track], [data-col=Action] {
width: 2rem;
}
small {
color: #aaa;
}
.card {
min-height: 300px;
}
.cardFooterPlayback {
padding: 0px;
}
.album-cover {
background-size: cover;
background-image: url("/assets/coverimage-loading.png");
border: 1px solid black;
border-radius: 5px;
overflow: hidden;
width: var(--mympd-coverimagesize, 250px);
max-width:100%;
height: var(--mympd-coverimagesize, 250px);
background-color: #eee;
float: left;
margin-right: 1.25rem;
margin-bottom: 1.25rem;
}
.album-desc {
min-width: 240px;
float: left;
padding-bottom:1.25rem;
}
.hide {
display: none !important;
}
.unvisible {
visibility: hidden;
}
.pull-right {
float: right !important;
}
.card-toolbar {
margin-bottom: 10px;
}
.card-toolbar > div, .card-toolbar > form {
margin-bottom: 5px;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(/assets/MaterialIcons-Regular.woff2) format('woff2'),
url(/assets/MaterialIcons-Regular.woff) format('woff'),
url(/assets/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 1.4rem; /* Preferred icon size */
display:inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
vertical-align: top;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
.material-icons-left {
font-size: 1rem;
margin-left: -1em;
vertical-align:middle;
}
.material-icons-small {
font-size: 1rem;
}
.material-icons-small-left {
font-size: 1rem;
margin-left: -1em;
}
.color-darkgrey, .color-darkgrey:hover {
color:#6c757d !important;
}
#btn-outputs-block > button {
margin-bottom:10px;
}
#btn-outputs-block > button:last-child {
margin-bottom:0px;
}
.card-body {
overflow-x:hidden;
}
.progressBarPlay {
font-size: 1.8rem;
}
#counter {
cursor: text;
}
#volumeBar {
margin-top:2px;
width:160px;
}
.title-icon {
float:left;
margin-right:5px;
font-size:1.8rem;
}
.header-logo {
font-size: 2rem;
float: left;
margin-right: 5px;
}
.letters > button {
width: 28px;
height: 28px;
}
.col-md {
max-width: 250px;
min-width: 250px;
}
a.card-img-left {
overflow: hidden;
display: block;
width: var(--mympd-coverimagesize);
height: var(--mympd-coverimagesize);
border-radius: 5px;
background-size: cover;
background-image: url(/assets/coverimage-loading.png);
margin-bottom: 5px;
cursor: pointer;
}
button.active {
color: #fff;
background-color: #28a745 !important;
border-color: #28a745 !important;
}
button.active-fg-green {
color: #28a745 !important;
}
button.active-fg-red {
color: #bd2130 !important;
}
div#alertBox {
position:fixed;
top: 4rem;
right:1rem;
width:80%;
max-width:400px;
z-index:1000;
opacity:0;
visibility:visible;
transition:opacity 0.5s ease-in;
}
div.alertBoxActive {
opacity:1 !important;
visibility:visible !important;
transition:opacity 0.5s ease-in;
}
.popover-content {
padding-top:4px;
padding-bottom:4px;
}
.opacity05 {
opacity:0.5;
}
caption {
caption-side: top;
font-size: 120%;
font-weight: bold;
color: black;
}
.dragover > td {
border-top:2px solid #28a745;
}
.dragover-th {
border-left:2px solid #28a745;
}
[draggable] {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
/* Required to make elements draggable in old WebKit */
-khtml-user-drag: element;
-webkit-user-drag: element;
cursor: pointer;
}
@keyframes changewidth {
from { margin-left: -20px; }
to { margin-left: 100%; }
}
#updateDBprogress {
width:20px;
}
.updateDBprogressAnimate {
animation-duration: 2s;
animation-name: changewidth;
animation-iteration-count: infinite;
}
.modal-body {
overflow-x: hidden;
}
.modal-body .album-cover {
float:none;
}
#BrowseDatabaseAlbumListCaption {
margin-left: 15px;
margin-right: 15px;
width: 100%;
}
#BrowseDatabaseAlbumListCaption h2 {
display: inline;
}
#BrowseDatabaseAlbumListCaption small {
padding-top: 0.8rem;
}
#menu-dbupdate {
padding-left:1rem;
}
div.key {
border: 1px solid #bbb;
background-color: #eee;
border-radius: 2px;
width: 20px;
height: 20px;
text-align: center;
}
ol#searchCrumb {
padding: .5rem;
}
.nodropdown::after {
content: none;
}

File diff suppressed because one or more lines are too long

View File

@ -5,62 +5,67 @@ $jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[
$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Object.is",function(a){return a?a:function(a,c){return a===c?0!==a||1/a===1/c:a!==a&&c!==c}},"es6","es3"); $jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Object.is",function(a){return a?a:function(a,c){return a===c?0!==a||1/a===1/c:a!==a&&c!==c}},"es6","es3");
$jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var d=this;d instanceof String&&(d=String(d));var b=d.length;c=c||0;for(0>c&&(c=Math.max(c+b,0));c<b;c++){var f=d[c];if(f===a||Object.is(f,a))return!0}return!1}},"es7","es3"); $jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var d=this;d instanceof String&&(d=String(d));var b=d.length;c=c||0;for(0>c&&(c=Math.max(c+b,0));c<b;c++){var f=d[c];if(f===a||Object.is(f,a))return!0}return!1}},"es7","es3");
$jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};$jscomp.polyfill("String.prototype.includes",function(a){return a?a:function(a,c){return-1!==$jscomp.checkStringArgs(this,a,"includes").indexOf(a,c||0)}},"es6","es3"); $jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};$jscomp.polyfill("String.prototype.includes",function(a){return a?a:function(a,c){return-1!==$jscomp.checkStringArgs(this,a,"includes").indexOf(a,c||0)}},"es6","es3");
$jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){var b=$jscomp.checkStringArgs(this,null,"repeat");if(0>a||1342177279<a)throw new RangeError("Invalid count value");a|=0;for(var d="";a;)if(a&1&&(d+=b),a>>>=1)b+=b;return d}},"es6","es3"); $jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){var b=$jscomp.checkStringArgs(this,null,"repeat");if(0>a||1342177279<a)throw new RangeError("Invalid count value");a|=0;for(var d="";a;)if(a&1&&(d+=b),a>>>=1)b+=b;return d}},"es6","es3");$jscomp.owns=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};
var socket,lastSong="",lastSongObj={},lastState,currentSong={},playstate="",settings={},alertTimeout,progressTimer,deferredPrompt,dragEl,playlistEl,app={apps:{Playback:{state:"0/-/",scrollPos:0},Queue:{active:"Current",tabs:{Current:{state:"0/any/",scrollPos:0},LastPlayed:{state:"0/any/",scrollPos:0}}},Browse:{active:"Database",tabs:{Filesystem:{state:"0/-/",scrollPos:0},Playlists:{active:"All",views:{All:{state:"0/-/",scrollPos:0},Detail:{state:"0/-/",scrollPos:0}}},Database:{active:"AlbumArtist", $jscomp.assign="function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var e in d)$jscomp.owns(d,e)&&(a[e]=d[e])}return a};$jscomp.polyfill("Object.assign",function(a){return a||$jscomp.assign},"es6","es3");
views:{}}}},Search:{state:"0/any/",scrollPos:0}},current:{app:"Playback",tab:void 0,view:void 0,page:0,filter:"",search:"",scrollPos:0},last:{app:void 0,tab:void 0,view:void 0,filter:"",search:"",scrollPos:0}},domCache={};domCache.navbarBottomBtns=document.getElementById("navbar-bottom").getElementsByTagName("div");domCache.navbarBottomBtnsLen=domCache.navbarBottomBtns.length;domCache.cardHeaderBrowse=document.getElementById("cardHeaderBrowse").getElementsByTagName("a"); var socket,lastSong="",lastSongObj={},lastState,currentSong={},playstate="",settingsLock=!1,settingsParsed=!1,settingsNew={},settings={},alertTimeout,progressTimer,deferredPrompt,dragEl,playlistEl,websocketConnected=!1,websocketTimer=null,appInited=!1,app={apps:{Playback:{state:"0/-/-/",scrollPos:0},Queue:{active:"Current",tabs:{Current:{state:"0/any/-/",scrollPos:0},LastPlayed:{state:"0/any/-/",scrollPos:0}}},Browse:{active:"Database",tabs:{Filesystem:{state:"0/-/-/",scrollPos:0},Playlists:{active:"All",
domCache.cardHeaderBrowseLen=domCache.cardHeaderBrowse.length;domCache.cardHeaderQueue=document.getElementById("cardHeaderQueue").getElementsByTagName("a");domCache.cardHeaderQueueLen=domCache.cardHeaderQueue.length;domCache.counter=document.getElementById("counter");domCache.volumePrct=document.getElementById("volumePrct");domCache.volumeControl=document.getElementById("volumeControl");domCache.volumeMenu=document.getElementById("volumeMenu");domCache.btnsPlay=document.getElementsByClassName("btnPlay"); views:{All:{state:"0/-/-/",scrollPos:0},Detail:{state:"0/-/-/",scrollPos:0}}},Database:{active:"AlbumArtist",views:{}}}},Search:{state:"0/any/-/",scrollPos:0}},current:{app:"Playback",tab:void 0,view:void 0,page:0,filter:"",search:"",sort:"",scrollPos:0},last:{app:void 0,tab:void 0,view:void 0,filter:"",search:"",sort:"",scrollPos:0}},domCache={};domCache.navbarBottomBtns=document.getElementById("navbar-bottom").getElementsByTagName("div");domCache.navbarBottomBtnsLen=domCache.navbarBottomBtns.length;
domCache.btnsPlayLen=domCache.btnsPlay.length;domCache.btnPrev=document.getElementById("btnPrev");domCache.btnNext=document.getElementById("btnNext");domCache.progressBar=document.getElementById("progressBar");domCache.volumeBar=document.getElementById("volumeBar");domCache.outputs=document.getElementById("outputs");domCache.btnAdd=document.getElementById("nav-add2homescreen");domCache.currentCover=document.getElementById("currentCover");domCache.currentTitle=document.getElementById("currentTitle"); domCache.cardHeaderBrowse=document.getElementById("cardHeaderBrowse").getElementsByTagName("a");domCache.cardHeaderBrowseLen=domCache.cardHeaderBrowse.length;domCache.cardHeaderQueue=document.getElementById("cardHeaderQueue").getElementsByTagName("a");domCache.cardHeaderQueueLen=domCache.cardHeaderQueue.length;domCache.counter=document.getElementById("counter");domCache.volumePrct=document.getElementById("volumePrct");domCache.volumeControl=document.getElementById("volumeControl");
domCache.btnVoteUp=document.getElementById("btnVoteUp");domCache.btnVoteDown=document.getElementById("btnVoteDown");domCache.badgeQueueItems=document.getElementById("badgeQueueItems");domCache.searchstr=document.getElementById("searchstr");domCache.searchCrumb=document.getElementById("searchCrumb"); domCache.volumeMenu=document.getElementById("volumeMenu");domCache.btnsPlay=document.getElementsByClassName("btnPlay");domCache.btnsPlayLen=domCache.btnsPlay.length;domCache.btnPrev=document.getElementById("btnPrev");domCache.btnNext=document.getElementById("btnNext");domCache.progressBar=document.getElementById("progressBar");domCache.volumeBar=document.getElementById("volumeBar");domCache.outputs=document.getElementById("outputs");domCache.btnAdd=document.getElementById("nav-add2homescreen");
domCache.currentCover=document.getElementById("currentCover");domCache.currentTitle=document.getElementById("currentTitle");domCache.btnVoteUp=document.getElementById("btnVoteUp");domCache.btnVoteDown=document.getElementById("btnVoteDown");domCache.badgeQueueItems=document.getElementById("badgeQueueItems");domCache.searchstr=document.getElementById("searchstr");domCache.searchCrumb=document.getElementById("searchCrumb");
var modalConnectionError=new Modal(document.getElementById("modalConnectionError"),{backdrop:"static",keyboard:!1}),modalSettings=new Modal(document.getElementById("modalSettings")),modalAbout=new Modal(document.getElementById("modalAbout")),modalSavequeue=new Modal(document.getElementById("modalSaveQueue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),modalAddToPlaylist=new Modal(document.getElementById("modalAddToPlaylist")),modalRenamePlaylist=new Modal(document.getElementById("modalRenamePlaylist")), var modalConnectionError=new Modal(document.getElementById("modalConnectionError"),{backdrop:"static",keyboard:!1}),modalSettings=new Modal(document.getElementById("modalSettings")),modalAbout=new Modal(document.getElementById("modalAbout")),modalSavequeue=new Modal(document.getElementById("modalSaveQueue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),modalAddToPlaylist=new Modal(document.getElementById("modalAddToPlaylist")),modalRenamePlaylist=new Modal(document.getElementById("modalRenamePlaylist")),
modalUpdateDB=new Modal(document.getElementById("modalUpdateDB")),modalSaveSmartPlaylist=new Modal(document.getElementById("modalSaveSmartPlaylist")),modalDeletePlaylist=new Modal(document.getElementById("modalDeletePlaylist")),modalHelp=new Modal(document.getElementById("modalHelp")),dropdownMainMenu,dropdownVolumeMenu=new Dropdown(document.getElementById("volumeMenu")),collapseDBupdate=new Collapse(document.getElementById("navDBupdate")); modalUpdateDB=new Modal(document.getElementById("modalUpdateDB")),modalSaveSmartPlaylist=new Modal(document.getElementById("modalSaveSmartPlaylist")),modalDeletePlaylist=new Modal(document.getElementById("modalDeletePlaylist")),modalHelp=new Modal(document.getElementById("modalHelp")),modalAppInit=new Modal(document.getElementById("modalAppInit"),{backdrop:"static",keyboard:!1}),dropdownMainMenu,dropdownVolumeMenu=new Dropdown(document.getElementById("volumeMenu")),collapseDBupdate=new Collapse(document.getElementById("navDBupdate"));
function appPrepare(a){if(app.current.app!=app.last.app||app.current.tab!=app.last.tab||app.current.view!=app.last.view){for(var b=0;b<domCache.navbarBottomBtnsLen;b++)domCache.navbarBottomBtns[b].classList.remove("active");document.getElementById("cardPlayback").classList.add("hide");document.getElementById("cardQueue").classList.add("hide");document.getElementById("cardBrowse").classList.add("hide");document.getElementById("cardSearch").classList.add("hide");for(b=0;b<domCache.cardHeaderBrowseLen;b++)domCache.cardHeaderBrowse[b].classList.remove("active"); function appPrepare(a){if(app.current.app!=app.last.app||app.current.tab!=app.last.tab||app.current.view!=app.last.view){for(var b=0;b<domCache.navbarBottomBtnsLen;b++)domCache.navbarBottomBtns[b].classList.remove("active");document.getElementById("cardPlayback").classList.add("hide");document.getElementById("cardQueue").classList.add("hide");document.getElementById("cardBrowse").classList.add("hide");document.getElementById("cardSearch").classList.add("hide");for(b=0;b<domCache.cardHeaderBrowseLen;b++)domCache.cardHeaderBrowse[b].classList.remove("active");
for(b=0;b<domCache.cardHeaderQueueLen;b++)domCache.cardHeaderQueue[b].classList.remove("active");document.getElementById("cardQueueCurrent").classList.add("hide");document.getElementById("cardQueueLastPlayed").classList.add("hide");document.getElementById("cardBrowsePlaylists").classList.add("hide");document.getElementById("cardBrowseDatabase").classList.add("hide");document.getElementById("cardBrowseFilesystem").classList.add("hide");document.getElementById("card"+app.current.app).classList.remove("hide"); for(b=0;b<domCache.cardHeaderQueueLen;b++)domCache.cardHeaderQueue[b].classList.remove("active");document.getElementById("cardQueueCurrent").classList.add("hide");document.getElementById("cardQueueLastPlayed").classList.add("hide");document.getElementById("cardBrowsePlaylists").classList.add("hide");document.getElementById("cardBrowseDatabase").classList.add("hide");document.getElementById("cardBrowseFilesystem").classList.add("hide");document.getElementById("card"+app.current.app).classList.remove("hide");
document.getElementById("nav"+app.current.app)&&document.getElementById("nav"+app.current.app).classList.add("active");void 0!=app.current.tab&&(document.getElementById("card"+app.current.app+app.current.tab).classList.remove("hide"),document.getElementById("card"+app.current.app+"Nav"+app.current.tab).classList.add("active"));scrollTo(a)}(a=document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+(void 0==app.current.view?"":app.current.view)+"List"))&&a.classList.add("opacity05")} document.getElementById("nav"+app.current.app)&&document.getElementById("nav"+app.current.app).classList.add("active");void 0!=app.current.tab&&(document.getElementById("card"+app.current.app+app.current.tab).classList.remove("hide"),document.getElementById("card"+app.current.app+"Nav"+app.current.tab).classList.add("active"));scrollTo(a)}(a=document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+(void 0==app.current.view?"":app.current.view)+"List"))&&a.classList.add("opacity05")}
function appGoto(a,b,c,d){var e=document.body.scrollTop?document.body.scrollTop:document.documentElement.scrollTop;void 0!=app.apps[app.current.app].scrollPos?app.apps[app.current.app].scrollPos=e:void 0!=app.apps[app.current.app].tabs[app.current.tab].scrollPos?app.apps[app.current.app].tabs[app.current.tab].scrollPos=e:void 0!=app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos&&(app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos=e);app.apps[a].tabs? function appGoto(a,b,c,d){var e=document.body.scrollTop?document.body.scrollTop:document.documentElement.scrollTop;void 0!=app.apps[app.current.app].scrollPos?app.apps[app.current.app].scrollPos=e:void 0!=app.apps[app.current.app].tabs[app.current.tab].scrollPos?app.apps[app.current.app].tabs[app.current.tab].scrollPos=e:void 0!=app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos&&(app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos=e);app.apps[a].tabs?
(void 0==b&&(b=app.apps[a].active),app.apps[a].tabs[b].views?(void 0==c&&(c=app.apps[a].tabs[b].active),a="/"+a+"/"+b+"/"+c+"!"+(void 0==d?app.apps[a].tabs[b].views[c].state:d)):a="/"+a+"/"+b+"!"+(void 0==d?app.apps[a].tabs[b].state:d)):a="/"+a+"!"+(void 0==d?app.apps[a].state:d);location.hash=a} (void 0==b&&(b=app.apps[a].active),app.apps[a].tabs[b].views?(void 0==c&&(c=app.apps[a].tabs[b].active),a="/"+a+"/"+b+"/"+c+"!"+(void 0==d?app.apps[a].tabs[b].views[c].state:d)):a="/"+a+"/"+b+"!"+(void 0==d?app.apps[a].tabs[b].state:d)):a="/"+a+"!"+(void 0==d?app.apps[a].state:d);location.hash=a}
function appRoute(){var a;if(a=decodeURI(location.hash).match(/^#\/(\w+)\/?(\w+)?\/?(\w+)?!((\d+)\/([^\/]+)\/(.*))$/)){app.current.app=a[1];app.current.tab=a[2];app.current.view=a[3];app.apps[app.current.app].state?(app.apps[app.current.app].state=a[4],app.current.scrollPos=app.apps[app.current.app].scrollPos):app.apps[app.current.app].tabs[app.current.tab].state?(app.apps[app.current.app].tabs[app.current.tab].state=a[4],app.apps[app.current.app].active=app.current.tab,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].scrollPos): function appRoute(){if(0==settingsParsed)appInitStart();else{var a;if(a=decodeURI(location.hash).match(/^#\/(\w+)\/?(\w+)?\/?(\w+)?!((\d+)\/([^\/]+)\/([^\/]+)\/(.*))$/)){app.current.app=a[1];app.current.tab=a[2];app.current.view=a[3];app.apps[app.current.app].state?(app.apps[app.current.app].state=a[4],app.current.scrollPos=app.apps[app.current.app].scrollPos):app.apps[app.current.app].tabs[app.current.tab].state?(app.apps[app.current.app].tabs[app.current.tab].state=a[4],app.apps[app.current.app].active=
app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state&&(app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state=a[4],app.apps[app.current.app].active=app.current.tab,app.apps[app.current.app].tabs[app.current.tab].active=app.current.view,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos);app.current.page=parseInt(a[5]);app.current.filter=a[6];app.current.search=a[7];appPrepare(app.current.scrollPos); app.current.tab,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].scrollPos):app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state&&(app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state=a[4],app.apps[app.current.app].active=app.current.tab,app.apps[app.current.app].tabs[app.current.tab].active=app.current.view,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos);app.current.page=
if("Playback"==app.current.app)sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);else if("Queue"==app.current.app&&"Current"==app.current.tab)selectTag("searchqueuetags","searchqueuetagsdesc",app.current.filter),getQueue();else if("Queue"==app.current.app&&"LastPlayed"==app.current.tab)sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"All"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_LIST", parseInt(a[5]);app.current.filter=a[6];app.current.sort=a[7];app.current.search=a[8];appPrepare(app.current.scrollPos);if("Playback"==app.current.app)sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);else if("Queue"==app.current.app&&"Current"==app.current.tab)selectTag("searchqueuetags","searchqueuetagsdesc",app.current.filter),getQueue();else if("Queue"==app.current.app&&"LastPlayed"==app.current.tab)sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);
data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Database"==app.current.tab)""!=app.current.search?(sendAPI({cmd:"MPD_API_DATABASE_TAG_ALBUM_LIST", else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"All"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");
data:{offset:app.current.page,filter:app.current.filter,search:app.current.search,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter")):(sendAPI({cmd:"MPD_API_DATABASE_TAG_LIST",data:{offset:app.current.page,filter:app.current.filter,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter"),selectTag("BrowseDatabaseByTagDropdown","btnBrowseDatabaseByTag",app.current.view));else if("Browse"==app.current.app&&"Filesystem"==app.current.tab){sendAPI({cmd:"MPD_API_DATABASE_FILESYSTEM_LIST", else if("Browse"==app.current.app&&"Database"==app.current.tab)""!=app.current.search?(sendAPI({cmd:"MPD_API_DATABASE_TAG_ALBUM_LIST",data:{offset:app.current.page,filter:app.current.filter,search:app.current.search,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter")):(sendAPI({cmd:"MPD_API_DATABASE_TAG_LIST",data:{offset:app.current.page,filter:app.current.filter,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter"),selectTag("BrowseDatabaseByTagDropdown",
data:{offset:app.current.page,path:app.current.search?app.current.search:"/",filter:app.current.filter}},parseFilesystem);app.current.search?(document.getElementById("BrowseFilesystemAddAllSongs").removeAttribute("disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").removeAttribute("disabled")):(document.getElementById("BrowseFilesystemAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").setAttribute("disabled","disabled"));var b= "btnBrowseDatabaseByTag",app.current.view));else if("Browse"==app.current.app&&"Filesystem"==app.current.tab){sendAPI({cmd:"MPD_API_DATABASE_FILESYSTEM_LIST",data:{offset:app.current.page,path:app.current.search?app.current.search:"/",filter:app.current.filter}},parseFilesystem);app.current.search?(document.getElementById("BrowseFilesystemAddAllSongs").removeAttribute("disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").removeAttribute("disabled")):(document.getElementById("BrowseFilesystemAddAllSongs").setAttribute("disabled",
'<li class="breadcrumb-item"><a data-uri="">root</a></li>',c=app.current.search.split("/"),d=c.length,e="";for(a=0;a<d;a++){if(d-1==a){b+='<li class="breadcrumb-item active">'+c[a]+"</li>";break}e+=c[a];b+='<li class="breadcrumb-item"><a data-uri="'+e+'">'+c[a]+"</a></li>";e+="/"}a=document.getElementById("BrowseBreadcrumb");a.innerHTML=b;b=a.getElementsByTagName("a");c=b.length;for(a=0;a<c;a++)b[a].addEventListener("click",function(){appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+ "disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").setAttribute("disabled","disabled"));var b='<li class="breadcrumb-item"><a data-uri="">root</a></li>',c=app.current.search.split("/"),d=c.length,e="";for(a=0;a<d;a++){if(d-1==a){b+='<li class="breadcrumb-item active">'+c[a]+"</li>";break}e+=c[a];b+='<li class="breadcrumb-item"><a data-uri="'+e+'">'+c[a]+"</a></li>";e+="/"}a=document.getElementById("BrowseBreadcrumb");a.innerHTML=b;b=a.getElementsByTagName("a");c=b.length;for(a=
this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else if("Search"==app.current.app){domCache.searchstr.focus();if(settings.featAdvsearch){b="";c=app.current.search.substring(1,app.current.search.length-1).split(" AND ");for(a=0;a<c.length-1;a++)d=c[a].substring(1,c[a].length-1),b+='<button data-filter="'+encodeURI(d)+'" class="btn btn-light mr-2">'+d+'<span class="ml-2 badge badge-secondary">&times</span></button>';domCache.searchCrumb.innerHTML=b;""==domCache.searchstr.value&& 0;a<c;a++)b[a].addEventListener("click",function(){appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+app.current.sort+"/"+this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else if("Search"==app.current.app){domCache.searchstr.focus();if(settings.featAdvsearch){b="";c=app.current.search.substring(1,app.current.search.length-1).split(" AND ");for(a=0;a<c.length-1;a++)d=c[a].substring(1,c[a].length-1),b+='<button data-filter="'+encodeURI(d)+'" class="btn btn-light mr-2">'+
1<=c.length&&(a=c[c.length-1].substring(1,c[c.length-1].length-1),b=a.substring(a.indexOf("'")+1,a.length-1),domCache.searchstr.value!=b&&(domCache.searchCrumb.innerHTML+='<button data-filter="'+encodeURI(a)+'" class="btn btn-light mr-2">'+a+'<span href="#" class="ml-2 badge badge-secondary">&times;</span></button>'),a=a.substring(a.indexOf(" ")+1),a=a.substring(0,a.indexOf(" ")),""==a&&(a="contains"),document.getElementById("searchMatch").value=a)}else""==domCache.searchstr.value&&""!=app.current.search&& d+'<span class="ml-2 badge badge-secondary">&times</span></button>';domCache.searchCrumb.innerHTML=b;""==domCache.searchstr.value&&1<=c.length&&(a=c[c.length-1].substring(1,c[c.length-1].length-1),b=a.substring(a.indexOf("'")+1,a.length-1),domCache.searchstr.value!=b&&(domCache.searchCrumb.innerHTML+='<button data-filter="'+encodeURI(a)+'" class="btn btn-light mr-2">'+a+'<span href="#" class="ml-2 badge badge-secondary">&times;</span></button>'),a=a.substring(a.indexOf(" ")+1),a=a.substring(0,a.indexOf(" ")),
(domCache.searchstr.value=app.current.search);app.last.app!=app.current.app&&""!=app.current.search&&(a=settings["cols"+app.current.app].length,a--,document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="'+a+'">Searching...</td></tr>');2<=domCache.searchstr.value.length||0<domCache.searchCrumb.children.length?settings.featAdvsearch?(a=document.getElementById("SearchList").getAttribute("data-sort"),b=!1,""== ""==a&&(a="contains"),document.getElementById("searchMatch").value=a)}else""==domCache.searchstr.value&&""!=app.current.search&&(domCache.searchstr.value=app.current.search);app.last.app!=app.current.app&&""!=app.current.search&&(a=settings["cols"+app.current.app].length,a--,document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="'+a+'">Searching...</td></tr>');2<=domCache.searchstr.value.length||0<domCache.searchCrumb.children.length?
a?(a=settings.tags.includes("Title")?"Title":"",document.getElementById("SearchList").setAttribute("data-sort",a)):0==a.indexOf("-")&&(b=!0,a=a.substring(1)),sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:"",offset:app.current.page,sort:a,sortdesc:b,expression:app.current.search}},parseSearch)):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:"",offset:app.current.page,filter:app.current.filter,searchstr:app.current.search}},parseSearch):(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML= settings.featAdvsearch?(a=app.current.sort,b=!1,"-"==a?(a=settings.tags.includes("Title")?"Title":"-",document.getElementById("SearchList").setAttribute("data-sort",a)):0==a.indexOf("-")&&(b=!0,a=a.substring(1)),sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:"",offset:app.current.page,sort:a,sortdesc:b,expression:app.current.search}},parseSearch)):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:"",offset:app.current.page,filter:app.current.filter,searchstr:app.current.search}},parseSearch):
"",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",document.getElementById("cardFooterSearch").innerText="",document.getElementById("SearchList").classList.remove("opacity05"),setPagination(0,0));selectTag("searchtags","searchtagsdesc",app.current.filter)}else appGoto("Playback");app.last.app=app.current.app;app.last.tab= (document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML="",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",document.getElementById("cardFooterSearch").innerText="",document.getElementById("SearchList").classList.remove("opacity05"),setPagination(0,0));selectTag("searchtags","searchtagsdesc",app.current.filter)}else appGoto("Playback");
app.current.tab;app.last.view=app.current.view}else appGoto("Playback")} app.last.app=app.current.app;app.last.tab=app.current.tab;app.last.view=app.current.view}else appGoto("Playback")}}
function appInit(){webSocketConnect();getSettings();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);domCache.volumeBar.value=0;document.getElementById("btnChVolumeDown").addEventListener("click",function(a){a.stopPropagation()},!1);document.getElementById("btnChVolumeUp").addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET", function appInitStart(){appInited=!1;document.getElementsByTagName("header")[0].classList.add("hide");document.getElementsByTagName("main")[0].classList.add("hide");document.getElementsByTagName("footer")[0].classList.add("hide");document.getElementById("appInitSettings").classList.add("unvisible");document.getElementById("appInitWebsocket").classList.add("unvisible");document.getElementById("appInitApply").classList.add("unvisible");modalAppInit.show();getSettings();appInitWait()}
data:{volume:domCache.volumeBar.value}})},!1);domCache.progressBar.value=0;domCache.progressBar.addEventListener("change",function(a){currentSong&&0<=currentSong.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_SEEK",data:{songid:currentSong.currentSongId,seek:Math.ceil(domCache.progressBar.value/1E3*currentSong.totalTime)}})},!1);document.getElementById("navDBupdate").addEventListener("click",function(a){a.stopPropagation();a.preventDefault();a=this.getElementsByTagName("span")[0];a.innerText="keyboard_arrow_right"== function appInitWait(){setTimeout(function(){1==settingsParsed&&1==websocketConnected?(document.getElementById("appInitWebsocket").classList.remove("unvisible"),appInit(),document.getElementById("appInitApply").classList.remove("unvisible"),document.getElementsByTagName("header")[0].classList.remove("hide"),document.getElementsByTagName("main")[0].classList.remove("hide"),document.getElementsByTagName("footer")[0].classList.remove("hide"),modalAppInit.hide(),appInited=!0):(1==settingsParsed&&(document.getElementById("appInitSettings").classList.remove("unvisible"),
a.innerText?"keyboard_arrow_down":"keyboard_arrow_right"},!1);document.getElementById("volumeMenu").parentNode.addEventListener("show.bs.dropdown",function(){sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs)});document.getElementById("modalAbout").addEventListener("shown.bs.modal",function(){sendAPI({cmd:"MPD_API_DATABASE_STATS"},parseStats)});document.getElementById("modalAddToPlaylist").addEventListener("shown.bs.modal",function(){document.getElementById("addStreamFrm").classList.contains("hide")? webSocketConnect()),appInitWait())},500)}
document.getElementById("addToPlaylistPlaylist").focus():document.getElementById("streamUrl").focus()});document.getElementById("modalHelp").addEventListener("show.bs.modal",function(){var a="",b;for(b in keymap)if(void 0==keymap[b].req||1==settings[keymap[b].req])a+='<tr><td><div class="key'+(keymap[b].key&&1<keymap[b].key.length?" material-icons material-icons-small":"")+'">'+(void 0!=keymap[b].key?keymap[b].key:b)+"</div></td><td>"+keymap[b].desc+"</td></tr>";document.getElementById("tbodyShortcuts").innerHTML= function appInit(){domCache.volumeBar.value=0;document.getElementById("btnChVolumeDown").addEventListener("click",function(a){a.stopPropagation()},!1);document.getElementById("btnChVolumeUp").addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",data:{volume:domCache.volumeBar.value}})},!1);domCache.progressBar.value=
a});document.getElementById("modalUpdateDB").addEventListener("hidden.bs.modal",function(){document.getElementById("updateDBprogress").classList.remove("updateDBprogressAnimate")});document.getElementById("modalSaveQueue").addEventListener("shown.bs.modal",function(){var a=document.getElementById("saveQueueName");a.focus();a.value="";a.classList.remove("is-invalid");document.getElementById("saveQueueFrm").classList.remove("was-validated")});document.getElementById("modalSettings").addEventListener("shown.bs.modal", 0;domCache.progressBar.addEventListener("change",function(a){currentSong&&0<=currentSong.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_SEEK",data:{songid:currentSong.currentSongId,seek:Math.ceil(domCache.progressBar.value/1E3*currentSong.totalTime)}})},!1);document.getElementById("navDBupdate").addEventListener("click",function(a){a.stopPropagation();a.preventDefault();a=this.getElementsByTagName("span")[0];a.innerText="keyboard_arrow_right"==a.innerText?"keyboard_arrow_down":"keyboard_arrow_right"},
function(){getSettings();document.getElementById("settingsFrm").classList.remove("was-validated");document.getElementById("inputCrossfade").classList.remove("is-invalid");document.getElementById("inputMixrampdb").classList.remove("is-invalid");document.getElementById("inputMixrampdelay").classList.remove("is-invalid")});document.getElementById("selectJukeboxMode").addEventListener("change",function(){var a=this.options[this.selectedIndex].value;0==a||2==a?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled", !1);document.getElementById("volumeMenu").parentNode.addEventListener("show.bs.dropdown",function(){sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs)});document.getElementById("modalAbout").addEventListener("shown.bs.modal",function(){sendAPI({cmd:"MPD_API_DATABASE_STATS"},parseStats)});document.getElementById("modalAddToPlaylist").addEventListener("shown.bs.modal",function(){document.getElementById("addStreamFrm").classList.contains("hide")?document.getElementById("addToPlaylistPlaylist").focus():
"disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled","disabled")):1==a&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled"))});document.getElementById("addToPlaylistPlaylist").addEventListener("change",function(a){"New Playlist"==this.options[this.selectedIndex].text?(document.getElementById("addToPlaylistNewPlaylistDiv").classList.remove("hide"),document.getElementById("addToPlaylistNewPlaylist").focus()): document.getElementById("streamUrl").focus()});document.getElementById("modalHelp").addEventListener("show.bs.modal",function(){var a="",b;for(b in keymap)if(void 0==keymap[b].req||1==settings[keymap[b].req])a+='<tr><td><div class="key'+(keymap[b].key&&1<keymap[b].key.length?" material-icons material-icons-small":"")+'">'+(void 0!=keymap[b].key?keymap[b].key:b)+"</div></td><td>"+keymap[b].desc+"</td></tr>";document.getElementById("tbodyShortcuts").innerHTML=a});document.getElementById("modalUpdateDB").addEventListener("hidden.bs.modal",
document.getElementById("addToPlaylistNewPlaylistDiv").classList.add("hide")},!1);addFilterLetter("BrowseFilesystemFilterLetters");addFilterLetter("BrowseDatabaseFilterLetters");addFilterLetter("BrowsePlaylistsFilterLetters");document.getElementById("syscmds").addEventListener("click",function(a){"A"==a.target.nodeName&&parseCmd(a,a.target.getAttribute("data-href"))},!1);for(var a=document.querySelectorAll("[data-href]"),b=a.length,c=0;c<b;c++)a[c].classList.add("clickable"),a[c].addEventListener("click", function(){document.getElementById("updateDBprogress").classList.remove("updateDBprogressAnimate")});document.getElementById("modalSaveQueue").addEventListener("shown.bs.modal",function(){var a=document.getElementById("saveQueueName");a.focus();a.value="";a.classList.remove("is-invalid");document.getElementById("saveQueueFrm").classList.remove("was-validated")});document.getElementById("modalSettings").addEventListener("shown.bs.modal",function(){getSettings();document.getElementById("settingsFrm").classList.remove("was-validated");
function(a){parseCmd(a,this.getAttribute("data-href"))},!1);a=document.getElementsByClassName("pages");b=a.length;for(c=0;c<b;c++)a[c].addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&gotoPage(a.target.getAttribute("data-page"))},!1);document.getElementById("cardPlaybackTags").addEventListener("click",function(a){"H4"==a.target.nodeName&&gotoBrowse(a.target)},!1);document.getElementById("modalSongDetails").getElementsByTagName("tbody")[0].addEventListener("click",function(a){"A"== document.getElementById("inputCrossfade").classList.remove("is-invalid");document.getElementById("inputMixrampdb").classList.remove("is-invalid");document.getElementById("inputMixrampdelay").classList.remove("is-invalid")});document.getElementById("selectJukeboxMode").addEventListener("change",function(){var a=this.options[this.selectedIndex].value;0==a||2==a?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled","disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled",
a.target.nodeName?void 0!=a.target.parentNode.getAttribute("data-tag")&&(modalSongDetails.hide(),a.preventDefault(),gotoBrowse(a.target)):"BUTTON"==a.target.nodeName&&a.target.getAttribute("data-href")&&parseCmd(a,a.target.getAttribute("data-href"))},!1);document.getElementById("outputs").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&a.stopPropagation();sendAPI({cmd:"MPD_API_PLAYER_TOGGLE_OUTPUT",data:{output:a.target.getAttribute("data-output-id"),state:a.target.classList.contains("active")? "disabled")):1==a&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled"))});document.getElementById("addToPlaylistPlaylist").addEventListener("change",function(a){"New Playlist"==this.options[this.selectedIndex].text?(document.getElementById("addToPlaylistNewPlaylistDiv").classList.remove("hide"),document.getElementById("addToPlaylistNewPlaylist").focus()):document.getElementById("addToPlaylistNewPlaylistDiv").classList.add("hide")},
0:1}});toggleBtn(a.target.id)},!1);document.getElementById("QueueCurrentList").addEventListener("click",function(a){"TD"==a.target.nodeName?sendAPI({cmd:"MPD_API_PLAYER_PLAY_TRACK",data:{track:a.target.parentNode.getAttribute("data-trackid")}}):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("QueueLastPlayedList").addEventListener("click",function(a){"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemList").addEventListener("click", !1);addFilterLetter("BrowseFilesystemFilterLetters");addFilterLetter("BrowseDatabaseFilterLetters");addFilterLetter("BrowsePlaylistsFilterLetters");document.getElementById("syscmds").addEventListener("click",function(a){"A"==a.target.nodeName&&parseCmd(a,a.target.getAttribute("data-href"))},!1);for(var a=document.querySelectorAll("[data-href]"),b=a.length,c=0;c<b;c++)a[c].classList.add("clickable"),a[c].addEventListener("click",function(a){parseCmd(a,this.getAttribute("data-href"))},!1);a=document.getElementsByClassName("pages");
function(a){if("TD"==a.target.nodeName)switch(a.target.parentNode.getAttribute("data-type")){case "dir":appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+decodeURI(a.target.parentNode.getAttribute("data-uri")));break;case "song":appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"));break;case "plist":appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"))}else"A"== b=a.length;for(c=0;c<b;c++)a[c].addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&gotoPage(a.target.getAttribute("data-page"))},!1);document.getElementById("cardPlaybackTags").addEventListener("click",function(a){"H4"==a.target.nodeName&&gotoBrowse(a.target)},!1);document.getElementById("modalSongDetails").getElementsByTagName("tbody")[0].addEventListener("click",function(a){"A"==a.target.nodeName?void 0!=a.target.parentNode.getAttribute("data-tag")&&(modalSongDetails.hide(),a.preventDefault(),
a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsAllList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsDetailList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")), gotoBrowse(a.target)):"BUTTON"==a.target.nodeName&&a.target.getAttribute("data-href")&&parseCmd(a,a.target.getAttribute("data-href"))},!1);document.getElementById("outputs").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&a.stopPropagation();sendAPI({cmd:"MPD_API_PLAYER_TOGGLE_OUTPUT",data:{output:a.target.getAttribute("data-output-id"),state:a.target.classList.contains("active")?0:1}});toggleBtn(a.target.id)},!1);document.getElementById("QueueCurrentList").addEventListener("click",
a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseDatabaseTagList").addEventListener("click",function(a){"TD"==a.target.nodeName&&appGoto("Browse","Database",app.current.view,"0/-/"+a.target.parentNode.getAttribute("data-uri"))},!1);document.getElementById("SearchList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")): function(a){"TD"==a.target.nodeName?sendAPI({cmd:"MPD_API_PLAYER_PLAY_TRACK",data:{track:a.target.parentNode.getAttribute("data-trackid")}}):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("QueueLastPlayedList").addEventListener("click",function(a){"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemList").addEventListener("click",function(a){if("TD"==a.target.nodeName)switch(a.target.parentNode.getAttribute("data-type")){case "dir":appGoto("Browse",
"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowse():"Add all to playlist"==a.target.innerText&&showAddToPlaylist(app.current.search))},!1);document.getElementById("searchAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromSearchPlist("queue"): "Filesystem",void 0,"0/"+app.current.filter+"/"+app.current.sort+"/"+decodeURI(a.target.parentNode.getAttribute("data-uri")));break;case "song":appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"));break;case "plist":appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"))}else"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsAllList").addEventListener("click",
"Add all to playlist"==a.target.innerText?showAddToPlaylist("SEARCH"):"Save as smart playlist"==a.target.innerText&&saveSearchAsSmartPlaylist())},!1);document.getElementById("BrowseDatabaseAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowseDatabasePlist("queue"):"Add all to playlist"==a.target.innerText&&showAddToPlaylist("DATABASE"))},!1);document.getElementById("searchtags").addEventListener("click",function(a){"BUTTON"== function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsDetailList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseDatabaseTagList").addEventListener("click",
a.target.nodeName&&(app.current.filter=a.target.getAttribute("data-tag"),search(domCache.searchstr.value))},!1);document.getElementById("searchqueuestr").addEventListener("keyup",function(a){"Escape"==a.key?this.blur():appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+this.value)},!1);document.getElementById("searchqueuetags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+ function(a){"TD"==a.target.nodeName&&appGoto("Browse","Database",app.current.view,"0/-/-/"+a.target.parentNode.getAttribute("data-uri"))},!1);document.getElementById("SearchList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemAddAllSongsDropdown").addEventListener("click",
"/"+a.target.getAttribute("data-tag")+"/"+app.current.search)},!1);a="QueueCurrentColsDropdown BrowseFilesystemColsDropdown SearchColsDropdown BrowsePlaylistsDetailColsDropdown BrowseDatabaseColsDropdown PlaybackColsDropdown QueueLastPlayedColsDropdown".split(" ");for(c=0;c<a.length;c++)document.getElementById(a[c]).addEventListener("click",function(a){"INPUT"==a.target.nodeName&&a.stopPropagation()},!1);document.getElementById("search").addEventListener("submit",function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit", function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowse():"Add all to playlist"==a.target.innerText&&showAddToPlaylist(app.current.search))},!1);document.getElementById("searchAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromSearchPlist("queue"):"Add all to playlist"==a.target.innerText?showAddToPlaylist("SEARCH"):"Save as smart playlist"==a.target.innerText&&saveSearchAsSmartPlaylist())},
function(){return!1},!1);domCache.searchstr.addEventListener("keyup",function(a){if("Escape"==a.key)this.blur();else if("Enter"==a.key&&settings.featAdvsearch)if(""!=this.value){a=document.getElementById("searchMatch");var b=document.createElement("button");b.classList.add("btn","btn-light","mr-2");b.setAttribute("data-filter",encodeURI(app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+"'"));b.innerHTML=app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+ !1);document.getElementById("BrowseDatabaseAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowseDatabasePlist("queue"):"Add all to playlist"==a.target.innerText&&showAddToPlaylist("DATABASE"))},!1);document.getElementById("searchtags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&(app.current.filter=a.target.getAttribute("data-tag"),search(domCache.searchstr.value))},!1);document.getElementById("searchqueuestr").addEventListener("keyup",
'\'<span class="ml-2 badge badge-secondary">&times;</span>';this.value="";domCache.searchCrumb.appendChild(b)}else search(this.value);else search(this.value)},!1);domCache.searchCrumb.addEventListener("click",function(a){a.preventDefault();a.stopPropagation();if("SPAN"==a.target.nodeName)a.target.parentNode.remove(),search("");else if("BUTTON"==a.target.nodeName){var b=decodeURI(a.target.getAttribute("data-filter"));domCache.searchstr.value=b.substring(b.indexOf("'")+1,b.length-1);var c=b.substring(0, function(a){"Escape"==a.key?this.blur():appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+app.current.sort+"/"+this.value)},!1);document.getElementById("searchqueuetags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+a.target.getAttribute("data-tag")+"/"+app.current.sort+"/"+app.current.search)},!1);a="QueueCurrentColsDropdown BrowseFilesystemColsDropdown SearchColsDropdown BrowsePlaylistsDetailColsDropdown BrowseDatabaseColsDropdown PlaybackColsDropdown QueueLastPlayedColsDropdown".split(" ");
b.indexOf(" "));selectTag("searchtags","searchtagsdesc",c);b=b.substring(b.indexOf(" ")+1);b=b.substring(0,b.indexOf(" "));document.getElementById("searchMatch").value=b;a.target.remove();search(domCache.searchstr.value)}},!1);document.getElementById("searchMatch").addEventListener("change",function(a){search(domCache.searchstr.value)},!1);document.getElementById("SearchList").getElementsByTagName("tr")[0].addEventListener("click",function(a){if(settings.featAdvsearch&&"TH"==a.target.nodeName){var b= for(c=0;c<a.length;c++)document.getElementById(a[c]).addEventListener("click",function(a){"INPUT"==a.target.nodeName&&a.stopPropagation()},!1);document.getElementById("search").addEventListener("submit",function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit",function(){return!1},!1);domCache.searchstr.addEventListener("keyup",function(a){if("Escape"==a.key)this.blur();else if("Enter"==a.key&&settings.featAdvsearch)if(""!=this.value){a=document.getElementById("searchMatch");
a.target.getAttribute("data-col");if("Duration"!=b){var c=document.getElementById("SearchList").getAttribute("data-sort"),d=!0;if(c==b||c=="-"+b)0==c.indexOf("-")?(d=!0,c.substring(1)):d=!1;0==d?(c="-"+b,d=!0):(d=!1,c=b);for(var h=document.getElementById("SearchList").getElementsByClassName("sort-dir"),l=0;l<h.length;l++)h[l].remove();document.getElementById("SearchList").setAttribute("data-sort",c);a.target.innerHTML=b+'<span class="sort-dir material-icons pull-right">'+(1==d?"arrow_drop_up":"arrow_drop_down")+ var b=document.createElement("button");b.classList.add("btn","btn-light","mr-2");b.setAttribute("data-filter",encodeURI(app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+"'"));b.innerHTML=app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+'\'<span class="ml-2 badge badge-secondary">&times;</span>';this.value="";domCache.searchCrumb.appendChild(b)}else search(this.value);else search(this.value)},!1);domCache.searchCrumb.addEventListener("click",function(a){a.preventDefault();
"</span>";appRoute()}}},!1);document.getElementById("BrowseDatabaseByTagDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,a.target.getAttribute("data-tag"),"0/"+app.current.filter+"/"+app.current.search)},!1);document.getElementsByTagName("body")[0].addEventListener("click",function(a){hideMenu()},!1);dragAndDropTable("QueueCurrentList");dragAndDropTable("BrowsePlaylistsDetailList");dragAndDropTableHeader("QueueCurrent");dragAndDropTableHeader("QueueLastPlayed"); a.stopPropagation();if("SPAN"==a.target.nodeName)a.target.parentNode.remove(),search("");else if("BUTTON"==a.target.nodeName){var b=decodeURI(a.target.getAttribute("data-filter"));domCache.searchstr.value=b.substring(b.indexOf("'")+1,b.length-1);var c=b.substring(0,b.indexOf(" "));selectTag("searchtags","searchtagsdesc",c);b=b.substring(b.indexOf(" ")+1);b=b.substring(0,b.indexOf(" "));document.getElementById("searchMatch").value=b;a.target.remove();search(domCache.searchstr.value)}},!1);document.getElementById("searchMatch").addEventListener("change",
dragAndDropTableHeader("Search");dragAndDropTableHeader("BrowseFilesystem");dragAndDropTableHeader("BrowsePlaylistsDetail");window.addEventListener("hashchange",appRoute,!1);window.addEventListener("focus",function(){sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState)},!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName&&"SELECT"!=a.target.tagName&&!a.ctrlKey&&!a.altKey){var b=keymap[a.key];b&&"function"===typeof window[b.cmd]&&(void 0==keymap[a.key].req||1==settings[keymap[a.key].req])&& function(a){search(domCache.searchstr.value)},!1);document.getElementById("SearchList").getElementsByTagName("tr")[0].addEventListener("click",function(a){if(settings.featAdvsearch&&"TH"==a.target.nodeName){var b=a.target.getAttribute("data-col");if("Duration"!=b){var c=app.current.sort,d=!0;if(c==b||c=="-"+b)0==c.indexOf("-")?(d=!0,c.substring(1)):d=!1;0==d?(c="-"+b,d=!0):(d=!1,c=b);for(var h=document.getElementById("SearchList").getElementsByClassName("sort-dir"),l=0;l<h.length;l++)h[l].remove();
parseCmd(a,b)}},!1);"serviceWorker"in navigator&&"https"==document.URL.substring(0,5)&&window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.min.js",{scope:"/"}).then(function(a){console.log("ServiceWorker registration successful with scope: ",a.scope);a.update()},function(a){console.log("ServiceWorker registration failed: ",a)})});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a});window.addEventListener("beforeinstallprompt", app.current.sort=c;a.target.innerHTML=b+'<span class="sort-dir material-icons pull-right">'+(1==d?"arrow_drop_up":"arrow_drop_down")+"</span>";appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+app.current.filter+"/"+app.current.sort+"/"+app.current.search)}}},!1);document.getElementById("BrowseDatabaseByTagDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,a.target.getAttribute("data-tag"),"0/"+app.current.filter+
function(a){a.preventDefault();deferredPrompt=a;domCache.btnAdd.classList.remove("hide")});domCache.btnAdd.addEventListener("click",function(a){domCache.btnAdd.classList.add("hide");deferredPrompt.prompt();deferredPrompt.userChoice.then(function(a){"accepted"===a.outcome?console.log("User accepted the A2HS prompt"):console.log("User dismissed the A2HS prompt");deferredPrompt=null})});window.addEventListener("appinstalled",function(a){console.log("myMPD installed as app")})} "/"+app.current.sort+"/"+app.current.search)},!1);document.getElementsByTagName("body")[0].addEventListener("click",function(a){hideMenu()},!1);dragAndDropTable("QueueCurrentList");dragAndDropTable("BrowsePlaylistsDetailList");dragAndDropTableHeader("QueueCurrent");dragAndDropTableHeader("QueueLastPlayed");dragAndDropTableHeader("Search");dragAndDropTableHeader("BrowseFilesystem");dragAndDropTableHeader("BrowsePlaylistsDetail");window.addEventListener("hashchange",appRoute,!1);window.addEventListener("focus",
function(){sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState)},!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName&&"SELECT"!=a.target.tagName&&!a.ctrlKey&&!a.altKey){var b=keymap[a.key];b&&"function"===typeof window[b.cmd]&&(void 0==keymap[a.key].req||1==settings[keymap[a.key].req])&&parseCmd(a,b)}},!1);"serviceWorker"in navigator&&"https"==document.URL.substring(0,5)&&window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.min.js",{scope:"/"}).then(function(a){console.log("ServiceWorker registration successful with scope: ",
a.scope);a.update()},function(a){console.log("ServiceWorker registration failed: ",a)})});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a;domCache.btnAdd.classList.remove("hide")});domCache.btnAdd.addEventListener("click",function(a){domCache.btnAdd.classList.add("hide");deferredPrompt.prompt();deferredPrompt.userChoice.then(function(a){"accepted"===a.outcome?
console.log("User accepted the A2HS prompt"):console.log("User dismissed the A2HS prompt");deferredPrompt=null})});window.addEventListener("appinstalled",function(a){console.log("myMPD installed as app")});window.addEventListener("beforeunload",function(){socket.onclose=function(){};socket.close();websocketConnected=!1})}
function parseCmd(a,b){a.preventDefault();a=b;"string"==typeof b&&(a=JSON.parse(b));if("function"===typeof window[a.cmd])switch(a.cmd){case "sendAPI":sendAPI.apply(null,$jscomp.arrayFromIterable(a.options));break;default:window[a.cmd].apply(null,$jscomp.arrayFromIterable(a.options))}} function parseCmd(a,b){a.preventDefault();a=b;"string"==typeof b&&(a=JSON.parse(b));if("function"===typeof window[a.cmd])switch(a.cmd){case "sendAPI":sendAPI.apply(null,$jscomp.arrayFromIterable(a.options));break;default:window[a.cmd].apply(null,$jscomp.arrayFromIterable(a.options))}}
function search(a){if(settings.featAdvsearch){for(var b="(",c=domCache.searchCrumb.children,d=0;d<c.length;d++)b+="("+decodeURI(c[d].getAttribute("data-filter"))+")",""!=a&&(b+=" AND ");""!=a?(c=document.getElementById("searchMatch"),b+="("+app.current.filter+" "+c.options[c.selectedIndex].value+" '"+a+"'))"):b+=")";2>=b.length&&(b="");appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+encodeURI(b))}else appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+a)} function search(a){if(settings.featAdvsearch){for(var b="(",c=domCache.searchCrumb.children,d=0;d<c.length;d++)b+="("+decodeURI(c[d].getAttribute("data-filter"))+")",""!=a&&(b+=" AND ");""!=a?(c=document.getElementById("searchMatch"),b+="("+app.current.filter+" "+c.options[c.selectedIndex].value+" '"+a+"'))"):b+=")";2>=b.length&&(b="");appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+app.current.sort+"/"+encodeURI(b))}else appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+app.current.sort+
function dragAndDropTable(a){var b=document.getElementById(a).getElementsByTagName("tbody")[0];b.addEventListener("dragstart",function(a){"TR"==a.target.nodeName&&(a.target.classList.add("opacity05"),a.dataTransfer.setDragImage(a.target,0,0),a.dataTransfer.effectAllowed="move",a.dataTransfer.setData("Text",a.target.getAttribute("id")),dragEl=a.target.cloneNode(!0))},!1);b.addEventListener("dragleave",function(a){a.preventDefault();if("TR"==dragEl.nodeName){var b=a.target;"TD"==a.target.nodeName&& "/"+a)}
(b=a.target.parentNode);"TR"==b.nodeName&&b.classList.remove("dragover")}},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");c=a.target;"TD"==a.target.nodeName&&(c=a.target.parentNode);"TR"==c.nodeName&&c.classList.add("dragover");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c= function dragAndDropTable(a){var b=document.getElementById(a).getElementsByTagName("tbody")[0];b.addEventListener("dragstart",function(a){"TR"==a.target.nodeName&&(a.target.classList.add("opacity05"),a.dataTransfer.setDragImage(a.target,0,0),a.dataTransfer.effectAllowed="move",a.dataTransfer.setData("Text",a.target.getAttribute("id")),dragEl=a.target.cloneNode(!0))},!1);b.addEventListener("dragleave",function(a){a.preventDefault();if("TR"==dragEl.nodeName){var b=a.target;"TD"==a.target.nodeName&&(b=
a.target.parentNode);"TR"==b.nodeName&&b.classList.remove("dragover")}},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");c=a.target;"TD"==a.target.nodeName&&(c=a.target.parentNode);"TR"==c.nodeName&&c.classList.add("dragover");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=
b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");document.getElementById(a.dataTransfer.getData("Text"))&&document.getElementById(a.dataTransfer.getData("Text")).classList.remove("opacity05")}},!1);b.addEventListener("drop",function(c){c.stopPropagation();c.preventDefault();if("TR"==dragEl.nodeName){var d=c.target;"TD"==c.target.nodeName&&(d=c.target.parentNode);var e=document.getElementById(c.dataTransfer.getData("Text")).getAttribute("data-songpos"), b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");document.getElementById(a.dataTransfer.getData("Text"))&&document.getElementById(a.dataTransfer.getData("Text")).classList.remove("opacity05")}},!1);b.addEventListener("drop",function(c){c.stopPropagation();c.preventDefault();if("TR"==dragEl.nodeName){var d=c.target;"TD"==c.target.nodeName&&(d=c.target.parentNode);var e=document.getElementById(c.dataTransfer.getData("Text")).getAttribute("data-songpos"),
f=d.getAttribute("data-songpos");document.getElementById(c.dataTransfer.getData("Text")).remove();dragEl.classList.remove("opacity05");b.insertBefore(dragEl,d);c=b.getElementsByClassName("dragover");d=c.length;for(var g=0;g<d;g++)c[g].classList.remove("dragover");document.getElementById(a).classList.add("opacity05");"Queue"==app.current.app&&"Current"==app.current.tab?sendAPI({cmd:"MPD_API_QUEUE_MOVE_TRACK",data:{from:e,to:f}}):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&& f=d.getAttribute("data-songpos");document.getElementById(c.dataTransfer.getData("Text")).remove();dragEl.classList.remove("opacity05");b.insertBefore(dragEl,d);c=b.getElementsByClassName("dragover");d=c.length;for(var g=0;g<d;g++)c[g].classList.remove("dragover");document.getElementById(a).classList.add("opacity05");"Queue"==app.current.app&&"Current"==app.current.tab?sendAPI({cmd:"MPD_API_QUEUE_MOVE_TRACK",data:{from:e,to:f}}):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&&
playlistMoveTrack(e,f)}},!1)} playlistMoveTrack(e,f)}},!1)}
@ -68,39 +73,40 @@ function dragAndDropTableHeader(a){if(document.getElementById(a+"List"))var b=do
function(a){a.preventDefault();"TH"==dragEl.nodeName&&"TH"==a.target.nodeName&&a.target.classList.remove("dragover-th")},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TH"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover-th"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover-th");"TH"==a.target.nodeName&&a.target.classList.add("dragover-th");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TH"==dragEl.nodeName){for(var c= function(a){a.preventDefault();"TH"==dragEl.nodeName&&"TH"==a.target.nodeName&&a.target.classList.remove("dragover-th")},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TH"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover-th"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover-th");"TH"==a.target.nodeName&&a.target.classList.add("dragover-th");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TH"==dragEl.nodeName){for(var c=
b.getElementsByClassName("dragover-th"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover-th");this.querySelector("[data-col="+a.dataTransfer.getData("Text")+"]")&&this.querySelector("[data-col="+a.dataTransfer.getData("Text")+"]").classList.remove("opacity05")}},!1);b.addEventListener("drop",function(c){c.stopPropagation();c.preventDefault();if("TH"==dragEl.nodeName){this.querySelector("[data-col="+c.dataTransfer.getData("Text")+"]").remove();dragEl.classList.remove("opacity05");b.insertBefore(dragEl, b.getElementsByClassName("dragover-th"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover-th");this.querySelector("[data-col="+a.dataTransfer.getData("Text")+"]")&&this.querySelector("[data-col="+a.dataTransfer.getData("Text")+"]").classList.remove("opacity05")}},!1);b.addEventListener("drop",function(c){c.stopPropagation();c.preventDefault();if("TH"==dragEl.nodeName){this.querySelector("[data-col="+c.dataTransfer.getData("Text")+"]").remove();dragEl.classList.remove("opacity05");b.insertBefore(dragEl,
c.target);c=b.getElementsByClassName("dragover-th");for(var d=c.length,e=0;e<d;e++)c[e].classList.remove("dragover-th");document.getElementById(a+"List")?(document.getElementById(a+"List").classList.add("opacity05"),saveCols(a)):saveCols(a,this.parentNode.parentNode)}},!1)}function playlistMoveTrack(a,b){sendAPI({cmd:"MPD_API_PLAYLIST_MOVE_TRACK",data:{plist:app.current.search,from:a,to:b}})} c.target);c=b.getElementsByClassName("dragover-th");for(var d=c.length,e=0;e<d;e++)c[e].classList.remove("dragover-th");document.getElementById(a+"List")?(document.getElementById(a+"List").classList.add("opacity05"),saveCols(a)):saveCols(a,this.parentNode.parentNode)}},!1)}function playlistMoveTrack(a,b){sendAPI({cmd:"MPD_API_PLAYLIST_MOVE_TRACK",data:{plist:app.current.search,from:a,to:b}})}
function webSocketConnect(){var a=getWsUrl();socket=new WebSocket(a);try{socket.onopen=function(){console.log("connected");showNotification("Connected to myMPD: "+a,"","","success");modalConnectionError.hide();appRoute();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState)},socket.onmessage=function(b){if(b.data!==lastState&&0!=b.data.length){try{var c=JSON.parse(b.data)}catch(d){console.log("Invalid JSON data received: "+b.data)}switch(c.type){case "update_state":parseState(c);break;case "disconnected":showNotification("Lost connection to myMPD: "+ function webSocketConnect(){var a=getWsUrl();socket=new WebSocket(a);try{socket.onopen=function(){console.log("Websocket is connected")},socket.onmessage=function(b){try{var c=JSON.parse(b.data)}catch(d){console.log("Invalid JSON data received: "+b.data)}switch(c.type){case "welcome":websocketConnected=!0;showNotification("Connected to myMPD: "+a,"","","success");modalConnectionError.hide();appRoute();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);break;case "update_state":parseState(c);break;case "disconnected":showNotification("Lost connection to myMPD: "+
a,"","","danger");break;case "update_queue":"Queue"===app.current.app&&getQueue();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);break;case "update_options":getSettings();break;case "update_outputs":sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs);break;case "update_started":updateDBstarted(!1);break;case "update_database":case "update_finished":updateDBfinished(c.type);break;case "update_volume":parseVolume(c);break;case "update_stored_playlist":"Browse"==app.current.app&&"Playlists"== a,"","","danger");break;case "update_queue":"Queue"===app.current.app&&getQueue();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);break;case "update_options":getSettings();break;case "update_outputs":sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs);break;case "update_started":updateDBstarted(!1);break;case "update_database":case "update_finished":updateDBfinished(c.type);break;case "update_volume":parseVolume(c);break;case "update_stored_playlist":"Browse"==app.current.app&&"Playlists"==
app.current.tab&&"All"==app.current.view?sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&&sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists);break;case "error":showNotification(c.data,"","","danger")}}},socket.onclose=function(){console.log("disconnected");modalConnectionError.show(); app.current.tab&&"All"==app.current.view?sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&&sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists);break;case "error":showNotification(c.data,"","","danger")}},socket.onclose=function(){console.log("Websocket is disconnected");
setTimeout(function(){console.log("reconnect");webSocketConnect()},3E3)}}catch(b){alert("Error: "+b)}}function getWsUrl(){var a=window.location.hostname,b=window.location.protocol,c=window.location.port;a=("https:"==b?"wss://":"ws://")+a+(""!=c?":"+c:"")+"/ws";return document.getElementById("wsUrl").innerText=a} 1==appInited&&modalConnectionError.show();websocketConnected=!1;null!=websocketTimer&&clearTimeout(websocketTimer);websocketTimer=setTimeout(function(){console.log("Reconnecting websocket");webSocketConnect()},3E3)}}catch(b){alert("Error: "+b)}}function getWsUrl(){var a=window.location.hostname,b=window.location.protocol,c=window.location.port;a=("https:"==b?"wss://":"ws://")+a+(""!=c?":"+c:"")+"/ws";return document.getElementById("wsUrl").innerText=a}
function parseStats(a){document.getElementById("mpdstats_artists").innerText=a.data.artists;document.getElementById("mpdstats_albums").innerText=a.data.albums;document.getElementById("mpdstats_songs").innerText=a.data.songs;document.getElementById("mpdstats_dbPlaytime").innerText=beautifyDuration(a.data.dbPlaytime);document.getElementById("mpdstats_playtime").innerText=beautifyDuration(a.data.playtime);document.getElementById("mpdstats_uptime").innerText=beautifyDuration(a.data.uptime);var b=new Date(1E3* function parseStats(a){document.getElementById("mpdstats_artists").innerText=a.data.artists;document.getElementById("mpdstats_albums").innerText=a.data.albums;document.getElementById("mpdstats_songs").innerText=a.data.songs;document.getElementById("mpdstats_dbPlaytime").innerText=beautifyDuration(a.data.dbPlaytime);document.getElementById("mpdstats_playtime").innerText=beautifyDuration(a.data.playtime);document.getElementById("mpdstats_uptime").innerText=beautifyDuration(a.data.uptime);var b=new Date(1E3*
a.data.dbUpdated);document.getElementById("mpdstats_dbUpdated").innerText=b.toUTCString();document.getElementById("mympdVersion").innerText=a.data.mympdVersion;document.getElementById("mpdVersion").innerText=a.data.mpdVersion;document.getElementById("libmpdclientVersion").innerText=a.data.libmpdclientVersion}function toggleBtn(a,b){if(a=document.getElementById(a))void 0==b&&(b=a.classList.contains("active")?0:1),1==b||1==b?a.classList.add("active"):a.classList.remove("active")} a.data.dbUpdated);document.getElementById("mpdstats_dbUpdated").innerText=b.toUTCString();document.getElementById("mympdVersion").innerText=a.data.mympdVersion;document.getElementById("mpdVersion").innerText=a.data.mpdVersion;document.getElementById("libmpdclientVersion").innerText=a.data.libmpdclientVersion}function toggleBtn(a,b){if(a=document.getElementById(a))void 0==b&&(b=a.classList.contains("active")?0:1),1==b||1==b?a.classList.add("active"):a.classList.remove("active")}
function filterCols(a){var b=settings.tags.slice();0==settings.featTags&&b.push("Title");b.push("Duration");"colsQueueCurrent"==a||"colsBrowsePlaylistsDetail"==a||"colsQueueLastPlayed"==a?b.push("Pos"):"colsBrowseFilesystem"==a&&b.push("Type");"colsQueueLastPlayed"==a&&b.push("LastPlayed");for(var c=[],d=0;d<settings[a].length;d++)b.includes(settings[a][d])&&c.push(settings[a][d]);settings[a]=c} function filterCols(a){var b=settings.tags.slice();0==settings.featTags&&b.push("Title");b.push("Duration");"colsQueueCurrent"==a||"colsBrowsePlaylistsDetail"==a||"colsQueueLastPlayed"==a?b.push("Pos"):"colsBrowseFilesystem"==a&&b.push("Type");"colsQueueLastPlayed"==a&&b.push("LastPlayed");for(var c=[],d=0;d<settings[a].length;d++)b.includes(settings[a][d])&&c.push(settings[a][d]);settings[a]=c}
function parseSettings(a){settings=a.data;toggleBtn("btnRandom",settings.random);toggleBtn("btnConsume",settings.consume);toggleBtn("btnSingle",settings.single);toggleBtn("btnRepeat",settings.repeat);void 0!=settings.crossfade?(document.getElementById("inputCrossfade").removeAttribute("disabled"),document.getElementById("inputCrossfade").value=settings.crossfade):document.getElementById("inputCrossfade").setAttribute("disabled","disabled");void 0!=settings.mixrampdb?(document.getElementById("inputMixrampdb").removeAttribute("disabled"), function parseSettings(){toggleBtn("btnRandom",settings.random);toggleBtn("btnConsume",settings.consume);toggleBtn("btnSingle",settings.single);toggleBtn("btnRepeat",settings.repeat);toggleBtn("btnAutoPlay",settings.autoPlay);void 0!=settings.crossfade?(document.getElementById("inputCrossfade").removeAttribute("disabled"),document.getElementById("inputCrossfade").value=settings.crossfade):document.getElementById("inputCrossfade").setAttribute("disabled","disabled");void 0!=settings.mixrampdb?(document.getElementById("inputMixrampdb").removeAttribute("disabled"),
document.getElementById("inputMixrampdb").value=settings.mixrampdb):document.getElementById("inputMixrampdb").setAttribute("disabled","disabled");void 0!=settings.mixrampdelay?(document.getElementById("inputMixrampdelay").removeAttribute("disabled"),document.getElementById("inputMixrampdelay").value=settings.mixrampdelay):document.getElementById("inputMixrampdelay").setAttribute("disabled","disabled");document.getElementById("selectReplaygain").value=settings.replaygain;a=document.getElementById("btnnotifyWeb"); document.getElementById("inputMixrampdb").value=settings.mixrampdb):document.getElementById("inputMixrampdb").setAttribute("disabled","disabled");void 0!=settings.mixrampdelay?(document.getElementById("inputMixrampdelay").removeAttribute("disabled"),document.getElementById("inputMixrampdelay").value=settings.mixrampdelay):document.getElementById("inputMixrampdelay").setAttribute("disabled","disabled");document.getElementById("selectReplaygain").value=settings.replaygain;var a=document.getElementById("btnnotifyWeb");
notificationsSupported()?settings.notificationWeb?(toggleBtn("btnnotifyWeb",settings.notificationWeb),Notification.requestPermission(function(a){"permission"in Notification||(Notification.permission=a);"granted"===a?toggleBtn("btnnotifyWeb",1):(toggleBtn("btnnotifyWeb",0),settings.notificationWeb=!0)})):toggleBtn("btnnotifyWeb",0):(a.setAttribute("disabled","disabled"),toggleBtn("btnnotifyWeb",0));toggleBtn("btnnotifyPage",settings.notificationPage);var b="featStickers featSmartpls featPlaylists featTags featLocalplayer featSyscmds featCoverimage featAdvsearch".split(" "); notificationsSupported()?settings.notificationWeb?(toggleBtn("btnnotifyWeb",settings.notificationWeb),Notification.requestPermission(function(a){"permission"in Notification||(Notification.permission=a);"granted"===a?toggleBtn("btnnotifyWeb",1):(toggleBtn("btnnotifyWeb",0),settings.notificationWeb=!0)})):toggleBtn("btnnotifyWeb",0):(a.setAttribute("disabled","disabled"),toggleBtn("btnnotifyWeb",0));toggleBtn("btnnotifyPage",settings.notificationPage);var b="featStickers featSmartpls featPlaylists featTags featLocalplayer featSyscmds featCoverimage featAdvsearch".split(" ");
document.documentElement.style.setProperty("--mympd-coverimagesize",settings.coverimagesize+"px");for(var c=0;c<b.length;c++){var d=document.getElementsByClassName(b[c]),e=d.length,f=1==settings[b[c]]?"":"none";for(a=0;a<e;a++)d[a].style.display=f}if(0==settings.featTags)app.apps.Browse.active="Filesystem",app.apps.Search.state="0/filename/",app.apps.Queue.state="0/filename/",settings.colsQueueCurrent=["Pos","Title","Duration"],settings.colsQueueLastPlayed=["Pos","Title","LastPlayed"],settings.colsSearch= document.documentElement.style.setProperty("--mympd-coverimagesize",settings.coverimagesize+"px");document.documentElement.style.setProperty("--mympd-backgroundcolor",settings.backgroundcolor);for(var c=0;c<b.length;c++){var d=document.getElementsByClassName(b[c]),e=d.length,f=1==settings[b[c]]?"":"none";for(a=0;a<e;a++)d[a].style.display=f}if(0==settings.featTags)app.apps.Browse.active="Filesystem",app.apps.Search.state="0/filename/",app.apps.Queue.state="0/filename/",settings.colsQueueCurrent=["Pos",
["Title","Duration"],settings.colsBrowseFilesystem=["Type","Title","Duration"],settings.colsBrowseDatabase=["Track","Title","Duration"],settings.colsPlayback=[];else{b="";for(a=0;a<settings.colsPlayback.length;a++)b+='<div id="current'+settings.colsPlayback[a]+'" data-tag="'+settings.colsPlayback[a]+'" data-name="'+encodeURI(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+'"><small>'+settings.colsPlayback[a]+"</small><h4",settings.browsetags.includes(settings.colsPlayback[a])&&(b+= "Title","Duration"],settings.colsQueueLastPlayed=["Pos","Title","LastPlayed"],settings.colsSearch=["Title","Duration"],settings.colsBrowseFilesystem=["Type","Title","Duration"],settings.colsBrowseDatabase=["Track","Title","Duration"],settings.colsPlayback=[];else{b="";for(a=0;a<settings.colsPlayback.length;a++)b+='<div id="current'+settings.colsPlayback[a]+'" data-tag="'+settings.colsPlayback[a]+'" data-name="'+encodeURI(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+'"><small>'+
' class="clickable"'),b+=">"+(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+"</h4></div>";document.getElementById("cardPlaybackTags").innerHTML=b}1==settings.mixramp?document.getElementsByClassName("mixramp")[0].style.display="":document.getElementsByClassName("mixramp")[0].style.display="none";!settings.tags.includes("AlbumArtist")&&settings.featTags&&(settings.tags.includes("Artist")?app.apps.Browse.tabs.Database.active="Artist":app.apps.Browse.tabs.Database.active=settings.tags[0]); settings.colsPlayback[a]+"</small><h4",settings.browsetags.includes(settings.colsPlayback[a])&&(b+=' class="clickable"'),b+=">"+(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+"</h4></div>";document.getElementById("cardPlaybackTags").innerHTML=b}1==settings.mixramp?document.getElementsByClassName("mixramp")[0].style.display="":document.getElementsByClassName("mixramp")[0].style.display="none";!settings.tags.includes("AlbumArtist")&&settings.featTags&&(settings.tags.includes("Artist")?
document.getElementById("selectJukeboxMode").value=settings.jukeboxMode;document.getElementById("inputJukeboxQueueLength").value=settings.jukeboxQueueLength;0==settings.jukeboxMode||2==settings.jukeboxMode?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled","disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled","disabled")):1==settings.jukeboxMode&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled")); app.apps.Browse.tabs.Database.active="Artist":app.apps.Browse.tabs.Database.active=settings.tags[0]);settings.tags.includes("Title")&&(app.apps.Search.state="0/any/Title/");document.getElementById("selectJukeboxMode").value=settings.jukeboxMode;document.getElementById("inputJukeboxQueueLength").value=settings.jukeboxQueueLength;0==settings.jukeboxMode||2==settings.jukeboxMode?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled","disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled",
settings.featPlaylists?(playlistEl="selectJukeboxPlaylist",sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:0,filter:"-"}},getAllPlaylists)):document.getElementById("selectJukeboxPlaylist").innerHTML="<option>Database</option>";settings.tags.sort();settings.searchtags.sort();settings.browsetags.sort();filterCols("colsSearch");filterCols("colsQueueCurrent");filterCols("colsQueueLastPlayed");filterCols("colsBrowsePlaylistsDetail");filterCols("colsBrowseFilesystem");filterCols("colsBrowseDatabase"); "disabled")):1==settings.jukeboxMode&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled"));settings.featPlaylists?(playlistEl="selectJukeboxPlaylist",sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:0,filter:"-"}},getAllPlaylists)):document.getElementById("selectJukeboxPlaylist").innerHTML="<option>Database</option>";settings.tags.sort();settings.searchtags.sort();settings.browsetags.sort();filterCols("colsSearch");
filterCols("colsPlayback");settings.featLocalplayer&&(""==settings.streamurl?(settings.mpdstream="http://",settings.mpdstream="127.0.0.1"==settings.mpdhost||"localhost"==settings.mpdhost?settings.mpdstream+window.location.hostname:settings.mpdstream+settings.mpdhost,settings.mpdstream+=":"+settings.streamport+"/"):settings.mpdstream=settings.streamurl);addTagList("BrowseDatabaseByTagDropdown","browsetags");addTagList("searchqueuetags","searchtags");addTagList("searchtags","searchtags");for(a=0;a< filterCols("colsQueueCurrent");filterCols("colsQueueLastPlayed");filterCols("colsBrowsePlaylistsDetail");filterCols("colsBrowseFilesystem");filterCols("colsBrowseDatabase");filterCols("colsPlayback");settings.featLocalplayer&&(""==settings.streamurl?(settings.mpdstream="http://",settings.mpdstream="127.0.0.1"==settings.mpdhost||"localhost"==settings.mpdhost?settings.mpdstream+window.location.hostname:settings.mpdstream+settings.mpdhost,settings.mpdstream+=":"+settings.streamport+"/"):settings.mpdstream=
settings.tags.length;a++)app.apps.Browse.tabs.Database.views[settings.tags[a]]={state:"0/-/",scrollPos:0};if(settings.featSyscmds){document.getElementById("mainMenuDropdown");b="";c=settings.syscmds.length;if(0<c)for(b='<div class="dropdown-divider"></div>',a=0;a<c;a++)b+='<a class="dropdown-item text-light bg-dark" href="#" data-href=\'{"cmd": "execSyscmd", "options": ["'+settings.syscmds[a]+"\"]}'>"+settings.syscmds[a]+"</a>";document.getElementById("syscmds").innerHTML=b}else document.getElementById("syscmds").innerHTML= settings.streamurl);addTagList("BrowseDatabaseByTagDropdown","browsetags");addTagList("searchqueuetags","searchtags");addTagList("searchtags","searchtags");for(a=0;a<settings.tags.length;a++)app.apps.Browse.tabs.Database.views[settings.tags[a]]={state:"0/-/-/",scrollPos:0};if(settings.featSyscmds){document.getElementById("mainMenuDropdown");b="";c=settings.syscmdList.length;if(0<c)for(b='<div class="dropdown-divider"></div>',a=0;a<c;a++)b+='<a class="dropdown-item text-light bg-dark" href="#" data-href=\'{"cmd": "execSyscmd", "options": ["'+
"";dropdownMainMenu=new Dropdown(document.getElementById("mainMenu"));setCols("QueueCurrent");setCols("Search");setCols("QueueLastPlayed");setCols("BrowseFilesystem");setCols("BrowsePlaylistsDetail");setCols("BrowseDatabase",".tblAlbumTitles");setCols("Playback");"Queue"==app.current.app&&"Current"==app.current.tab?getQueue():"Queue"==app.current.app&&"LastPlayed"==app.current.tab?appRoute():"Search"==app.current.app?appRoute():"Browse"==app.current.app&&"Filesystem"==app.current.tab?appRoute():"Browse"== settings.syscmdList[a]+"\"]}'>"+settings.syscmdList[a]+"</a>";document.getElementById("syscmds").innerHTML=b}else document.getElementById("syscmds").innerHTML="";dropdownMainMenu=new Dropdown(document.getElementById("mainMenu"));setCols("QueueCurrent");setCols("Search");setCols("QueueLastPlayed");setCols("BrowseFilesystem");setCols("BrowsePlaylistsDetail");setCols("BrowseDatabase",".tblAlbumTitles");setCols("Playback");"Queue"==app.current.app&&"Current"==app.current.tab?getQueue():"Queue"==app.current.app&&
app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view?appRoute():"Browse"==app.current.app&&"Database"==app.current.tab&&""!=app.current.search&&appRoute()} "LastPlayed"==app.current.tab?appRoute():"Search"==app.current.app?appRoute():"Browse"==app.current.app&&"Filesystem"==app.current.tab?appRoute():"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view?appRoute():"Browse"==app.current.app&&"Database"==app.current.tab&&""!=app.current.search&&appRoute();settingsParsed=!0}
function setCols(a,b){var c="",d=settings.tags.slice();0==settings.featTags&&d.push("Title");d.push("Duration");"QueueCurrent"!=a&&"BrowsePlaylistsDetail"!=a&&"QueueLastPlayed"!=a||d.push("Pos");"BrowseFilesystem"==a&&d.push("Type");"QueueLastPlayed"==a&&d.push("LastPlayed");d.sort();for(var e=0;e<d.length;e++)if("Playback"!=a||"Title"!=d[e])c+='<div class="form-check"><input class="form-check-input" type="checkbox" value="1" name="'+d[e]+'"',settings["cols"+a].includes(d[e])&&(c+="checked"),c+='><label class="form-check-label text-light" for="'+ function setCols(a,b){var c="",d=settings.tags.slice();0==settings.featTags&&d.push("Title");d.push("Duration");"QueueCurrent"!=a&&"BrowsePlaylistsDetail"!=a&&"QueueLastPlayed"!=a||d.push("Pos");"BrowseFilesystem"==a&&d.push("Type");"QueueLastPlayed"==a&&d.push("LastPlayed");d.sort();for(var e=0;e<d.length;e++)if("Playback"!=a||"Title"!=d[e])c+='<div class="form-check"><input class="form-check-input" type="checkbox" value="1" name="'+d[e]+'"',settings["cols"+a].includes(d[e])&&(c+="checked"),c+='><label class="form-check-label text-light" for="'+
d[e]+'">&nbsp;&nbsp;'+d[e]+"</label></div>";document.getElementById(a+"ColsDropdown").firstChild.innerHTML=c;d=document.getElementById("SearchList").getAttribute("data-sort");""==d&&(d=settings.featTags?"Title":"Filename");if("Playback"!=a){c="";for(e=0;e<settings["cols"+a].length;e++){var f=settings["cols"+a][e];c+='<th draggable="true" data-col="'+f+'">';if("Track"==f||"Pos"==f)f="#";c+=f;"Search"==a&&f==d&&(f=!1,0==d.indexOf("-")&&(f=!0,d=d.substring(1)),c+='<span class="sort-dir material-icons pull-right">'+ d[e]+'">&nbsp;&nbsp;'+d[e]+"</label></div>";document.getElementById(a+"ColsDropdown").firstChild.innerHTML=c;d=app.current.sort;"Search"==a&&"0/any/Title/"==app.apps.Search.state&&(d=settings.tags.includes("Title")?"Title":0==settings.featTags?"Filename":"-");if("Playback"!=a){c="";for(e=0;e<settings["cols"+a].length;e++){var f=settings["cols"+a][e];c+='<th draggable="true" data-col="'+f+'">';if("Track"==f||"Pos"==f)f="#";c+=f;"Search"!=a||f!=d&&"-"+f!=d||(f=!1,0==app.current.sort.indexOf("-")&&(f=
(1==f?"arrow_drop_up":"arrow_drop_down")+"</span>");c+="</th>"}c+="<th></th>";if(void 0==b)document.getElementById(a+"List").getElementsByTagName("tr")[0].innerHTML=c;else for(a=document.querySelectorAll(b),e=0;e<a.length;e++)a[e].getElementsByTagName("tr")[0].innerHTML=c}}function getSettings(){sendAPI({cmd:"MPD_API_SETTINGS_GET"},parseSettings)} !0),c+='<span class="sort-dir material-icons pull-right">'+(1==f?"arrow_drop_up":"arrow_drop_down")+"</span>");c+="</th>"}c+="<th></th>";if(void 0==b)document.getElementById(a+"List").getElementsByTagName("tr")[0].innerHTML=c;else for(a=document.querySelectorAll(b),e=0;e<a.length;e++)a[e].getElementsByTagName("tr")[0].innerHTML=c}}function getSettings(){0==settingsLock&&(settingsLock=!0,sendAPI({cmd:"MYMPD_API_SETTINGS_GET"},getMpdSettings))}
function getMpdSettings(a){settingsNew=a.data;sendAPI({cmd:"MPD_API_SETTINGS_GET"},joinSettings)}function joinSettings(a){for(var b in a.data)settingsNew[b]=a.data[b];settings=Object.assign({},settingsNew);settingsLock=!1;parseSettings()}
function saveCols(a,b){var c=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input");var d=void 0==b?document.getElementById(a+"List").getElementsByTagName("tr")[0]:"string"==typeof b?document.querySelector(b).getElementsByTagName("tr")[0]:b.getElementsByTagName("tr")[0];for(b=0;b<c.length;b++){var e=d.querySelector("[data-col="+c[b].name+"]");0==c[b].checked?e&&e.remove():e||(e=document.createElement("th"),e.innerText=c[b].name,e.setAttribute("data-col",c[b].name),d.appendChild(e))}a= function saveCols(a,b){var c=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input");var d=void 0==b?document.getElementById(a+"List").getElementsByTagName("tr")[0]:"string"==typeof b?document.querySelector(b).getElementsByTagName("tr")[0]:b.getElementsByTagName("tr")[0];for(b=0;b<c.length;b++){var e=d.querySelector("[data-col="+c[b].name+"]");0==c[b].checked?e&&e.remove():e||(e=document.createElement("th"),e.innerText=c[b].name,e.setAttribute("data-col",c[b].name),d.appendChild(e))}a=
{cmd:"MPD_API_COLS_SAVE",data:{table:"cols"+a,cols:[]}};c=d.getElementsByTagName("th");for(b=0;b<c.length;b++)(d=c[b].getAttribute("data-col"))&&a.data.cols.push(d);sendAPI(a,getSettings)} {cmd:"MYMPD_API_COLS_SAVE",data:{table:"cols"+a,cols:[]}};c=d.getElementsByTagName("th");for(b=0;b<c.length;b++)(d=c[b].getAttribute("data-col"))&&a.data.cols.push(d);sendAPI(a,getSettings)}
function saveColsPlayback(a){for(var b=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input"),c=document.getElementById("cardPlaybackTags"),d=0;d<b.length;d++){var e=document.getElementById("current"+b[d].name);0==b[d].checked?e&&e.remove():e||(e=document.createElement("div"),e.innerHTML="<small>"+b[d].name+"</small><h4></h4>",e.setAttribute("id","current"+b[d].name),e.setAttribute("data-tag",b[d].name),c.appendChild(e))}a={cmd:"MPD_API_COLS_SAVE",data:{table:"cols"+a, function saveColsPlayback(a){for(var b=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input"),c=document.getElementById("cardPlaybackTags"),d=0;d<b.length;d++){var e=document.getElementById("current"+b[d].name);0==b[d].checked?e&&e.remove():e||(e=document.createElement("div"),e.innerHTML="<small>"+b[d].name+"</small><h4></h4>",e.setAttribute("id","current"+b[d].name),e.setAttribute("data-tag",b[d].name),c.appendChild(e))}a={cmd:"MYMPD_API_COLS_SAVE",data:{table:"cols"+
cols:[]}};c=c.getElementsByTagName("div");for(d=0;d<c.length;d++)(b=c[d].getAttribute("data-tag"))&&a.data.cols.push(b);sendAPI(a,getSettings)} a,cols:[]}};c=c.getElementsByTagName("div");for(d=0;d<c.length;d++)(b=c[d].getAttribute("data-tag"))&&a.data.cols.push(b);sendAPI(a,getSettings)}
function parseOutputs(a){for(var b="",c=a.data.outputs.length,d=0;d<c;d++)b+='<button id="btnOutput'+a.data.outputs[d].id+'" data-output-id="'+a.data.outputs[d].id+'" class="btn btn-secondary btn-block',1==a.data.outputs[d].state&&(b+=" active"),b+='"><span class="material-icons float-left">volume_up</span> '+a.data.outputs[d].name+"</button>";domCache.outputs.innerHTML=b} function parseOutputs(a){for(var b="",c=a.data.outputs.length,d=0;d<c;d++)b+='<button id="btnOutput'+a.data.outputs[d].id+'" data-output-id="'+a.data.outputs[d].id+'" class="btn btn-secondary btn-block',1==a.data.outputs[d].state&&(b+=" active"),b+='"><span class="material-icons float-left">volume_up</span> '+a.data.outputs[d].name+"</button>";domCache.outputs.innerHTML=b}
function setCounter(a,b,c){currentSong.totalTime=b;currentSong.elapsedTime=c;currentSong.currentSongId=a;var d=Math.floor(b/60),e=b-60*d,f=Math.floor(c/60),g=c-60*f;domCache.progressBar.value=Math.floor(1E3*c/b);b=f+":"+(10>g?"0":"")+g+" / "+d+":"+(10>e?"0":"")+e;domCache.counter.innerText=b;if(lastState&&lastState.data.currentSongId!=a&&(c=document.getElementById("queueTrackId"+lastState.data.currentSongId))){if(d=c.querySelector("[data-col=Duration]"))d.innerText=c.getAttribute("data-duration"); function setCounter(a,b,c){currentSong.totalTime=b;currentSong.elapsedTime=c;currentSong.currentSongId=a;var d=Math.floor(b/60),e=b-60*d,f=Math.floor(c/60),g=c-60*f;domCache.progressBar.value=Math.floor(1E3*c/b);b=f+":"+(10>g?"0":"")+g+" / "+d+":"+(10>e?"0":"")+e;domCache.counter.innerText=b;if(lastState&&lastState.data.currentSongId!=a&&(c=document.getElementById("queueTrackId"+lastState.data.currentSongId))){if(d=c.querySelector("[data-col=Duration]"))d.innerText=c.getAttribute("data-duration");
if(d=c.querySelector("[data-col=Pos]"))d.classList.remove("material-icons"),d.innerText=c.getAttribute("data-songpos");c.classList.remove("font-weight-bold")}if(c=document.getElementById("queueTrackId"+a)){if(d=c.querySelector("[data-col=Duration]"))d.innerText=b;(d=c.querySelector("[data-col=Pos]"))&&!d.classList.contains("material-icons")&&(d.classList.add("material-icons"),d.innerText="play_arrow");c.classList.add("font-weight-bold")}progressTimer&&clearTimeout(progressTimer);"play"==playstate&& if(d=c.querySelector("[data-col=Pos]"))d.classList.remove("material-icons"),d.innerText=c.getAttribute("data-songpos");c.classList.remove("font-weight-bold")}if(c=document.getElementById("queueTrackId"+a)){if(d=c.querySelector("[data-col=Duration]"))d.innerText=b;(d=c.querySelector("[data-col=Pos]"))&&!d.classList.contains("material-icons")&&(d.classList.add("material-icons"),d.innerText="play_arrow");c.classList.add("font-weight-bold")}progressTimer&&clearTimeout(progressTimer);"play"==playstate&&
(progressTimer=setTimeout(function(){currentSong.elapsedTime++;setCounter(currentSong.currentSongId,currentSong.totalTime,currentSong.elapsedTime)},1E3))} (progressTimer=setTimeout(function(){currentSong.elapsedTime++;setCounter(currentSong.currentSongId,currentSong.totalTime,currentSong.elapsedTime)},1E3))}
function parseState(a){if(JSON.stringify(a)!==JSON.stringify(lastState)){if(1==a.data.state){for(var b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="stop"}else if(2==a.data.state){for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="pause";playstate="play"}else{for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="pause"}-1==a.data.nextSongPos&&0==settings.jukeboxMode?domCache.btnNext.setAttribute("disabled","disabled"): function parseState(a){if(JSON.stringify(a)!==JSON.stringify(lastState)){if(1==a.data.state){for(var b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="stop"}else if(2==a.data.state){for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="pause";playstate="play"}else{for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="pause"}-1==a.data.nextSongPos&&0==settings.jukeboxMode?domCache.btnNext.setAttribute("disabled","disabled"):
domCache.btnNext.removeAttribute("disabled");0>=a.data.songPos?domCache.btnPrev.setAttribute("disabled","disabled"):domCache.btnPrev.removeAttribute("disabled");if(0==a.data.queueLength)for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].setAttribute("disabled","disabled");else for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].removeAttribute("disabled");domCache.badgeQueueItems.innerText=a.data.queueLength;parseVolume(a);setCounter(a.data.currentSongId,a.data.totalTime,a.data.elapsedTime); domCache.btnNext.removeAttribute("disabled");0>=a.data.songPos?domCache.btnPrev.setAttribute("disabled","disabled"):domCache.btnPrev.removeAttribute("disabled");if(0==a.data.queueLength)for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].setAttribute("disabled","disabled");else for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].removeAttribute("disabled");domCache.badgeQueueItems.innerText=a.data.queueLength;parseVolume(a);setCounter(a.data.currentSongId,a.data.totalTime,a.data.elapsedTime);
lastState&&lastState.data.currentSongId!=a.data.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);if("-1"==a.data.songPos){domCache.currentTitle.innerText="Not playing";domCache.currentCover.style.backgroundImage="";var c=document.getElementById("cardPlaybackTags").getElementsByTagName("h4");for(b=0;b<c.length;b++)c[b].innerText=""}"Queue"==app.current.app&&"LastPlayed"==app.current.tab&&sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed); lastState&&lastState.data.currentSongId==a.data.currentSongId||sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);if("-1"==a.data.songPos){domCache.currentTitle.innerText="Not playing";domCache.currentCover.style.backgroundImage="";var c=document.getElementById("cardPlaybackTags").getElementsByTagName("h4");for(b=0;b<c.length;b++)c[b].innerText=""}"Queue"==app.current.app&&"LastPlayed"==app.current.tab&&sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);
lastState=a}}function parseVolume(a){-1==a.data.volume?(domCache.volumePrct.innerText="Volumecontrol disabled",domCache.volumeControl.classList.add("hide")):(domCache.volumeControl.classList.remove("hide"),domCache.volumePrct.innerText=a.data.volume+" %",domCache.volumeMenu.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume} lastState=a}}function parseVolume(a){-1==a.data.volume?(domCache.volumePrct.innerText="Volumecontrol disabled",domCache.volumeControl.classList.add("hide")):(domCache.volumeControl.classList.remove("hide"),domCache.volumePrct.innerText=a.data.volume+" %",domCache.volumeMenu.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume}
function getQueue(){2<=app.current.search.length?sendAPI({cmd:"MPD_API_QUEUE_SEARCH",data:{filter:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseQueue):sendAPI({cmd:"MPD_API_QUEUE_LIST",data:{offset:app.current.page}},parseQueue)} function getQueue(){2<=app.current.search.length?sendAPI({cmd:"MPD_API_QUEUE_SEARCH",data:{filter:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseQueue):sendAPI({cmd:"MPD_API_QUEUE_LIST",data:{offset:app.current.page}},parseQueue)}
function parseQueue(a){0<a.totalTime&&a.totalEntities<=settings.maxElementsPerPage?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song")+" \u2013 "+beautifyDuration(a.totalTime):0<a.totalEntities?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song"):document.getElementById("cardFooterQueue").innerText="";var b=a.data.length,c=document.getElementById("QueueCurrentList");c.setAttribute("data-version", function parseQueue(a){0<a.totalTime&&a.totalEntities<=settings.maxElementsPerPage?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song")+" \u2013 "+beautifyDuration(a.totalTime):0<a.totalEntities?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song"):document.getElementById("cardFooterQueue").innerText="";var b=a.data.length,c=document.getElementById("QueueCurrentList");c.setAttribute("data-version",
@ -140,11 +146,11 @@ function setPagination(a,b){var c=app.current.app+(void 0==app.current.tab?"":ap
-1==a&&b>=settings.maxElementsPerPage?(document.getElementById(c+e[f]+"Next").removeAttribute("disabled"),document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):(document.getElementById(c+e[f]+"Next").setAttribute("disabled","disabled"),document.getElementById(c+e[f]).classList.add("hide"),document.getElementById(c+"ButtonsBottom").classList.add("hide"));0<app.current.page?(document.getElementById(c+e[f]+"Prev").removeAttribute("disabled"), -1==a&&b>=settings.maxElementsPerPage?(document.getElementById(c+e[f]+"Next").removeAttribute("disabled"),document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):(document.getElementById(c+e[f]+"Next").setAttribute("disabled","disabled"),document.getElementById(c+e[f]).classList.add("hide"),document.getElementById(c+"ButtonsBottom").classList.add("hide"));0<app.current.page?(document.getElementById(c+e[f]+"Prev").removeAttribute("disabled"),
document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):document.getElementById(c+e[f]+"Prev").setAttribute("disabled","disabled")}}function appendQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:b}});showNotification('"'+c+'" added',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_ADD_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" added',"","","success")}} document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):document.getElementById(c+e[f]+"Prev").setAttribute("disabled","disabled")}}function appendQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:b}});showNotification('"'+c+'" added',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_ADD_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" added',"","","success")}}
function appendAfterQueue(a,b,c,d){switch(a){case "song":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK_AFTER",data:{uri:b,to:c}}),showNotification('"'+d+'" added to pos '+c,"","","success")}}function replaceQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_TRACK",data:{uri:b}});showNotification('"'+c+'" replaced',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" replaced',"","","success")}} function appendAfterQueue(a,b,c,d){switch(a){case "song":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK_AFTER",data:{uri:b,to:c}}),showNotification('"'+d+'" added to pos '+c,"","","success")}}function replaceQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_TRACK",data:{uri:b}});showNotification('"'+c+'" replaced',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" replaced',"","","success")}}
function clickTitle(){var a=decodeURI(domCache.currentTitle.getAttribute("data-uri"));""!=a&&songDetails(a)}function gotoBrowse(a){var b=a.parentNode.getAttribute("data-tag");a=decodeURI(a.parentNode.getAttribute("data-name"));""!=b&&""!=a&&"-"!=a&&settings.browsetags.includes(b)&&appGoto("Browse","Database",b,"0/-/"+a)}function songDetails(a){sendAPI({cmd:"MPD_API_DATABASE_SONGDETAILS",data:{uri:a}},parseSongDetails);modalSongDetails.show()} function clickTitle(){var a=decodeURI(domCache.currentTitle.getAttribute("data-uri"));""!=a&&songDetails(a)}function gotoBrowse(a){var b=a.parentNode.getAttribute("data-tag");a=decodeURI(a.parentNode.getAttribute("data-name"));""!=b&&""!=a&&"-"!=a&&settings.browsetags.includes(b)&&appGoto("Browse","Database",b,"0/-/-/"+a)}function songDetails(a){sendAPI({cmd:"MPD_API_DATABASE_SONGDETAILS",data:{uri:a}},parseSongDetails);modalSongDetails.show()}
function parseSongDetails(a){var b=document.getElementById("modalSongDetails");b.getElementsByClassName("album-cover")[0].style.backgroundImage='url("'+a.data.cover+'")';b.getElementsByTagName("h1")[0].innerText=a.data.Title;for(var c="",d=0;d<settings.tags.length;d++)c+="<tr><th>"+settings.tags[d]+'</th><td data-tag="'+settings.tags[d]+'" data-name="'+encodeURI(a.data[settings.tags[d]])+'">',c=settings.browsetags.includes(settings.tags[d])?c+('<a class="text-success" href="#">'+a.data[settings.tags[d]]+ function parseSongDetails(a){var b=document.getElementById("modalSongDetails");b.getElementsByClassName("album-cover")[0].style.backgroundImage='url("'+a.data.cover+'")';b.getElementsByTagName("h1")[0].innerText=a.data.Title;for(var c="",d=0;d<settings.tags.length;d++)c+="<tr><th>"+settings.tags[d]+'</th><td data-tag="'+settings.tags[d]+'" data-name="'+encodeURI(a.data[settings.tags[d]])+'">',c=settings.browsetags.includes(settings.tags[d])?c+('<a class="text-success" href="#">'+a.data[settings.tags[d]]+
"</a>"):c+a.data[settings.tags[d]],c+="</td></tr>";var e=a.data.Duration;d=Math.floor(e/60);e-=60*d;c+="<tr><th>Duration</th><td>"+(d+":"+(10>e?"0":"")+e)+"</td></tr>";c=settings.featLibrary?c+('<tr><th>Filename</th><td><a class="text-success" href="/library/'+encodeURI(a.data.uri)+'">'+a.data.uri+"</a></td></tr>"):c+("<tr><th>Filename</th><td>"+a.data.uri+"</td></tr>");1==settings.featStickers&&(c+='<tr><th colspan="2">Statistics</th></tr><tr><th>Play count</th><td>'+a.data.playCount+"</td></tr><tr><th>Skip count</th><td>"+ "</a>"):c+a.data[settings.tags[d]],c+="</td></tr>";var e=a.data.Duration;d=Math.floor(e/60);e-=60*d;c+="<tr><th>Duration</th><td>"+(d+":"+(10>e?"0":"")+e)+"</td></tr>";c=settings.featLibrary?c+('<tr><th>Filename</th><td><a class="text-success" href="/library/'+encodeURI(a.data.uri)+'">'+a.data.uri+"</a></td></tr>"):c+("<tr><th>Filename</th><td>"+a.data.uri+"</td></tr>");1==settings.featStickers&&(c+='<tr><th colspan="2">Statistics</th></tr><tr><th>Play count</th><td>'+a.data.playCount+"</td></tr><tr><th>Skip count</th><td>"+
a.data.skipCount+"</td></tr><tr><th>Last played</th><td>"+(0==a.data.lastPlayed?"never":(new Date(1E3*a.data.lastPlayed)).toUTCString())+'</td></tr><tr><th>Like</th><td><div class="btn-group btn-group-sm"><button title="Dislike song" id="btnVoteDown2" data-href=\'{"cmd": "voteSong", "options": [0]}\' class="btn btn-sm btn-light material-icons">thumb_down</button><button title="Like song" id="btnVoteUp2" data-href=\'{"cmd": "voteSong", "options": [2]}\' class="btn btn-sm btn-light material-icons">thumb_up</button></div></td></tr>'); a.data.skipCount+"</td></tr><tr><th>Last played</th><td>"+(0==a.data.lastPlayed?"never":(new Date(1E3*a.data.lastPlayed)).toUTCString())+'</td></tr><tr><th>Like</th><td><div class="btn-group btn-group-sm"><button title="Dislike song" id="btnVoteDown2" data-href=\'{"cmd": "voteSong", "options": [0]}\' class="btn btn-sm btn-light material-icons">thumb_down</button><button title="Like song" id="btnVoteUp2" data-href=\'{"cmd": "voteSong", "options": [2]}\' class="btn btn-sm btn-light material-icons">thumb_up</button></div></td></tr>');
b.getElementsByTagName("tbody")[0].innerHTML=c;setVoteSongBtns(a.data.like,a.data.uri)}function execSyscmd(a){sendAPI({cmd:"MPD_API_SYSCMD",data:{cmd:a}})}function playlistDetails(a){document.getElementById("BrowsePlaylistsAllList").classList.add("opacity05");appGoto("Browse","Playlists","Detail","0/-/"+a)}function removeFromPlaylist(a,b){b--;sendAPI({cmd:"MPD_API_PLAYLIST_RM_TRACK",data:{uri:a,track:b}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")} b.getElementsByTagName("tbody")[0].innerHTML=c;setVoteSongBtns(a.data.like,a.data.uri)}function execSyscmd(a){sendAPI({cmd:"MYMPD_API_SYSCMD",data:{cmd:a}})}function playlistDetails(a){document.getElementById("BrowsePlaylistsAllList").classList.add("opacity05");appGoto("Browse","Playlists","Detail","0/-/-/"+a)}function removeFromPlaylist(a,b){b--;sendAPI({cmd:"MPD_API_PLAYLIST_RM_TRACK",data:{uri:a,track:b}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")}
function playlistClear(){var a=document.getElementById("BrowsePlaylistsDetailList").getAttribute("data-uri");sendAPI({cmd:"MPD_API_PLAYLIST_CLEAR_AND_LIST",data:{uri:a}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")} function playlistClear(){var a=document.getElementById("BrowsePlaylistsDetailList").getAttribute("data-uri");sendAPI({cmd:"MPD_API_PLAYLIST_CLEAR_AND_LIST",data:{uri:a}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")}
function getAllPlaylists(a){var b=a.data.length,c="";0==a.offset&&("addToPlaylistPlaylist"==playlistEl?c="<option></option><option>New Playlist</option>":"selectJukeboxPlaylist"==playlistEl&&(c="<option>Database</option>"));for(var d=0;d<b;d++)c+="<option","selectJukeboxPlaylist"==playlistEl&&a.data[d].uri==settings.jukeboxPlaylist&&(c+=" selected"),c+=">"+a.data[d].uri+"</option>";0==a.offset?document.getElementById(playlistEl).innerHTML=c:document.getElementById(playlistEl).innerHTML+=c;a.totalEntities> function getAllPlaylists(a){var b=a.data.length,c="";0==a.offset&&("addToPlaylistPlaylist"==playlistEl?c="<option></option><option>New Playlist</option>":"selectJukeboxPlaylist"==playlistEl&&(c="<option>Database</option>"));for(var d=0;d<b;d++)c+="<option","selectJukeboxPlaylist"==playlistEl&&a.data[d].uri==settings.jukeboxPlaylist&&(c+=" selected"),c+=">"+a.data[d].uri+"</option>";0==a.offset?document.getElementById(playlistEl).innerHTML=c:document.getElementById(playlistEl).innerHTML+=c;a.totalEntities>
a.returnedEntities&&(a.offset+=settings.maxElementsPerPage,sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:a.offset,filter:"-"}},getAllPlaylists))}function updateSmartPlaylists(){sendAPI({cmd:"MPD_API_SMARTPLS_UPDATE_ALL"})} a.returnedEntities&&(a.offset+=settings.maxElementsPerPage,sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:a.offset,filter:"-"}},getAllPlaylists))}function updateSmartPlaylists(){sendAPI({cmd:"MPD_API_SMARTPLS_UPDATE_ALL"})}
@ -189,13 +195,13 @@ function updateDBfinished(a){document.getElementById("modalUpdateDB").classList.
"update_database"==a?showNotification("Database successfully updated.","","","success"):"update_finished"==a&&showNotification("Database update finished.","","","success")}function clickPlay(){"play"!=playstate?sendAPI({cmd:"MPD_API_PLAYER_PLAY"}):sendAPI({cmd:"MPD_API_PLAYER_PAUSE"})}function clickStop(){sendAPI({cmd:"MPD_API_PLAYER_STOP"})}function clickPrev(){sendAPI({cmd:"MPD_API_PLAYER_PREV"})}function clickNext(){sendAPI({cmd:"MPD_API_PLAYER_NEXT"})} "update_database"==a?showNotification("Database successfully updated.","","","success"):"update_finished"==a&&showNotification("Database update finished.","","","success")}function clickPlay(){"play"!=playstate?sendAPI({cmd:"MPD_API_PLAYER_PLAY"}):sendAPI({cmd:"MPD_API_PLAYER_PAUSE"})}function clickStop(){sendAPI({cmd:"MPD_API_PLAYER_STOP"})}function clickPrev(){sendAPI({cmd:"MPD_API_PLAYER_PREV"})}function clickNext(){sendAPI({cmd:"MPD_API_PLAYER_NEXT"})}
function delQueueSong(a,b,c){"range"==a?sendAPI({cmd:"MPD_API_QUEUE_RM_RANGE",data:{start:b,end:c}}):"single"==a&&sendAPI({cmd:"MPD_API_QUEUE_RM_TRACK",data:{track:b}})}function showDelPlaylist(a){document.getElementById("deletePlaylist").value=a;modalDeletePlaylist.show()}function delPlaylist(){var a=document.getElementById("deletePlaylist").value;sendAPI({cmd:"MPD_API_PLAYLIST_RM",data:{uri:a}});modalDeletePlaylist.hide()} function delQueueSong(a,b,c){"range"==a?sendAPI({cmd:"MPD_API_QUEUE_RM_RANGE",data:{start:b,end:c}}):"single"==a&&sendAPI({cmd:"MPD_API_QUEUE_RM_TRACK",data:{track:b}})}function showDelPlaylist(a){document.getElementById("deletePlaylist").value=a;modalDeletePlaylist.show()}function delPlaylist(){var a=document.getElementById("deletePlaylist").value;sendAPI({cmd:"MPD_API_PLAYLIST_RM",data:{uri:a}});modalDeletePlaylist.hide()}
function confirmSettings(){var a=!0,b=document.getElementById("inputCrossfade");if(!b.getAttribute("disabled")){var c=parseInt(b.value);isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c}b=document.getElementById("inputJukeboxQueueLength");c=parseInt(b.value);isNaN(c)?(b.classList.add("is-invalid"),a=!1):0<c?b.value=c:(b.classList.add("is-invalid"),a=!1);settings.mixramp&&(b=document.getElementById("inputMixrampdb"),b.getAttribute("disabled")||(c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"), function confirmSettings(){var a=!0,b=document.getElementById("inputCrossfade");if(!b.getAttribute("disabled")){var c=parseInt(b.value);isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c}b=document.getElementById("inputJukeboxQueueLength");c=parseInt(b.value);isNaN(c)?(b.classList.add("is-invalid"),a=!1):0<c?b.value=c:(b.classList.add("is-invalid"),a=!1);settings.mixramp&&(b=document.getElementById("inputMixrampdb"),b.getAttribute("disabled")||(c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"),
a=!1):b.value=c),b=document.getElementById("inputMixrampdelay"),b.getAttribute("disabled")||("nan"==b.value&&(b.value="-1"),c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c));1==a?(a=document.getElementById("selectReplaygain"),c=document.getElementById("selectJukeboxPlaylist"),b=document.getElementById("selectJukeboxMode"),sendAPI({cmd:"MPD_API_SETTINGS_SET",data:{consume:document.getElementById("btnConsume").classList.contains("active")?1:0,random:document.getElementById("btnRandom").classList.contains("active")? a=!1):b.value=c),b=document.getElementById("inputMixrampdelay"),b.getAttribute("disabled")||("nan"==b.value&&(b.value="-1"),c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c));1==a?(a=document.getElementById("selectReplaygain"),c=document.getElementById("selectJukeboxPlaylist"),b=document.getElementById("selectJukeboxMode"),sendAPI({cmd:"MYMPD_API_SETTINGS_SET",data:{consume:document.getElementById("btnConsume").classList.contains("active")?1:0,random:document.getElementById("btnRandom").classList.contains("active")?
1:0,single:document.getElementById("btnSingle").classList.contains("active")?1:0,repeat:document.getElementById("btnRepeat").classList.contains("active")?1:0,replaygain:a.options[a.selectedIndex].value,crossfade:document.getElementById("inputCrossfade").value,mixrampdb:1==settings.mixramp?document.getElementById("inputMixrampdb").value:settings.mixrampdb,mixrampdelay:1==settings.mixramp?document.getElementById("inputMixrampdelay").value:settings.mixrampdelay,notificationWeb:document.getElementById("btnnotifyWeb").classList.contains("active")? 1:0,single:document.getElementById("btnSingle").classList.contains("active")?1:0,repeat:document.getElementById("btnRepeat").classList.contains("active")?1:0,replaygain:a.options[a.selectedIndex].value,crossfade:document.getElementById("inputCrossfade").value,mixrampdb:1==settings.mixramp?document.getElementById("inputMixrampdb").value:settings.mixrampdb,mixrampdelay:1==settings.mixramp?document.getElementById("inputMixrampdelay").value:settings.mixrampdelay,notificationWeb:document.getElementById("btnnotifyWeb").classList.contains("active")?
!0:!1,notificationPage:document.getElementById("btnnotifyPage").classList.contains("active")?!0:!1,jukeboxMode:b.options[b.selectedIndex].value,jukeboxPlaylist:c.options[c.selectedIndex].value,jukeboxQueueLength:document.getElementById("inputJukeboxQueueLength").value}},getSettings),modalSettings.hide()):document.getElementById("settingsFrm").classList.add("was-validated")} !0:!1,notificationPage:document.getElementById("btnnotifyPage").classList.contains("active")?!0:!1,jukeboxMode:b.options[b.selectedIndex].value,jukeboxPlaylist:c.options[c.selectedIndex].value,jukeboxQueueLength:document.getElementById("inputJukeboxQueueLength").value,autoPlay:document.getElementById("btnAutoPlay").classList.contains("active")?!0:!1}},getSettings),modalSettings.hide()):document.getElementById("settingsFrm").classList.add("was-validated")}
function addAllFromBrowseFilesystem(){sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")} function addAllFromBrowseFilesystem(){sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")}
function addAllFromSearchPlist(a){var b=parseInt(document.getElementById("panel-heading-search").innerText);!isNaN(b)&&0<b&&(settings.featAdvsearch?sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:a,sort:"",sortdesc:!1,expression:app.current.search,offset:0}}):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.filter,searchstr:app.current.search,offset:0}}),showNotification("Added "+b+" songs from search to "+a,"","","success"))} function addAllFromSearchPlist(a){var b=parseInt(document.getElementById("panel-heading-search").innerText);!isNaN(b)&&0<b&&(settings.featAdvsearch?sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:a,sort:"",sortdesc:!1,expression:app.current.search,offset:0}}):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.filter,searchstr:app.current.search,offset:0}}),showNotification("Added "+b+" songs from search to "+a,"","","success"))}
function addAllFromBrowseDatabasePlist(a){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.view,searchstr:app.current.search,offset:0}}),showNotification("Added songs from database selection to "+a,"","","success"))}function scrollTo(a){document.body.scrollTop=a;document.documentElement.scrollTop=a} function addAllFromBrowseDatabasePlist(a){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.view,searchstr:app.current.search,offset:0}}),showNotification("Added songs from database selection to "+a,"","","success"))}function scrollTo(a){document.body.scrollTop=a;document.documentElement.scrollTop=a}
function gotoPage(a){switch(a){case "next":app.current.page+=settings.maxElementsPerPage;break;case "prev":app.current.page-=settings.maxElementsPerPage;0>app.current.page&&(app.current.page=0);break;default:app.current.page=a}appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+app.current.filter+"/"+app.current.search)} function gotoPage(a){switch(a){case "next":app.current.page+=settings.maxElementsPerPage;break;case "prev":app.current.page-=settings.maxElementsPerPage;0>app.current.page&&(app.current.page=0);break;default:app.current.page=a}appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+app.current.filter+"/"+app.current.sort+"/"+app.current.search)}
function saveQueue(){var a=document.getElementById("saveQueueName").value,b=a.replace(/[\w\-]/g,"");""!=a&&""==b?(sendAPI({cmd:"MPD_API_QUEUE_SAVE",data:{plist:a}}),modalSavequeue.hide()):(alert(b),document.getElementById("saveQueueName").classList.add("is-invalid"),document.getElementById("saveQueueFrm").classList.add("was-validated"))} function saveQueue(){var a=document.getElementById("saveQueueName").value,b=a.replace(/[\w\-]/g,"");""!=a&&""==b?(sendAPI({cmd:"MPD_API_QUEUE_SAVE",data:{plist:a}}),modalSavequeue.hide()):(alert(b),document.getElementById("saveQueueName").classList.add("is-invalid"),document.getElementById("saveQueueFrm").classList.add("was-validated"))}
function showNotification(a,b,c,d){1==settings.notificationWeb&&(b=new Notification(a,{icon:"assets/favicon.ico",body:b}),setTimeout(function(a){a.close()},3E3,b));1==settings.notificationPage&&(document.getElementById("alertBox")?b=document.getElementById("alertBox"):(b=document.createElement("div"),b.setAttribute("id","alertBox"),b.addEventListener("click",function(){hideNotification()},!1)),b.classList.remove("alert-success","alert-danger"),b.classList.add("alert","alert-"+d),b.innerHTML="<div><strong>"+ function showNotification(a,b,c,d){1==settings.notificationWeb&&(b=new Notification(a,{icon:"assets/favicon.ico",body:b}),setTimeout(function(a){a.close()},3E3,b));1==settings.notificationPage&&(document.getElementById("alertBox")?b=document.getElementById("alertBox"):(b=document.createElement("div"),b.setAttribute("id","alertBox"),b.addEventListener("click",function(){hideNotification()},!1)),b.classList.remove("alert-success","alert-danger"),b.classList.add("alert","alert-"+d),b.innerHTML="<div><strong>"+
a+"</strong><br/>"+c+"</div>",document.getElementsByTagName("main")[0].append(b),document.getElementById("alertBox").classList.add("alertBoxActive"),alertTimeout&&clearTimeout(alertTimeout),alertTimeout=setTimeout(function(){hideNotification()},3E3))}function hideNotification(){document.getElementById("alertBox")&&(document.getElementById("alertBox").classList.remove("alertBoxActive"),setTimeout(function(){var a=document.getElementById("alertBox");a&&a.remove()},600))} a+"</strong><br/>"+c+"</div>",document.getElementsByTagName("main")[0].append(b),document.getElementById("alertBox").classList.add("alertBoxActive"),alertTimeout&&clearTimeout(alertTimeout),alertTimeout=setTimeout(function(){hideNotification()},3E3))}function hideNotification(){document.getElementById("alertBox")&&(document.getElementById("alertBox").classList.remove("alertBoxActive"),setTimeout(function(){var a=document.getElementById("alertBox");a&&a.remove()},600))}
@ -205,7 +211,7 @@ function songChange(a){if("error"!=a.type&&"result"!=a.type){var b=a.data.Title+
a.data[settings.colsPlayback[e]];f.setAttribute("data-name",encodeURI(a.data[settings.colsPlayback[e]]))}if(e=document.getElementById("queueTrackId"+a.data.currentSongId))e.getElementsByTagName("td")[1].innerText=a.data.Title;"play"==playstate&&showNotification(a.data.Title,c,d,"success");lastSong=b;lastSongObj=a}}} a.data[settings.colsPlayback[e]];f.setAttribute("data-name",encodeURI(a.data[settings.colsPlayback[e]]))}if(e=document.getElementById("queueTrackId"+a.data.currentSongId))e.getElementsByTagName("td")[1].innerText=a.data.Title;"play"==playstate&&showNotification(a.data.Title,c,d,"success");lastSong=b;lastSongObj=a}}}
function doSetFilterLetter(a){var b=document.getElementById(a+"Letters").getElementsByClassName("active")[0];b&&b.classList.remove("active");b=app.current.filter;"0"==b&&(b="#");document.getElementById(a).innerText="Filter"+("-"!=b?": "+b:"");if("-"!=b){a=document.getElementById(a+"Letters").getElementsByTagName("button");for(var c=a.length,d=0;d<c;d++)if(a[d].innerText==b){a[d].classList.add("active");break}}} function doSetFilterLetter(a){var b=document.getElementById(a+"Letters").getElementsByClassName("active")[0];b&&b.classList.remove("active");b=app.current.filter;"0"==b&&(b="#");document.getElementById(a).innerText="Filter"+("-"!=b?": "+b:"");if("-"!=b){a=document.getElementById(a+"Letters").getElementsByTagName("button");for(var c=a.length,d=0;d<c;d++)if(a[d].innerText==b){a[d].classList.add("active");break}}}
function addFilterLetter(a){for(var b='<button class="mr-1 mb-1 btn btn-sm btn-secondary material-icons material-icons-small">delete</button><button class="mr-1 mb-1 btn btn-sm btn-secondary">#</button>',c=65;90>=c;c++)b+='<button class="mr-1 mb-1 btn-sm btn btn-secondary">'+String.fromCharCode(c)+"</button>";a=document.getElementById(a);a.innerHTML=b;a.addEventListener("click",function(a){switch(a.target.innerText){case "delete":b="-";break;case "#":b="0";break;default:b=a.target.innerText}appGoto(app.current.app, function addFilterLetter(a){for(var b='<button class="mr-1 mb-1 btn btn-sm btn-secondary material-icons material-icons-small">delete</button><button class="mr-1 mb-1 btn btn-sm btn-secondary">#</button>',c=65;90>=c;c++)b+='<button class="mr-1 mb-1 btn-sm btn btn-secondary">'+String.fromCharCode(c)+"</button>";a=document.getElementById(a);a.innerHTML=b;a.addEventListener("click",function(a){switch(a.target.innerText){case "delete":b="-";break;case "#":b="0";break;default:b=a.target.innerText}appGoto(app.current.app,
app.current.tab,app.current.view,"0/"+b+"/"+app.current.search)},!1)}function selectTag(a,b,c){a=document.getElementById(a);var d=a.querySelector(".active");d&&d.classList.remove("active");if(d=a.querySelector("[data-tag="+c+"]"))d.classList.add("active"),document.getElementById(b).innerText=d.innerText} app.current.tab,app.current.view,"0/"+b+"/"+app.current.sort+"/"+app.current.search)},!1)}function selectTag(a,b,c){a=document.getElementById(a);var d=a.querySelector(".active");d&&d.classList.remove("active");if(d=a.querySelector("[data-tag="+c+"]"))d.classList.add("active"),document.getElementById(b).innerText=d.innerText}
function addTagList(a,b){var c="";"searchtags"==b&&(1==settings.featTags&&(c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="any">Any Tag</button>'),c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="filename">Filename</button>');for(var d=0;d<settings[b].length;d++)c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="'+settings[b][d]+'">'+settings[b][d]+"</button>";document.getElementById(a).innerHTML=c} function addTagList(a,b){var c="";"searchtags"==b&&(1==settings.featTags&&(c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="any">Any Tag</button>'),c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="filename">Filename</button>');for(var d=0;d<settings[b].length;d++)c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="'+settings[b][d]+'">'+settings[b][d]+"</button>";document.getElementById(a).innerHTML=c}
function gotoTagList(){appGoto(app.current.app,app.current.tab,app.current.view,"0/-/")}function openModal(a){window[a].show()}function openDropdown(a){window[a].toggle()}function focusSearch(){"Queue"==app.current.app?document.getElementById("searchqueuestr").focus():"Search"==app.current.app?domCache.searchstr.focus():appGoto("Search")} function gotoTagList(){appGoto(app.current.app,app.current.tab,app.current.view,"0/-/-/")}function openModal(a){window[a].show()}function openDropdown(a){window[a].toggle()}function focusSearch(){"Queue"==app.current.app?document.getElementById("searchqueuestr").focus():"Search"==app.current.app?domCache.searchstr.focus():appGoto("Search")}
function chVolume(a){a=parseInt(domCache.volumeBar.value)+a;0>a?a=0:100<a&&(a=100);domCache.volumeBar.value=a;sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",data:{volume:a}})}function beautifyDuration(a){var b=Math.floor(a/86400),c=Math.floor(a/3600)-24*b,d=Math.floor(a/60)-60*c-1440*b;a=a-86400*b-3600*c-60*d;return(0<b?b+"\u2009d ":"")+(0<c?c+"\u2009h "+(10>d?"0":""):"")+d+"\u2009m "+(10>a?"0":"")+a+"\u2009s"}function genId(a){return"id"+a.replace(/[^\w\-]/g,"")}appInit(); function chVolume(a){a=parseInt(domCache.volumeBar.value)+a;0>a?a=0:100<a&&(a=100);domCache.volumeBar.value=a;sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",data:{volume:a}})}function beautifyDuration(a){var b=Math.floor(a/86400),c=Math.floor(a/3600)-24*b,d=Math.floor(a/60)-60*c-1440*b;a=a-86400*b-3600*c-60*d;return(0<b?b+"\u2009d ":"")+(0<c?c+"\u2009h "+(10>d?"0":""):"")+d+"\u2009m "+(10>a?"0":"")+a+"\u2009s"}function genId(a){return"id"+a.replace(/[^\w\-]/g,"")}appInitStart();

View File

@ -10,5 +10,5 @@ function(){function a(a){return function(d){c||(c=!0,a.call(b,d))}}var b=this,c=
void 0;try{d=a.then}catch(h){this.reject_(h);return}"function"==typeof d?this.settleSameAsThenable_(d,a):this.fulfill_(a)};c.prototype.reject_=function(a){this.settle_(2,a)};c.prototype.fulfill_=function(a){this.settle_(1,a)};c.prototype.settle_=function(a,b){if(0!=this.state_)throw Error("Cannot settle("+a+", "+b+"): Promise already settled in state"+this.state_);this.state_=a;this.result_=b;this.executeOnSettledCallbacks_()};c.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var a= void 0;try{d=a.then}catch(h){this.reject_(h);return}"function"==typeof d?this.settleSameAsThenable_(d,a):this.fulfill_(a)};c.prototype.reject_=function(a){this.settle_(2,a)};c.prototype.fulfill_=function(a){this.settle_(1,a)};c.prototype.settle_=function(a,b){if(0!=this.state_)throw Error("Cannot settle("+a+", "+b+"): Promise already settled in state"+this.state_);this.state_=a;this.result_=b;this.executeOnSettledCallbacks_()};c.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var a=
0;a<this.onSettledCallbacks_.length;++a)l.asyncExecute(this.onSettledCallbacks_[a]);this.onSettledCallbacks_=null}};var l=new b;c.prototype.settleSameAsPromise_=function(a){var b=this.createResolveAndReject_();a.callWhenSettled_(b.resolve,b.reject)};c.prototype.settleSameAsThenable_=function(a,b){var c=this.createResolveAndReject_();try{a.call(b,c.resolve,c.reject)}catch(k){c.reject(k)}};c.prototype.then=function(a,b){function d(a,b){return"function"==typeof a?function(b){try{f(a(b))}catch(m){e(m)}}: 0;a<this.onSettledCallbacks_.length;++a)l.asyncExecute(this.onSettledCallbacks_[a]);this.onSettledCallbacks_=null}};var l=new b;c.prototype.settleSameAsPromise_=function(a){var b=this.createResolveAndReject_();a.callWhenSettled_(b.resolve,b.reject)};c.prototype.settleSameAsThenable_=function(a,b){var c=this.createResolveAndReject_();try{a.call(b,c.resolve,c.reject)}catch(k){c.reject(k)}};c.prototype.then=function(a,b){function d(a,b){return"function"==typeof a?function(b){try{f(a(b))}catch(m){e(m)}}:
b}var f,e,g=new c(function(a,b){f=a;e=b});this.callWhenSettled_(d(a,f),d(b,e));return g};c.prototype.catch=function(a){return this.then(void 0,a)};c.prototype.callWhenSettled_=function(a,b){function c(){switch(d.state_){case 1:a(d.result_);break;case 2:b(d.result_);break;default:throw Error("Unexpected state: "+d.state_);}}var d=this;null==this.onSettledCallbacks_?l.asyncExecute(c):this.onSettledCallbacks_.push(c)};c.resolve=f;c.reject=function(a){return new c(function(b,c){c(a)})};c.race=function(a){return new c(function(b, b}var f,e,g=new c(function(a,b){f=a;e=b});this.callWhenSettled_(d(a,f),d(b,e));return g};c.prototype.catch=function(a){return this.then(void 0,a)};c.prototype.callWhenSettled_=function(a,b){function c(){switch(d.state_){case 1:a(d.result_);break;case 2:b(d.result_);break;default:throw Error("Unexpected state: "+d.state_);}}var d=this;null==this.onSettledCallbacks_?l.asyncExecute(c):this.onSettledCallbacks_.push(c)};c.resolve=f;c.reject=function(a){return new c(function(b,c){c(a)})};c.race=function(a){return new c(function(b,
c){for(var d=$jscomp.makeIterator(a),e=d.next();!e.done;e=d.next())f(e.value).callWhenSettled_(b,c)})};c.all=function(a){var b=$jscomp.makeIterator(a),d=b.next();return d.done?f([]):new c(function(a,c){function e(b){return function(c){g[b]=c;h--;0==h&&a(g)}}var g=[],h=0;do g.push(void 0),h++,f(d.value).callWhenSettled_(e(g.length-1),c),d=b.next();while(!d.done)})};return c},"es6","es3");var CACHE="myMPD-cache-v4.7.2",urlsToCache="/ /player.html /css/bootstrap.min.css /css/mympd.min.css /js/bootstrap-native-v4.min.js /js/mympd.min.js /js/player.min.js /js/keymap.min.js /assets/appicon-167.png /assets/appicon-192.png /assets/appicon-512.png /assets/coverimage-httpstream.png /assets/coverimage-notavailable.png /assets/coverimage-loading.png /assets/favicon.ico /assets/MaterialIcons-Regular.ttf /assets/MaterialIcons-Regular.woff /assets/MaterialIcons-Regular.woff2".split(" "); c){for(var d=$jscomp.makeIterator(a),e=d.next();!e.done;e=d.next())f(e.value).callWhenSettled_(b,c)})};c.all=function(a){var b=$jscomp.makeIterator(a),d=b.next();return d.done?f([]):new c(function(a,c){function e(b){return function(c){g[b]=c;h--;0==h&&a(g)}}var g=[],h=0;do g.push(void 0),h++,f(d.value).callWhenSettled_(e(g.length-1),c),d=b.next();while(!d.done)})};return c},"es6","es3");var CACHE="myMPD-cache-v5.0.0",urlsToCache="/ /player.html /css/bootstrap.min.css /css/mympd.min.css /js/bootstrap-native-v4.min.js /js/mympd.min.js /js/player.min.js /js/keymap.min.js /assets/appicon-167.png /assets/appicon-192.png /assets/appicon-512.png /assets/coverimage-httpstream.png /assets/coverimage-notavailable.png /assets/coverimage-loading.png /assets/favicon.ico /assets/MaterialIcons-Regular.ttf /assets/MaterialIcons-Regular.woff /assets/MaterialIcons-Regular.woff2".split(" ");
self.addEventListener("install",function(a){a.waitUntil(caches.open(CACHE).then(function(a){return a.addAll(urlsToCache)}))});self.addEventListener("fetch",function(a){if(a.request.url.match("^http://"))return!1;a.respondWith(caches.match(a.request).then(function(b){return b?b:fetch(a.request)}))});self.addEventListener("activate",function(a){a.waitUntil(caches.keys().then(function(a){return Promise.all(a.map(function(a){if(a!=CACHE)return caches.delete(a)}))}))}); self.addEventListener("install",function(a){a.waitUntil(caches.open(CACHE).then(function(a){return a.addAll(urlsToCache)}))});self.addEventListener("fetch",function(a){if(a.request.url.match("^http://"))return!1;a.respondWith(caches.match(a.request).then(function(b){return b?b:fetch(a.request)}))});self.addEventListener("activate",function(a){a.waitUntil(caches.keys().then(function(a){return Promise.all(a.map(function(a){if(a!=CACHE)return caches.delete(a)}))}))});

View File

@ -4589,7 +4589,7 @@ struct mg_ssl_if_ctx {
size_t identity_len; size_t identity_len;
}; };
void mg_ssl_if_init() { void mg_ssl_if_init(void) {
SSL_library_init(); SSL_library_init();
} }

View File

@ -428,7 +428,7 @@ unsigned int sleep(unsigned int seconds);
*/ */
#if !(defined(__cplusplus) && __cplusplus >= 201103L) && \ #if !(defined(__cplusplus) && __cplusplus >= 201103L) && \
!(defined(__DARWIN_C_LEVEL) && __DARWIN_C_LEVEL >= 200809L) !(defined(__DARWIN_C_LEVEL) && __DARWIN_C_LEVEL >= 200809L)
long long strtoll(const char *, char **, int); //long long strtoll(const char *, char **, int);
#endif #endif
typedef int sock_t; typedef int sock_t;
@ -3747,7 +3747,7 @@ extern "C" {
struct mg_ssl_if_ctx; struct mg_ssl_if_ctx;
struct mg_connection; struct mg_connection;
void mg_ssl_if_init(); void mg_ssl_if_init(void);
enum mg_ssl_if_result { enum mg_ssl_if_result {
MG_SSL_OK = 0, MG_SSL_OK = 0,

View File

@ -1,5 +1,6 @@
:root { :root {
--mympd-coverimagesize: 240px; --mympd-coverimagesize: 240px;
--mympd-bacgkroundcolor: #888;
} }
html { html {
@ -8,13 +9,13 @@ html {
} }
body { body {
padding-top: 50px; padding-top: 4rem;
padding-bottom: 50px; padding-bottom: 4rem;
background-color: #888; background-color: var(--mympd-backgroundcolor, #888);
} }
main { main {
padding-top: 10px; padding-top: 1rem;
} }
footer { footer {
@ -51,7 +52,7 @@ button {
} }
[data-col=Pos], [data-col=Type], [data-col=Track], [data-col=Action] { [data-col=Pos], [data-col=Type], [data-col=Track], [data-col=Action] {
width: 30px; width: 2rem;
} }
small { small {
@ -72,9 +73,9 @@ small {
border: 1px solid black; border: 1px solid black;
border-radius: 5px; border-radius: 5px;
overflow: hidden; overflow: hidden;
width: var(--mympd-coverimagesize); width: var(--mympd-coverimagesize, 250px);
max-width:100%; max-width:100%;
height: var(--mympd-coverimagesize); height: var(--mympd-coverimagesize, 250px);
background-color: #eee; background-color: #eee;
float: left; float: left;
margin-right: 1.25rem; margin-right: 1.25rem;
@ -91,6 +92,10 @@ small {
display: none !important; display: none !important;
} }
.unvisible {
visibility: hidden;
}
.pull-right { .pull-right {
float: right !important; float: right !important;
} }
@ -116,7 +121,7 @@ small {
font-family: 'Material Icons'; font-family: 'Material Icons';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-size: 18px; /* Preferred icon size */ font-size: 1.4rem; /* Preferred icon size */
display:inline-block; display:inline-block;
line-height: 1; line-height: 1;
text-transform: none; text-transform: none;
@ -142,7 +147,7 @@ small {
} }
.material-icons-small { .material-icons-small {
font-size: 16px; font-size: 1rem;
} }
.material-icons-small-left { .material-icons-small-left {
@ -167,7 +172,7 @@ small {
} }
.progressBarPlay { .progressBarPlay {
font-size: 22px; font-size: 1.8rem;
} }
#counter { #counter {
@ -230,8 +235,8 @@ button.active-fg-red {
div#alertBox { div#alertBox {
position:fixed; position:fixed;
top: 50px; top: 4rem;
right:10px; right:1rem;
width:80%; width:80%;
max-width:400px; max-width:400px;
z-index:1000; z-index:1000;
@ -327,7 +332,7 @@ div.key {
background-color: #eee; background-color: #eee;
border-radius: 2px; border-radius: 2px;
width: 20px; width: 20px;
heigth: 20px; height: 20px;
text-align: center; text-align: center;
} }

View File

@ -16,7 +16,7 @@
<link rel="apple-touch-icon" href="/assets/appicon-167.png"> <link rel="apple-touch-icon" href="/assets/appicon-167.png">
</head> </head>
<body> <body>
<header> <header class="hide">
<nav class="navbar navbar-expand navbar-dark fixed-top bg-dark"> <nav class="navbar navbar-expand navbar-dark fixed-top bg-dark">
<div class="dropdown col-auto mr-auto pl-0"> <div class="dropdown col-auto mr-auto pl-0">
<a class="dropdown-toggle navbar-brand" href="#" id="mainMenu"> <a class="dropdown-toggle navbar-brand" href="#" id="mainMenu">
@ -74,7 +74,7 @@
</div> </div>
</nav> </nav>
</header> </header>
<main class="container"> <main class="container hide">
<noscript> <noscript>
<div class="alert alert-danger">JavaScript is disabled!</div> <div class="alert alert-danger">JavaScript is disabled!</div>
</noscript> </noscript>
@ -567,10 +567,10 @@
</div> </div>
</div> </div>
</div> </div>
<ol class="FeatAdvsearch breadcrumb" id="searchCrumb"></ol> <ol class="featAdvsearch breadcrumb" id="searchCrumb"></ol>
<div class="table-responsive-md"> <div class="table-responsive-md">
<table id="SearchList" class="table table-hover table-sm" data-sort=""> <table id="SearchList" class="table table-hover table-sm">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
@ -607,7 +607,7 @@
</div> </div>
</main> </main>
<footer class="footer"> <footer class="footer hide">
<nav class="navbar navbar-expand navbar-dark fixed-bottom bg-dark"> <nav class="navbar navbar-expand navbar-dark fixed-bottom bg-dark">
<div class="d-flex flex-fill navbar-nav" id="navbar-bottom"> <div class="d-flex flex-fill navbar-nav" id="navbar-bottom">
<div id="navPlayback" class="nav-item flex-fill text-center"><a data-href='{"cmd": "appGoto", "options": ["Playback"]}' class="nav-link" href="#">Playback</a></div> <div id="navPlayback" class="nav-item flex-fill text-center"><a data-href='{"cmd": "appGoto", "options": ["Playback"]}' class="nav-link" href="#">Playback</a></div>
@ -619,6 +619,23 @@
</footer> </footer>
<!-- Modals --> <!-- Modals -->
<div class="modal" id="modalAppInit" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-success text-light">
<h5 class="modal-title"><span class="material-icons title-icon">play_circle_outline</span> Initializing myMPD</h5>
</div>
<div class="modal-body">
<p>Please wait, myMPD is initializing...</p>
<p><span id="appInitSettings" class="material-icons unvisible">check</span> Fetch settings</p>
<p><span id="appInitWebsocket" class="material-icons unvisible">check</span> Connect to websocket</p>
<p><span id="appInitApply" class="material-icons unvisible">check</span> Applying settings</p>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalConnectionError" tabindex="-1"> <div class="modal fade" id="modalConnectionError" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -886,6 +903,13 @@
<div class="invalid-feedback">Must be a number.</div> <div class="invalid-feedback">Must be a number.</div>
</div> </div>
</div> </div>
<div class="row">
<div class="form-group col-md-6" data-toggle="buttons">
<button data-href='{"cmd": "toggleBtn", "options": ["btnAutoPlay"]}' id="btnAutoPlay" type="button" class="btn btn-secondary btn-block" title="Start playing after first song is added to the queue">
AutoPlay
</button>
</div>
</div>
<hr/> <hr/>
<h4>Jukebox</h4> <h4>Jukebox</h4>
<div class="row"> <div class="row">

View File

@ -40,7 +40,6 @@ var time_end = 0;
var time_all = 0; var time_all = 0;
var cmds = [ var cmds = [
'{"cmd":"MPD_API_WELCOME"}',
'{"cmd":"MPD_API_QUEUE_CLEAR"}', '{"cmd":"MPD_API_QUEUE_CLEAR"}',
'{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":""}}', '{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":""}}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}', '{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}',
@ -64,10 +63,10 @@ var cmds = [
'{"cmd":"MPD_API_PLAYLIST_LIST","data":{"offset":0,"filter":""}}', '{"cmd":"MPD_API_PLAYLIST_LIST","data":{"offset":0,"filter":""}}',
'{"cmd":"MPD_API_PLAYLIST_CLEAR","data":{"uri":"test2"}}', '{"cmd":"MPD_API_PLAYLIST_CLEAR","data":{"uri":"test2"}}',
'{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":"test2"}}', '{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":"test2"}}',
'{"cmd":"MPD_API_QUEUE_CROP"}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}', '{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI2__"}}', '{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI2__"}}',
'{"cmd":"MPD_API_PLAYER_PLAY"}', '{"cmd":"MPD_API_PLAYER_PLAY"}',
'{"cmd":"MPD_API_QUEUE_CROP"}',
'{"cmd":"MPD_API_QUEUE_LAST_PLAYED","data":{"offset":0}}', '{"cmd":"MPD_API_QUEUE_LAST_PLAYED","data":{"offset":0}}',
'{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}', '{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}',
'{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}', '{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}',
@ -93,11 +92,12 @@ var cmds = [
'{"cmd":"MPD_API_PLAYER_OUTPUT_LIST"}', '{"cmd":"MPD_API_PLAYER_OUTPUT_LIST"}',
'{"cmd":"MPD_API_PLAYER_TOGGLE_OUTPUT","data":{"output":"__OUTPUTID__","state":1}}', '{"cmd":"MPD_API_PLAYER_TOGGLE_OUTPUT","data":{"output":"__OUTPUTID__","state":1}}',
'{"cmd":"MPD_API_PLAYER_STATE"}', '{"cmd":"MPD_API_PLAYER_STATE"}',
'{"cmd":"MYMPD_API_SETTINGS_GET"}',
'{"cmd":"MPD_API_SETTINGS_GET"}', '{"cmd":"MPD_API_SETTINGS_GET"}',
'{"cmd":"MPD_API_SETTINGS_SET","data":{"random": 0}}', '{"cmd":"MYMPD_API_SETTINGS_SET","data":{"random": 0}}',
'{"cmd":"MPD_API_LIKE","data":{"uri":"__URI2__","like":2}}', '{"cmd":"MPD_API_LIKE","data":{"uri":"__URI2__","like":2}}',
'{"cmd":"MPD_API_SYSCMD","data":{"cmd": "Echo"}}', '{"cmd":"MYMPD_API_SYSCMD","data":{"cmd": "Echo"}}',
'{"cmd":"MPD_API_COLS_SAVE","data":{"table":"colsPlayback","cols":["Artist","Album","AlbumArtist"]}}', '{"cmd":"MYMPD_API_COLS_SAVE","data":{"table":"colsPlayback","cols":["Artist","Album","AlbumArtist"]}}',
'{"cmd":"MPD_API_SMARTPLS_UPDATE_ALL"}', '{"cmd":"MPD_API_SMARTPLS_UPDATE_ALL"}',
'{"cmd":"MPD_API_SMARTPLS_SAVE","data":{"type":"newest","playlist":"myMPDsmart-newestSongs","timerange":2678400}}', '{"cmd":"MPD_API_SMARTPLS_SAVE","data":{"type":"newest","playlist":"myMPDsmart-newestSongs","timerange":2678400}}',
'{"cmd":"MPD_API_SMARTPLS_GET","data":{"playlist":"myMPDsmart-newestSongs"}}' '{"cmd":"MPD_API_SMARTPLS_GET","data":{"playlist":"myMPDsmart-newestSongs"}}'

View File

@ -29,28 +29,34 @@ var lastSongObj = {};
var lastState; var lastState;
var currentSong = new Object(); var currentSong = new Object();
var playstate = ''; var playstate = '';
var settingsLock = false;
var settingsParsed = false;
var settingsNew = {};
var settings = {}; var settings = {};
var alertTimeout; var alertTimeout;
var progressTimer; var progressTimer;
var deferredPrompt; var deferredPrompt;
var dragEl; var dragEl;
var playlistEl; var playlistEl;
var websocketConnected = false;
var websocketTimer = null;
var appInited = false;
var app = {}; var app = {};
app.apps = { "Playback": { "state": "0/-/", "scrollPos": 0 }, app.apps = { "Playback": { "state": "0/-/-/", "scrollPos": 0 },
"Queue": { "Queue": {
"active": "Current", "active": "Current",
"tabs": { "Current": { "state": "0/any/", "scrollPos": 0 }, "tabs": { "Current": { "state": "0/any/-/", "scrollPos": 0 },
"LastPlayed": { "state": "0/any/", "scrollPos": 0 } "LastPlayed": { "state": "0/any/-/", "scrollPos": 0 }
} }
}, },
"Browse": { "Browse": {
"active": "Database", "active": "Database",
"tabs": { "Filesystem": { "state": "0/-/", "scrollPos": 0 }, "tabs": { "Filesystem": { "state": "0/-/-/", "scrollPos": 0 },
"Playlists": { "Playlists": {
"active": "All", "active": "All",
"views": { "All": { "state": "0/-/", "scrollPos": 0 }, "views": { "All": { "state": "0/-/-/", "scrollPos": 0 },
"Detail": { "state": "0/-/", "scrollPos": 0 } "Detail": { "state": "0/-/-/", "scrollPos": 0 }
} }
}, },
"Database": { "Database": {
@ -60,11 +66,11 @@ app.apps = { "Playback": { "state": "0/-/", "scrollPos": 0 },
} }
} }
}, },
"Search": { "state": "0/any/", "scrollPos": 0 } "Search": { "state": "0/any/-/", "scrollPos": 0 }
}; };
app.current = { "app": "Playback", "tab": undefined, "view": undefined, "page": 0, "filter": "", "search": "", "scrollPos": 0 }; app.current = { "app": "Playback", "tab": undefined, "view": undefined, "page": 0, "filter": "", "search": "", "sort": "", "scrollPos": 0 };
app.last = { "app": undefined, "tab": undefined, "view": undefined, "filter": "", "search": "", "scrollPos": 0 }; app.last = { "app": undefined, "tab": undefined, "view": undefined, "filter": "", "search": "", "sort": "", "scrollPos": 0 };
var domCache = {}; var domCache = {};
domCache.navbarBottomBtns = document.getElementById('navbar-bottom').getElementsByTagName('div'); domCache.navbarBottomBtns = document.getElementById('navbar-bottom').getElementsByTagName('div');
@ -104,6 +110,7 @@ var modalUpdateDB = new Modal(document.getElementById('modalUpdateDB'));
var modalSaveSmartPlaylist = new Modal(document.getElementById('modalSaveSmartPlaylist')); var modalSaveSmartPlaylist = new Modal(document.getElementById('modalSaveSmartPlaylist'));
var modalDeletePlaylist = new Modal(document.getElementById('modalDeletePlaylist')); var modalDeletePlaylist = new Modal(document.getElementById('modalDeletePlaylist'));
var modalHelp = new Modal(document.getElementById('modalHelp')); var modalHelp = new Modal(document.getElementById('modalHelp'));
var modalAppInit = new Modal(document.getElementById('modalAppInit'), { backdrop: 'static', keyboard: false});
var dropdownMainMenu; var dropdownMainMenu;
var dropdownVolumeMenu = new Dropdown(document.getElementById('volumeMenu')); var dropdownVolumeMenu = new Dropdown(document.getElementById('volumeMenu'));
@ -178,9 +185,13 @@ function appGoto(a,t,v,s) {
} }
function appRoute() { function appRoute() {
if (settingsParsed == false) {
appInitStart();
return;
}
var hash = decodeURI(location.hash); var hash = decodeURI(location.hash);
var params; var params;
if (params = hash.match(/^\#\/(\w+)\/?(\w+)?\/?(\w+)?\!((\d+)\/([^\/]+)\/(.*))$/)) { if (params = hash.match(/^\#\/(\w+)\/?(\w+)?\/?(\w+)?\!((\d+)\/([^\/]+)\/([^\/]+)\/(.*))$/)) {
app.current.app = params[1]; app.current.app = params[1];
app.current.tab = params[2]; app.current.tab = params[2];
app.current.view = params[3]; app.current.view = params[3];
@ -201,7 +212,8 @@ function appRoute() {
} }
app.current.page = parseInt(params[5]); app.current.page = parseInt(params[5]);
app.current.filter = params[6]; app.current.filter = params[6];
app.current.search = params[7]; app.current.sort = params[7];
app.current.search = params[8];
} }
else { else {
appGoto('Playback'); appGoto('Playback');
@ -270,7 +282,7 @@ function appRoute() {
var breadcrumbItemsLen = breadcrumbItems.length; var breadcrumbItemsLen = breadcrumbItems.length;
for (var i = 0; i < breadcrumbItemsLen; i++) { for (var i = 0; i < breadcrumbItemsLen; i++) {
breadcrumbItems[i].addEventListener('click', function() { breadcrumbItems[i].addEventListener('click', function() {
appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter + '/' + this.getAttribute('data-uri')); appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + this.getAttribute('data-uri'));
}, false); }, false);
} }
doSetFilterLetter('BrowseFilesystemFilter'); doSetFilterLetter('BrowseFilesystemFilter');
@ -313,13 +325,13 @@ function appRoute() {
if (domCache.searchstr.value.length >= 2 || domCache.searchCrumb.children.length > 0) { if (domCache.searchstr.value.length >= 2 || domCache.searchCrumb.children.length > 0) {
if (settings.featAdvsearch) { if (settings.featAdvsearch) {
var sort = document.getElementById('SearchList').getAttribute('data-sort'); var sort = app.current.sort;//document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = false; var sortdesc = false;
if (sort == '') { if (sort == '-') {
if (settings.tags.includes('Title')) if (settings.tags.includes('Title'))
sort = 'Title'; sort = 'Title';
else else
sort = ''; sort = '-';
document.getElementById('SearchList').setAttribute('data-sort', sort); document.getElementById('SearchList').setAttribute('data-sort', sort);
} }
else { else {
@ -353,10 +365,45 @@ function appRoute() {
app.last.view = app.current.view; app.last.view = app.current.view;
}; };
function appInit() { function appInitStart() {
webSocketConnect(); appInited = false;
document.getElementsByTagName('header')[0].classList.add('hide');
document.getElementsByTagName('main')[0].classList.add('hide');
document.getElementsByTagName('footer')[0].classList.add('hide');
document.getElementById('appInitSettings').classList.add('unvisible');
document.getElementById('appInitWebsocket').classList.add('unvisible');
document.getElementById('appInitApply').classList.add('unvisible');
modalAppInit.show();
getSettings(); getSettings();
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState); appInitWait();
}
function appInitWait() {
setTimeout(function() {
if (settingsParsed == true && websocketConnected == true) {
//app initialized
document.getElementById('appInitWebsocket').classList.remove('unvisible');
appInit();
document.getElementById('appInitApply').classList.remove('unvisible');
document.getElementsByTagName('header')[0].classList.remove('hide');
document.getElementsByTagName('main')[0].classList.remove('hide');
document.getElementsByTagName('footer')[0].classList.remove('hide');
modalAppInit.hide();
appInited = true;
return;
}
if (settingsParsed == true) {
//parsed settings, now its save to connect to websocket
document.getElementById('appInitSettings').classList.remove('unvisible');
webSocketConnect();
}
appInitWait();
}, 500);
}
function appInit() {
// sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
domCache.volumeBar.value = 0; domCache.volumeBar.value = 0;
@ -530,7 +577,7 @@ function appInit() {
if (event.target.nodeName == 'TD') { if (event.target.nodeName == 'TD') {
switch(event.target.parentNode.getAttribute('data-type')) { switch(event.target.parentNode.getAttribute('data-type')) {
case 'dir': case 'dir':
appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter +'/' + decodeURI(event.target.parentNode.getAttribute("data-uri"))); appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + decodeURI(event.target.parentNode.getAttribute("data-uri")));
break; break;
case 'song': case 'song':
appendQueue('song', decodeURI(event.target.parentNode.getAttribute("data-uri")), event.target.parentNode.getAttribute("data-name")); appendQueue('song', decodeURI(event.target.parentNode.getAttribute("data-uri")), event.target.parentNode.getAttribute("data-name"));
@ -565,7 +612,7 @@ function appInit() {
document.getElementById('BrowseDatabaseTagList').addEventListener('click', function(event) { document.getElementById('BrowseDatabaseTagList').addEventListener('click', function(event) {
if (event.target.nodeName == 'TD') { if (event.target.nodeName == 'TD') {
appGoto('Browse', 'Database', app.current.view, '0/-/' + event.target.parentNode.getAttribute('data-uri')); appGoto('Browse', 'Database', app.current.view, '0/-/-/' + event.target.parentNode.getAttribute('data-uri'));
} }
}, false); }, false);
@ -625,12 +672,12 @@ function appInit() {
if (event.key == 'Escape') if (event.key == 'Escape')
this.blur(); this.blur();
else else
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + app.current.filter + '/' + this.value); appGoto(app.current.app, app.current.tab, app.current.view, '0/' + app.current.filter + '/' + app.current.sort + '/' + this.value);
}, false); }, false);
document.getElementById('searchqueuetags').addEventListener('click', function(event) { document.getElementById('searchqueuetags').addEventListener('click', function(event) {
if (event.target.nodeName == 'BUTTON') if (event.target.nodeName == 'BUTTON')
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + event.target.getAttribute('data-tag') + '/' + app.current.search); appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + event.target.getAttribute('data-tag') + '/' + app.current.sort + '/' + app.current.search);
}, false); }, false);
var dropdowns = ['QueueCurrentColsDropdown', 'BrowseFilesystemColsDropdown', 'SearchColsDropdown', 'BrowsePlaylistsDetailColsDropdown', var dropdowns = ['QueueCurrentColsDropdown', 'BrowseFilesystemColsDropdown', 'SearchColsDropdown', 'BrowsePlaylistsDetailColsDropdown',
@ -700,7 +747,7 @@ function appInit() {
var col = event.target.getAttribute('data-col'); var col = event.target.getAttribute('data-col');
if (col == 'Duration') if (col == 'Duration')
return; return;
var sortcol = document.getElementById('SearchList').getAttribute('data-sort'); var sortcol = app.current.sort; //document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = true; var sortdesc = true;
if (sortcol == col || sortcol == '-' + col) { if (sortcol == col || sortcol == '-' + col) {
@ -723,16 +770,18 @@ function appInit() {
var s = document.getElementById('SearchList').getElementsByClassName('sort-dir'); var s = document.getElementById('SearchList').getElementsByClassName('sort-dir');
for (var i = 0; i < s.length; i++) for (var i = 0; i < s.length; i++)
s[i].remove(); s[i].remove();
document.getElementById('SearchList').setAttribute('data-sort', sortcol); //document.getElementById('SearchList').setAttribute('data-sort', sortcol);
app.current.sort = sortcol;
event.target.innerHTML = col + '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>'; event.target.innerHTML = col + '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
appRoute(); appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.sort + '/' + app.current.search);
//appRoute();
} }
} }
}, false); }, false);
document.getElementById('BrowseDatabaseByTagDropdown').addEventListener('click', function(event) { document.getElementById('BrowseDatabaseByTagDropdown').addEventListener('click', function(event) {
if (event.target.nodeName == 'BUTTON') if (event.target.nodeName == 'BUTTON')
appGoto(app.current.app, app.current.tab, event.target.getAttribute('data-tag') , '0/' + app.current.filter + '/' + app.current.search); appGoto(app.current.app, app.current.tab, event.target.getAttribute('data-tag') , '0/' + app.current.filter + '/' + app.current.sort + '/' + app.current.search);
}, false); }, false);
document.getElementsByTagName('body')[0].addEventListener('click', function(event) { document.getElementsByTagName('body')[0].addEventListener('click', function(event) {
@ -811,6 +860,12 @@ function appInit() {
window.addEventListener('appinstalled', function(event) { window.addEventListener('appinstalled', function(event) {
console.log('myMPD installed as app'); console.log('myMPD installed as app');
}); });
window.addEventListener('beforeunload', function() {
socket.onclose = function () {}; // disable onclose handler first
socket.close();
websocketConnected = false;
});
} }
function parseCmd(event, href) { function parseCmd(event, href) {
@ -846,10 +901,10 @@ function search(x) {
expression += ')'; expression += ')';
if (expression.length <= 2) if (expression.length <= 2)
expression = ''; expression = '';
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + encodeURI(expression)); appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + encodeURI(expression));
} }
else else
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + x); appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + x);
} }
function dragAndDropTable(table) { function dragAndDropTable(table) {
@ -1010,17 +1065,10 @@ function webSocketConnect() {
try { try {
socket.onopen = function() { socket.onopen = function() {
console.log('connected'); console.log('Websocket is connected');
showNotification('Connected to myMPD: ' + wsUrl, '', '', 'success');
modalConnectionError.hide();
appRoute();
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
} }
socket.onmessage = function got_packet(msg) { socket.onmessage = function got_packet(msg) {
if (msg.data === lastState || msg.data.length == 0)
return;
try { try {
var obj = JSON.parse(msg.data); var obj = JSON.parse(msg.data);
} catch(e) { } catch(e) {
@ -1028,6 +1076,13 @@ function webSocketConnect() {
} }
switch (obj.type) { switch (obj.type) {
case 'welcome':
websocketConnected = true;
showNotification('Connected to myMPD: ' + wsUrl, '', '', 'success');
modalConnectionError.hide();
appRoute();
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
break;
case 'update_state': case 'update_state':
parseState(obj); parseState(obj);
break; break;
@ -1070,10 +1125,17 @@ function webSocketConnect() {
} }
socket.onclose = function(){ socket.onclose = function(){
console.log('disconnected'); console.log('Websocket is disconnected');
if (appInited == true) {
//Show modal only if websocket was already connected before
modalConnectionError.show(); modalConnectionError.show();
setTimeout(function() { }
console.log('reconnect'); websocketConnected = false;
if (websocketTimer != null) {
clearTimeout(websocketTimer);
}
websocketTimer = setTimeout(function() {
console.log('Reconnecting websocket');
webSocketConnect(); webSocketConnect();
}, 3000); }, 3000);
} }
@ -1145,13 +1207,12 @@ function filterCols(x) {
settings[x] = cols; settings[x] = cols;
} }
function parseSettings(obj) { function parseSettings() {
settings = obj.data;
toggleBtn('btnRandom', settings.random); toggleBtn('btnRandom', settings.random);
toggleBtn('btnConsume', settings.consume); toggleBtn('btnConsume', settings.consume);
toggleBtn('btnSingle', settings.single); toggleBtn('btnSingle', settings.single);
toggleBtn('btnRepeat', settings.repeat); toggleBtn('btnRepeat', settings.repeat);
toggleBtn('btnAutoPlay', settings.autoPlay);
if (settings.crossfade != undefined) { if (settings.crossfade != undefined) {
document.getElementById('inputCrossfade').removeAttribute('disabled'); document.getElementById('inputCrossfade').removeAttribute('disabled');
@ -1202,6 +1263,7 @@ function parseSettings(obj) {
var features = ["featStickers", "featSmartpls", "featPlaylists", "featTags", "featLocalplayer", "featSyscmds", "featCoverimage", "featAdvsearch"]; var features = ["featStickers", "featSmartpls", "featPlaylists", "featTags", "featLocalplayer", "featSyscmds", "featCoverimage", "featAdvsearch"];
document.documentElement.style.setProperty('--mympd-coverimagesize', settings.coverimagesize + "px"); document.documentElement.style.setProperty('--mympd-coverimagesize', settings.coverimagesize + "px");
document.documentElement.style.setProperty('--mympd-backgroundcolor', settings.backgroundcolor);
for (var j = 0; j < features.length; j++) { for (var j = 0; j < features.length; j++) {
var Els = document.getElementsByClassName(features[j]); var Els = document.getElementsByClassName(features[j]);
@ -1213,8 +1275,8 @@ function parseSettings(obj) {
if (settings.featTags == false) { if (settings.featTags == false) {
app.apps.Browse.active = 'Filesystem'; app.apps.Browse.active = 'Filesystem';
app.apps.Search.state = '0/filename/'; app.apps.Search.state = '0/filename/-/';
app.apps.Queue.state = '0/filename/'; app.apps.Queue.state = '0/filename/-/';
settings.colsQueueCurrent = ["Pos", "Title", "Duration"]; settings.colsQueueCurrent = ["Pos", "Title", "Duration"];
settings.colsQueueLastPlayed = ["Pos", "Title", "LastPlayed"]; settings.colsQueueLastPlayed = ["Pos", "Title", "LastPlayed"];
settings.colsSearch = ["Title", "Duration"]; settings.colsSearch = ["Title", "Duration"];
@ -1247,6 +1309,9 @@ function parseSettings(obj) {
else else
app.apps.Browse.tabs.Database.active = settings.tags[0]; app.apps.Browse.tabs.Database.active = settings.tags[0];
} }
if (settings.tags.includes('Title')) {
app.apps.Search.state = '0/any/Title/';
}
document.getElementById('selectJukeboxMode').value = settings.jukeboxMode; document.getElementById('selectJukeboxMode').value = settings.jukeboxMode;
document.getElementById('inputJukeboxQueueLength').value = settings.jukeboxQueueLength; document.getElementById('inputJukeboxQueueLength').value = settings.jukeboxQueueLength;
@ -1295,17 +1360,17 @@ function parseSettings(obj) {
addTagList('searchtags', 'searchtags'); addTagList('searchtags', 'searchtags');
for (var i = 0; i < settings.tags.length; i++) for (var i = 0; i < settings.tags.length; i++)
app.apps.Browse.tabs.Database.views[settings.tags[i]] = { "state": "0/-/", "scrollPos": 0 }; app.apps.Browse.tabs.Database.views[settings.tags[i]] = { "state": "0/-/-/", "scrollPos": 0 };
if (settings.featSyscmds) { if (settings.featSyscmds) {
var mainMenuDropdown = document.getElementById('mainMenuDropdown'); var mainMenuDropdown = document.getElementById('mainMenuDropdown');
var syscmdsList = ''; var syscmdsList = '';
var syscmdsListLen = settings.syscmds.length; var syscmdsListLen = settings.syscmdList.length;
if (syscmdsListLen > 0) { if (syscmdsListLen > 0) {
syscmdsList = '<div class="dropdown-divider"></div>'; syscmdsList = '<div class="dropdown-divider"></div>';
for (var i = 0; i < syscmdsListLen; i++) { for (var i = 0; i < syscmdsListLen; i++) {
syscmdsList += '<a class="dropdown-item text-light bg-dark" href="#" data-href=\'{"cmd": "execSyscmd", "options": ["' + syscmdsList += '<a class="dropdown-item text-light bg-dark" href="#" data-href=\'{"cmd": "execSyscmd", "options": ["' +
settings.syscmds[i] + '"]}\'>' + settings.syscmds[i] + '</a>'; settings.syscmdList[i] + '"]}\'>' + settings.syscmdList[i] + '</a>';
} }
} }
document.getElementById('syscmds').innerHTML = syscmdsList; document.getElementById('syscmds').innerHTML = syscmdsList;
@ -1333,6 +1398,8 @@ function parseSettings(obj) {
appRoute(); appRoute();
else if (app.current.app == 'Browse' && app.current.tab == 'Database' && app.current.search != '') else if (app.current.app == 'Browse' && app.current.tab == 'Database' && app.current.search != '')
appRoute(); appRoute();
settingsParsed = true;
} }
function setCols(table, className) { function setCols(table, className) {
@ -1363,13 +1430,22 @@ function setCols(table, className) {
} }
document.getElementById(table + 'ColsDropdown').firstChild.innerHTML = tagChks; document.getElementById(table + 'ColsDropdown').firstChild.innerHTML = tagChks;
var sort = document.getElementById('SearchList').getAttribute('data-sort'); var sort = app.current.sort;
if (sort == '') {
if (settings.featTags) if (table == 'Search') {
if (app.apps.Search.state == '0/any/Title/') {
if (settings.tags.includes('Title')) {
sort = 'Title'; sort = 'Title';
else }
else if (settings.featTags == false) {
sort = 'Filename'; sort = 'Filename';
} }
else {
sort = '-';
}
}
}
if (table != 'Playback') { if (table != 'Playback') {
var heading = ''; var heading = '';
@ -1380,11 +1456,10 @@ function setCols(table, className) {
h = '#'; h = '#';
heading += h; heading += h;
if (table == 'Search' && h == sort ) { if (table == 'Search' && (h == sort || '-' + h == sort) ) {
var sortdesc = false; var sortdesc = false;
if (sort.indexOf('-') == 0) { if (app.current.sort.indexOf('-') == 0) {
sortdesc = true; sortdesc = true;
sort = sort.substring(1);
} }
heading += '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>'; heading += '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
} }
@ -1404,7 +1479,24 @@ function setCols(table, className) {
} }
function getSettings() { function getSettings() {
sendAPI({"cmd": "MPD_API_SETTINGS_GET"}, parseSettings); if (settingsLock == false) {
settingsLock = true;
sendAPI({"cmd": "MYMPD_API_SETTINGS_GET"}, getMpdSettings);
}
}
function getMpdSettings(obj) {
settingsNew = obj.data;
sendAPI({"cmd": "MPD_API_SETTINGS_GET"}, joinSettings);
}
function joinSettings(obj) {
for (var key in obj.data) {
settingsNew[key] = obj.data[key];
}
settings = Object.assign({}, settingsNew);
settingsLock = false;
parseSettings();
} }
function saveCols(table, tableEl) { function saveCols(table, tableEl) {
@ -1431,7 +1523,7 @@ function saveCols(table, tableEl) {
} }
} }
var cols = {"cmd": "MPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}}; var cols = {"cmd": "MYMPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}};
var ths = header.getElementsByTagName('th'); var ths = header.getElementsByTagName('th');
for (var i = 0; i < ths.length; i++) { for (var i = 0; i < ths.length; i++) {
var name = ths[i].getAttribute('data-col'); var name = ths[i].getAttribute('data-col');
@ -1460,7 +1552,7 @@ function saveColsPlayback(table) {
} }
} }
var cols = {"cmd": "MPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}}; var cols = {"cmd": "MYMPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}};
var ths = header.getElementsByTagName('div'); var ths = header.getElementsByTagName('div');
for (var i = 0; i < ths.length; i++) { for (var i = 0; i < ths.length; i++) {
var name = ths[i].getAttribute('data-tag'); var name = ths[i].getAttribute('data-tag');
@ -1585,7 +1677,7 @@ function parseState(obj) {
setCounter(obj.data.currentSongId, obj.data.totalTime, obj.data.elapsedTime); setCounter(obj.data.currentSongId, obj.data.totalTime, obj.data.elapsedTime);
//Get current song //Get current song
if (lastState && lastState.data.currentSongId != obj.data.currentSongId) if (!lastState || lastState.data.currentSongId != obj.data.currentSongId)
sendAPI({"cmd": "MPD_API_PLAYER_CURRENT_SONG"}, songChange); sendAPI({"cmd": "MPD_API_PLAYER_CURRENT_SONG"}, songChange);
//clear playback card if not playing //clear playback card if not playing
if (obj.data.songPos == '-1') { if (obj.data.songPos == '-1') {
@ -2182,7 +2274,7 @@ function gotoBrowse(x) {
var tag = x.parentNode.getAttribute('data-tag'); var tag = x.parentNode.getAttribute('data-tag');
var name = decodeURI(x.parentNode.getAttribute('data-name')); var name = decodeURI(x.parentNode.getAttribute('data-name'));
if (tag != '' && name != '' && name != '-' && settings.browsetags.includes(tag)) if (tag != '' && name != '' && name != '-' && settings.browsetags.includes(tag))
appGoto('Browse', 'Database', tag, '0/-/' + name); appGoto('Browse', 'Database', tag, '0/-/-/' + name);
} }
function songDetails(uri) { function songDetails(uri) {
@ -2232,12 +2324,12 @@ function parseSongDetails(obj) {
} }
function execSyscmd(cmd) { function execSyscmd(cmd) {
sendAPI({"cmd": "MPD_API_SYSCMD", "data": {"cmd": cmd}}); sendAPI({"cmd": "MYMPD_API_SYSCMD", "data": {"cmd": cmd}});
} }
function playlistDetails(uri) { function playlistDetails(uri) {
document.getElementById('BrowsePlaylistsAllList').classList.add('opacity05'); document.getElementById('BrowsePlaylistsAllList').classList.add('opacity05');
appGoto('Browse', 'Playlists', 'Detail', '0/-/' + uri); appGoto('Browse', 'Playlists', 'Detail', '0/-/-/' + uri);
} }
function removeFromPlaylist(uri, pos) { function removeFromPlaylist(uri, pos) {
@ -2865,7 +2957,7 @@ function confirmSettings() {
var selectReplaygain = document.getElementById('selectReplaygain'); var selectReplaygain = document.getElementById('selectReplaygain');
var selectJukeboxPlaylist = document.getElementById('selectJukeboxPlaylist'); var selectJukeboxPlaylist = document.getElementById('selectJukeboxPlaylist');
var selectJukeboxMode = document.getElementById('selectJukeboxMode'); var selectJukeboxMode = document.getElementById('selectJukeboxMode');
sendAPI({"cmd": "MPD_API_SETTINGS_SET", "data": { sendAPI({"cmd": "MYMPD_API_SETTINGS_SET", "data": {
"consume": (document.getElementById('btnConsume').classList.contains('active') ? 1 : 0), "consume": (document.getElementById('btnConsume').classList.contains('active') ? 1 : 0),
"random": (document.getElementById('btnRandom').classList.contains('active') ? 1 : 0), "random": (document.getElementById('btnRandom').classList.contains('active') ? 1 : 0),
"single": (document.getElementById('btnSingle').classList.contains('active') ? 1 : 0), "single": (document.getElementById('btnSingle').classList.contains('active') ? 1 : 0),
@ -2878,7 +2970,8 @@ function confirmSettings() {
"notificationPage": (document.getElementById('btnnotifyPage').classList.contains('active') ? true : false), "notificationPage": (document.getElementById('btnnotifyPage').classList.contains('active') ? true : false),
"jukeboxMode": selectJukeboxMode.options[selectJukeboxMode.selectedIndex].value, "jukeboxMode": selectJukeboxMode.options[selectJukeboxMode.selectedIndex].value,
"jukeboxPlaylist": selectJukeboxPlaylist.options[selectJukeboxPlaylist.selectedIndex].value, "jukeboxPlaylist": selectJukeboxPlaylist.options[selectJukeboxPlaylist.selectedIndex].value,
"jukeboxQueueLength": document.getElementById('inputJukeboxQueueLength').value "jukeboxQueueLength": document.getElementById('inputJukeboxQueueLength').value,
"autoPlay": (document.getElementById('btnAutoPlay').classList.contains('active') ? true : false)
}}, getSettings); }}, getSettings);
modalSettings.hide(); modalSettings.hide();
} else } else
@ -2926,7 +3019,7 @@ function gotoPage(x) {
default: default:
app.current.page = x; app.current.page = x;
} }
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.search); appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.sort + '/' + app.current.search);
} }
function saveQueue() { function saveQueue() {
@ -3087,7 +3180,7 @@ function addFilterLetter(x) {
default: default:
filter = event.target.innerText; filter = event.target.innerText;
} }
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + filter + '/' + app.current.search); appGoto(app.current.app, app.current.tab, app.current.view, '0/' + filter + '/' + app.current.sort + '/' + app.current.search);
}, false); }, false);
} }
@ -3116,7 +3209,7 @@ function addTagList(el, list) {
} }
function gotoTagList() { function gotoTagList() {
appGoto(app.current.app, app.current.tab, app.current.view, '0/-/'); appGoto(app.current.app, app.current.tab, app.current.view, '0/-/-/');
} }
function openModal(modal) { function openModal(modal) {
@ -3162,4 +3255,4 @@ function genId(x) {
} }
//Init app //Init app
appInit(); appInitStart();

View File

@ -1,4 +1,4 @@
var CACHE = 'myMPD-cache-v4.8.0'; var CACHE = 'myMPD-cache-v5.0.0';
var urlsToCache = [ var urlsToCache = [
'/', '/',
'/player.html', '/player.html',

View File

@ -20,7 +20,7 @@ fi
echo "Linking pics directory" echo "Linking pics directory"
[ -e $PWD/htdocs/pics ] || ln -s /var/lib/mympd/pics htdocs/ [ -e $PWD/htdocs/pics ] || ln -s /var/lib/mympd/pics htdocs/
[ -d debug ] || mkdir debug install -d debug
cd debug cd debug
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=DEBUG .. cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=DEBUG ..
make make VERBOSE=1

View File

@ -1,62 +1,77 @@
#/bin/sh #!/bin/bash
java=$(which java 2> /dev/null) JAVABIN=$(which java 2> /dev/null)
HASJAVA="$?"
if [ -f dist/buildtools/closure-compiler.jar ] && [ "$java" != "" ] function minify {
then TYPE="$1"
echo "Minifying javascript" SRC="$2"
[ htdocs/js/player.js -nt dist/htdocs/js/player.min.js ] && \ DST="$3"
java -jar dist/buildtools/closure-compiler.jar htdocs/js/player.js > dist/htdocs/js/player.min.js ERROR="1"
[ htdocs/js/mympd.js -nt dist/htdocs/js/mympd.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/js/mympd.js > dist/htdocs/js/mympd.min.js
[ htdocs/sw.js -nt dist/htdocs/sw.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/sw.js > dist/htdocs/sw.min.js
[ htdocs/js/keymap.js -nt dist/htdocs/js/keymap.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/js/keymap.js > dist/htdocs/js/keymap.min.js
[ htdocs/js/keymap.js -nt dist/htdocs/js/keymap.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/js/keymap.js > dist/htdocs/js/keymap.min.js
[ dist/htdocs/js/bootstrap-native-v4.js -nt dist/htdocs/js/bootstrap-native-v4.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar dist/htdocs/js/bootstrap-native-v4.js > dist/htdocs/js/bootstrap-native-v4.min.js
else
echo "dist/buildtools/closure-compiler.jar not found, using non-minified files"
[ htdocs/js/player.js -nt dist/htdocs/js/player.min.js ] && \
cp htdocs/js/player.js dist/htdocs/js/player.min.js
[ htdocs/js/mympd.js -nt dist/htdocs/js/mympd.min.js ] && \
cp htdocs/js/mympd.js dist/htdocs/js/mympd.min.js
[ htdocs/sw.js -nt dist/htdocs/sw.min.js ] && \
cp htdocs/sw.js dist/htdocs/sw.min.js
[ htdocs/js/keymap.js -nt dist/htdocs/js/keymap.min.js ] && \
cp htdocs/js/keymap.js dist/htdocs/js/keymap.min.js
[ dist/htdocs/js/bootstrap-native-v4.js -nt dist/htdocs/js/bootstrap-native-v4.min.js ] && \
cp dist/htdocs/js/bootstrap-native-v4.js dist/htdocs/js/bootstrap-native-v4.min.js
fi
if [ -f dist/buildtools/closure-stylesheets.jar ] && [ "$java" != "" ] if [ "$DST" -nt "$SRC" ]
then then
echo "Minifying stylesheets" return
[ htdocs/css/mympd.css -nt dist/htdocs/css/mympd.min.css ] && \ fi
java -jar dist/buildtools/closure-stylesheets.jar --allow-unrecognized-properties htdocs/css/mympd.css > dist/htdocs/css/mympd.min.css
else if [ "$TYPE" = "html" ]
echo "dist/buildtools/closure-stylesheets.jar not found, using non-minified files" then
[ htdocs/css/mympd.css -nt dist/htdocs/css/mympd.min.css ] && \ perl -pe 's/^\s*//gm; s/\s*$//gm' $SRC > $DST
cp htdocs/css/mympd.css dist/htdocs/css/mympd.min.css ERROR="$?"
fi elif [ "$TYPE" = "js" ] && [ "$HASJAVA" = "0" ]
then
$JAVABIN -jar dist/buildtools/closure-compiler.jar $SRC > $DST
ERROR="$?"
elif [ "$TYPE" = "css" ] && [ "$HASJAVA" = "0" ]
then
$JAVABIN -jar dist/buildtools/closure-stylesheets.jar --allow-unrecognized-properties $SRC > $DST
ERROR="$?"
elif [ "$TYPE" = "cp" ]
then
cp $SRC $DST
ERROR="$?"
else
ERROR="1"
fi
if [ "$ERROR" = "1" ]
then
echo "Error minifying $SRC, copy $SRC to $DST"
cp $SRC $DST
fi
}
echo "Minifying javascript"
minify js htdocs/js/player.js dist/htdocs/js/player.min.js
minify js htdocs/js/mympd.js dist/htdocs/js/mympd.min.js
minify js htdocs/sw.js dist/htdocs/sw.min.js
minify js htdocs/js/keymap.js dist/htdocs/js/keymap.min.js
minify js htdocs/js/keymap.js dist/htdocs/js/keymap.min.js
minify js dist/htdocs/js/bootstrap-native-v4.js dist/htdocs/js/bootstrap-native-v4.min.js
echo "Minifying stylesheets"
minify css htdocs/css/mympd.css dist/htdocs/css/mympd.min.css
echo "Minifying html" echo "Minifying html"
[ htdocs/index.html -nt dist/htdocs/index.html ] && \ minify html htdocs/index.html dist/htdocs/index.html
perl -pe 's/^\s*//gm; s/\s*$//gm' htdocs/index.html > dist/htdocs/index.html minify html htdocs/player.html dist/htdocs/player.html
[ htdocs/player.html -nt dist/htdocs/player.html ] && \
perl -pe 's/^\s*//gm; s/\s*$//gm' htdocs/player.html > dist/htdocs/player.html
echo "Compiling and installing mympd" echo "Compiling and installing mympd"
[ -d release ] || mkdir release install -d release
cd release cd release
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=RELEASE .. INSTALL_PREFIX="${MYMPD_INSTALL_PREFIX:-/usr}"
cmake -DCMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=RELEASE ..
make make
sudo make install if [ $INSTALL_PREFIX = "/usr" ]
cd .. then
sudo make install
sudo debian/postinst cd ..
sudo debian/postinst
else
# Container build implied when $INSTALL_PREFIX != /usr
make install
cd ..
fi
if [ -x /usr/bin/cppcheck ] if [ -x /usr/bin/cppcheck ]
then then

93
src/global.c Normal file
View File

@ -0,0 +1,93 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <dirent.h>
#include "tiny_queue.h"
#include "list.h"
#include "global.h"
bool testdir(const char *name, const char *dirname) {
DIR* dir = opendir(dirname);
if (dir) {
closedir(dir);
LOG_INFO() printf("%s: \"%s\"\n", name, dirname);
return true;
}
else {
printf("%s: \"%s\" don't exists\n", name, dirname);
return false;
}
}
int randrange(int n) {
return rand() / (RAND_MAX / (n + 1) + 1);
}
bool validate_string(const char *data) {
static char ok_chars[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"1234567890_-. ";
const char *cp = data;
const char *end = data + strlen(data);
for (cp += strspn(cp, ok_chars); cp != end; cp += strspn(cp, ok_chars)) {
printf("ERROR: Invalid character in string\n");
return false;
}
return true;
}
int replacechar(char *str, const char orig, const char rep) {
char *ix = str;
int n = 0;
while ((ix = strchr(ix, orig)) != NULL) {
*ix++ = rep;
n++;
}
return n;
}
int copy_string(char * const dest, char const * const src, size_t const dst_len, size_t const src_len) {
if (dst_len == 0 || src_len == 0)
return 0;
size_t const max = (src_len < dst_len) ? src_len : dst_len -1;
memcpy(dest, src, max);
dest[max] = '\0';
return max;
}
enum mympd_cmd_ids get_cmd_id(const char *cmd) {
const char * mympd_cmd_strs[] = { MYMPD_CMDS(GEN_STR) };
for (unsigned i = 0; i < sizeof(mympd_cmd_strs) / sizeof(mympd_cmd_strs[0]); i++)
if (!strncmp(cmd, mympd_cmd_strs[i], strlen(mympd_cmd_strs[i])))
return i;
return 0;
}

197
src/global.h.in Normal file
View File

@ -0,0 +1,197 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __GLOBAL_H__
#define __GLOBAL_H__
#include <signal.h>
//architecture
#cmakedefine PKGARCH64
//myMPD version from cmake
#define MYMPD_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}
#define MYMPD_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}
#define MYMPD_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}
#define MYMPD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}"
//Webserver document root
#define DOC_ROOT "${ASSETS_PATH}"
//Max size of mpd_client response buffer
#define MAX_SIZE 2048 * 400
#define MAX_ELEMENTS_PER_PAGE 400
//central logging definition
#cmakedefine DEBUG
#define LOG_INFO() if (loglevel >= 1)
#define LOG_VERBOSE() if (loglevel >= 2)
#define LOG_DEBUG() if (loglevel == 3)
//check and return buffer size
#define CHECK_RETURN_LEN() do { \
if (len > MAX_SIZE) \
printf("ERROR: Buffer truncated %d / %d\n", (int)len, MAX_SIZE); \
return len; \
} while (0)
//API cmds
#define MYMPD_CMDS(X) \
X(MPD_API_UNKNOWN) \
X(MPD_API_QUEUE_CLEAR) \
X(MPD_API_QUEUE_CROP) \
X(MPD_API_QUEUE_SAVE) \
X(MPD_API_QUEUE_LIST) \
X(MPD_API_QUEUE_SEARCH) \
X(MPD_API_QUEUE_RM_TRACK) \
X(MPD_API_QUEUE_RM_RANGE) \
X(MPD_API_QUEUE_MOVE_TRACK) \
X(MPD_API_QUEUE_ADD_TRACK_AFTER) \
X(MPD_API_QUEUE_ADD_TRACK) \
X(MPD_API_QUEUE_ADD_PLAY_TRACK) \
X(MPD_API_QUEUE_REPLACE_TRACK) \
X(MPD_API_QUEUE_ADD_PLAYLIST) \
X(MPD_API_QUEUE_REPLACE_PLAYLIST) \
X(MPD_API_QUEUE_SHUFFLE) \
X(MPD_API_QUEUE_LAST_PLAYED) \
X(MPD_API_PLAYLIST_CLEAR) \
X(MPD_API_PLAYLIST_RENAME) \
X(MPD_API_PLAYLIST_MOVE_TRACK) \
X(MPD_API_PLAYLIST_ADD_TRACK) \
X(MPD_API_PLAYLIST_RM_TRACK) \
X(MPD_API_PLAYLIST_RM) \
X(MPD_API_PLAYLIST_LIST) \
X(MPD_API_PLAYLIST_CONTENT_LIST) \
X(MPD_API_SMARTPLS_UPDATE_ALL) \
X(MPD_API_SMARTPLS_SAVE) \
X(MPD_API_SMARTPLS_GET) \
X(MPD_API_DATABASE_SEARCH_ADV) \
X(MPD_API_DATABASE_SEARCH) \
X(MPD_API_DATABASE_UPDATE) \
X(MPD_API_DATABASE_RESCAN) \
X(MPD_API_DATABASE_FILESYSTEM_LIST) \
X(MPD_API_DATABASE_TAG_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST) \
X(MPD_API_DATABASE_STATS) \
X(MPD_API_DATABASE_SONGDETAILS) \
X(MPD_API_PLAYER_PLAY_TRACK) \
X(MPD_API_PLAYER_VOLUME_SET) \
X(MPD_API_PLAYER_VOLUME_GET) \
X(MPD_API_PLAYER_PAUSE) \
X(MPD_API_PLAYER_PLAY) \
X(MPD_API_PLAYER_STOP) \
X(MPD_API_PLAYER_SEEK) \
X(MPD_API_PLAYER_NEXT) \
X(MPD_API_PLAYER_PREV) \
X(MPD_API_PLAYER_OUTPUT_LIST) \
X(MPD_API_PLAYER_TOGGLE_OUTPUT) \
X(MPD_API_PLAYER_CURRENT_SONG) \
X(MPD_API_PLAYER_STATE) \
X(MPD_API_SETTINGS_GET) \
X(MPD_API_LIKE) \
X(MYMPD_API_COLS_SAVE) \
X(MYMPD_API_SYSCMD) \
X(MYMPD_API_SETTINGS_GET) \
X(MYMPD_API_SETTINGS_SET) \
#define GEN_ENUM(X) X,
#define GEN_STR(X) #X,
//Global variables
int loglevel;
//signal handler
sig_atomic_t s_signal_received;
enum mympd_cmd_ids {
MYMPD_CMDS(GEN_ENUM)
};
enum jukebox_modes {
JUKEBOX_OFF,
JUKEBOX_ADD_SONG,
JUKEBOX_ADD_ALBUM,
};
//message queue
tiny_queue_t *web_server_queue;
tiny_queue_t *mpd_client_queue;
tiny_queue_t *mympd_api_queue;
typedef struct t_work_request {
long conn_id; // needed to identify the connection where to send the reply
char data[1000];
int length;
enum mympd_cmd_ids cmd_id;
} t_work_request;
typedef struct t_work_result {
long conn_id; // needed to identify the connection where to send the reply
char data[MAX_SIZE];
int length;
} t_work_result;
//myMPD configuration
typedef struct t_config {
long mpdport;
char *mpdhost;
char *mpdpass;
char *webport;
bool ssl;
char *sslport;
char *sslcert;
char *sslkey;
char *user;
bool coverimage;
char *coverimagename;
long coverimagesize;
bool stickers;
bool mixramp;
char *taglist;
char *searchtaglist;
char *browsetaglist;
bool smartpls;
char *varlibdir;
char *etcdir;
unsigned long max_elements_per_page;
bool syscmds;
bool localplayer;
long streamport;
char *streamurl;
unsigned long last_played_count;
long loglevel;
char *backgroundcolor;
} t_config;
//global functions
bool testdir(const char *name, const char *dirname);
int randrange(int n);
bool validate_string(const char *data);
int copy_string(char * const dest, char const * const src, size_t const dst_len, size_t const src_len);
int replacechar(char *str, const char orig, const char rep);
enum mympd_cmd_ids get_cmd_id(const char *cmd);
#endif

View File

@ -1,5 +1,5 @@
/* myMPD /* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de> (c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle This linked list implementation is based on: https://github.com/joshkunz/ashuffle
@ -81,7 +81,7 @@ int list_shuffle(struct list *l) {
if (l->length < 2) if (l->length < 2)
return 1; return 1;
srand((unsigned int)time(NULL)); // srand((unsigned int)time(NULL));
struct node *current = l->list; struct node *current = l->list;
while (current != NULL) { while (current != NULL) {

View File

@ -1,5 +1,5 @@
/* myMPD /* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de> (c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle This linked list implementation is based on: https://github.com/joshkunz/ashuffle
@ -18,19 +18,20 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef __LIST_H__
#define __LIST_H__
struct node { struct node {
char *data; char *data;
long value; long value;
struct node *next; struct node *next;
}; };
struct list { struct list {
unsigned length; unsigned length;
struct node *list; struct node *list;
}; };
int list_init(struct list *l); int list_init(struct list *l);
int list_push(struct list *l, const char *data, int value); int list_push(struct list *l, const char *data, int value);
int list_insert(struct list *l, const char *data, int value); int list_insert(struct list *l, const char *data, int value);
@ -43,3 +44,4 @@ int list_shuffle(struct list *l);
int list_sort_by_value(struct list *l, bool order); int list_sort_by_value(struct list *l, bool order);
int list_swap_item(struct node *n1, struct node *n2); int list_swap_item(struct node *n1, struct node *n2);
struct node *list_node_at(const struct list * l, unsigned index); struct node *list_node_at(const struct list * l, unsigned index);
#endif

374
src/main.c Normal file
View File

@ -0,0 +1,374 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of: ympd (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
ympd project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include <pthread.h>
#include <dirent.h>
#include <mpd/client.h>
#include <stdbool.h>
#include "list.h"
#include "tiny_queue.h"
#include "global.h"
#include "mpd_client.h"
#include "../dist/src/mongoose/mongoose.h"
#include "web_server.h"
#include "mympd_api.h"
#include "../dist/src/inih/ini.h"
static void signal_handler(int sig_num) {
signal(sig_num, signal_handler); // Reinstantiate signal handler
s_signal_received = sig_num;
//Wakeup mympd_api_loop
pthread_cond_signal(&mympd_api_queue->wakeup);
}
static int inihandler(void *user, const char *section, const char *name, const char* value) {
t_config* p_config = (t_config*)user;
char *crap;
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
if (MATCH("mpd", "mpdhost")) {
free(p_config->mpdhost);
p_config->mpdhost = strdup(value);
}
else if (MATCH("mpd", "mpdport")) {
p_config->mpdport = strtol(value, &crap, 10);
}
else if (MATCH("mpd", "mpdpass")) {
free(p_config->mpdpass);
p_config->mpdpass = strdup(value);
}
else if (MATCH("mpd", "streamport")) {
p_config->streamport = strtol(value, &crap, 10);
}
else if (MATCH("webserver", "webport")) {
free(p_config->webport);
p_config->webport = strdup(value);
}
else if (MATCH("webserver", "ssl")) {
p_config->ssl = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("webserver", "sslport")) {
free(p_config->sslport);
p_config->sslport = strdup(value);
}
else if (MATCH("webserver", "sslcert")) {
free(p_config->sslcert);
p_config->sslcert = strdup(value);
}
else if (MATCH("webserver", "sslkey")) {
free(p_config->sslkey);
p_config->sslkey = strdup(value);
}
else if (MATCH("mympd", "user")) {
free(p_config->user);
p_config->user = strdup(value);
}
else if (MATCH("mympd", "coverimage")) {
p_config->coverimage = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "coverimagename")) {
free(p_config->coverimagename);
p_config->coverimagename = strdup(value);
}
else if (MATCH("mympd", "coverimagesize")) {
p_config->coverimagesize = strtol(value, &crap, 10);
}
else if (MATCH("mympd", "varlibdir")) {
free(p_config->varlibdir);
p_config->varlibdir = strdup(value);
}
else if (MATCH("mympd", "stickers")) {
p_config->stickers = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "smartpls")) {
p_config->smartpls = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "mixramp")) {
p_config->mixramp = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "taglist")) {
free(p_config->taglist);
p_config->taglist = strdup(value);
}
else if (MATCH("mympd", "searchtaglist")) {
free(p_config->searchtaglist);
p_config->searchtaglist = strdup(value);
}
else if (MATCH("mympd", "browsetaglist")) {
free(p_config->browsetaglist);
p_config->browsetaglist = strdup(value);
}
else if (MATCH("mympd", "max_elements_per_page")) {
p_config->max_elements_per_page = strtol(value, &crap, 10);
if (p_config->max_elements_per_page > MAX_ELEMENTS_PER_PAGE) {
printf("Setting max_elements_per_page to maximal value %d", MAX_ELEMENTS_PER_PAGE);
p_config->max_elements_per_page = MAX_ELEMENTS_PER_PAGE;
}
}
else if (MATCH("mympd", "syscmds")) {
p_config->syscmds = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "localplayer")) {
p_config->localplayer = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "streamurl")) {
free(p_config->streamurl);
p_config->streamurl = strdup(value);
}
else if (MATCH("mympd", "last_played_count")) {
p_config->last_played_count = strtol(value, &crap, 10);
}
else if (MATCH("mympd", "loglevel")) {
p_config->loglevel = strtol(value, &crap, 10);
}
else if (MATCH("theme", "backgroundcolor")) {
free(p_config->backgroundcolor);
p_config->backgroundcolor = strdup(value);
}
else {
printf("Unkown config option: %s - %s\n", section, name);
return 0;
}
return 1;
}
static void free_config(t_config *config) {
free(config->mpdhost);
free(config->mpdpass);
free(config->webport);
free(config->sslport);
free(config->sslcert);
free(config->sslkey);
free(config->user);
free(config->coverimagename);
free(config->taglist);
free(config->searchtaglist);
free(config->browsetaglist);
free(config->varlibdir);
free(config->etcdir);
free(config->streamurl);
free(config->backgroundcolor);
free(config);
}
int main(int argc, char **argv) {
s_signal_received = 0;
char testdirname[400];
bool init_webserver = false;
bool init_thread_webserver = false;
bool init_thread_mpdclient = false;
bool init_thread_mympdapi = false;
int rc = EXIT_FAILURE;
mpd_client_queue = tiny_queue_create();
mympd_api_queue = tiny_queue_create();
web_server_queue = tiny_queue_create();
srand((unsigned int)time(NULL));
//mympd config defaults
t_config *config = (t_config *)malloc(sizeof(t_config));
config->mpdhost = strdup("127.0.0.1");
config->mpdport = 6600;
config->mpdpass = NULL;
config->webport = strdup("80");
config->ssl = true;
config->sslport = strdup("443");
config->sslcert = strdup("/etc/mympd/ssl/server.pem");
config->sslkey = strdup("/etc/mympd/ssl/server.key");
config->user = strdup("mympd");
config->streamport = 8000;
config->streamurl = strdup("");
config->coverimage = true;
config->coverimagename = strdup("folder.jpg");
config->coverimagesize = 240;
config->varlibdir = strdup("/var/lib/mympd");
config->stickers = true;
config->mixramp = true;
config->taglist = strdup("Artist,Album,AlbumArtist,Title,Track,Genre,Date,Composer,Performer");
config->searchtaglist = strdup("Artist,Album,AlbumArtist,Title,Genre,Composer,Performer");
config->browsetaglist = strdup("Artist,Album,AlbumArtist,Genre,Composer,Performer");
config->smartpls = true;
config->max_elements_per_page = 100;
config->last_played_count = 20;
char *etcdir = strdup(argv[1]);
config->etcdir = strdup(dirname(etcdir));
free(etcdir);
config->syscmds = false;
config->localplayer = true;
config->loglevel = 1;
config->backgroundcolor = strdup("#888");
if (argc == 2) {
printf("Starting myMPD %s\n", MYMPD_VERSION);
printf("Libmpdclient %i.%i.%i\n", LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION, LIBMPDCLIENT_PATCH_VERSION);
printf("Parsing config file: %s\n", argv[1]);
if (ini_parse(argv[1], inihandler, config) < 0) {
printf("Can't load config file \"%s\"\n", argv[1]);
goto cleanup;
}
}
else {
printf("myMPD %s\n"
"Copyright (C) 2018-2019 Juergen Mang <mail@jcgames.de>\n"
"https://github.com/jcorporation/myMPD\n"
"Built " __DATE__ " "__TIME__"\n\n"
"Usage: %s /path/to/mympd.conf\n",
MYMPD_VERSION,
argv[0]
);
goto cleanup;
}
#ifdef DEBUG
printf("Debug flag enabled, setting loglevel to debug\n");
config->loglevel = 3;
#endif
printf("Setting loglevel to %ld\n", config->loglevel);
loglevel = config->loglevel;
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
//init webserver
struct mg_mgr mgr;
if (!web_server_init(&mgr, config)) {
goto cleanup;
}
else {
init_webserver = true;
}
//drop privileges
if (config->user != NULL) {
printf("Droping privileges to %s\n", config->user);
struct passwd *pw;
if ((pw = getpwnam(config->user)) == NULL) {
printf("ERROR: getpwnam() failed, unknown user\n");
goto cleanup;
} else if (setgroups(0, NULL) != 0) {
printf("ERROR: setgroups() failed\n");
goto cleanup;
} else if (setgid(pw->pw_gid) != 0) {
printf("ERROR: setgid() failed\n");
goto cleanup;
} else if (setuid(pw->pw_uid) != 0) {
printf("ERROR: setuid() failed\n");
goto cleanup;
}
}
if (getuid() == 0) {
printf("myMPD should not be run with root privileges\n");
goto cleanup;
}
//check needed directories
if (!testdir("Document root", DOC_ROOT)) {
goto cleanup;
}
snprintf(testdirname, 400, "%s/library", DOC_ROOT);
if (!testdir("Link to mpd music_directory", testdirname)) {
printf("Disabling coverimage support\n");
config->coverimage = false;
}
snprintf(testdirname, 400, "%s/tmp", config->varlibdir);
if (!testdir("Temp dir", testdirname)) {
goto cleanup;
}
snprintf(testdirname, 400, "%s/smartpls", config->varlibdir);
if (!testdir("Smartpls dir", testdirname)) {
goto cleanup;
}
snprintf(testdirname, 400, "%s/state", config->varlibdir);
if (!testdir("State dir", testdirname)) {
goto cleanup;
}
//Create working threads
pthread_t mpd_client_thread, web_server_thread, mympd_api_thread;
//mpd connection
if (pthread_create(&mpd_client_thread, NULL, mpd_client_loop, config) == 0) {
pthread_setname_np(mpd_client_thread, "mympd_mpdclient");
init_thread_mpdclient = true;
}
else {
printf("ERROR: can't create mympd_client thread\n");
s_signal_received = SIGTERM;
}
//webserver
if (pthread_create(&web_server_thread, NULL, web_server_loop, &mgr) == 0) {
pthread_setname_np(web_server_thread, "mympd_webserver");
init_thread_webserver = true;
}
else {
printf("ERROR: can't create mympd_webserver thread\n");
s_signal_received = SIGTERM;
}
//mympd api
if (pthread_create(&mympd_api_thread, NULL, mympd_api_loop, config) == 0) {
pthread_setname_np(mympd_api_thread, "mympd_mympdapi");
init_thread_mympdapi = true;
}
else {
printf("ERROR: can't create mympd_mympdapi thread\n");
s_signal_received = SIGTERM;
}
//Outsourced all work to separate threads, do nothing...
rc = EXIT_SUCCESS;
//Try to cleanup all
cleanup:
if (init_thread_mpdclient)
pthread_join(mpd_client_thread, NULL);
if (init_thread_webserver)
pthread_join(web_server_thread, NULL);
if (init_thread_mympdapi)
pthread_join(mympd_api_thread, NULL);
if (init_webserver)
web_server_free(&mgr);
tiny_queue_free(web_server_queue);
tiny_queue_free(mpd_client_queue);
tiny_queue_free(mympd_api_queue);
free_config(config);
return rc;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/* myMPD /* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de> (c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of: myMPD ist fork of:
@ -24,265 +24,6 @@
#ifndef __MPD_CLIENT_H__ #ifndef __MPD_CLIENT_H__
#define __MPD_CLIENT_H__ #define __MPD_CLIENT_H__
void *mpd_client_loop(void *arg_config);
#include "../dist/src/mongoose/mongoose.h"
#include "list.h"
#define RETURN_ERROR_AND_RECOVER(X) do { \
printf("MPD %s: %s\n", X, mpd_connection_get_error_message(mpd.conn)); \
len = json_printf(&out, "{type: error, data: %Q}", mpd_connection_get_error_message(mpd.conn)); \
if (!mpd_connection_clear_error(mpd.conn)) \
mpd.conn_state = MPD_FAILURE; \
return len; \
} while (0)
#define LOG_ERROR_AND_RECOVER(X) do { \
printf("MPD %s: %s\n", X, mpd_connection_get_error_message(mpd.conn)); \
if (!mpd_connection_clear_error(mpd.conn)) \
mpd.conn_state = MPD_FAILURE; \
} while (0)
#define CHECK_RETURN_LEN() do { \
if (len > MAX_SIZE) \
printf("Buffer truncated: %d, %d\n", len, MAX_SIZE); \
return len; \
} while (0)
#define PUT_SONG_TAGS() do { \
struct node *current = mympd_tags.list; \
int tagnr = 0; \
while (current != NULL) { \
if (tagnr ++) \
len += json_printf(&out, ","); \
len += json_printf(&out, "%Q: %Q", current->data, mympd_get_tag(song, mpd_tag_name_parse(current->data))); \
current = current->next; \
} \
len += json_printf(&out, ", Duration: %d, uri: %Q", mpd_song_get_duration(song), mpd_song_get_uri(song)); \
} while (0)
#define PUT_MIN_SONG_TAGS() do { \
len += json_printf(&out, "Title: %Q, Duration: %d, uri: %Q", mympd_get_tag(song, MPD_TAG_TITLE), mpd_song_get_duration(song), mpd_song_get_uri(song)); \
} while (0)
#define LOG_INFO() if (config.loglevel >= 1)
#define LOG_VERBOSE() if (config.loglevel >= 2)
#define LOG_DEBUG() if (config.loglevel == 3)
#define MAX_SIZE 2048 * 400
#define MAX_ELEMENTS_PER_PAGE 400
#define GEN_ENUM(X) X,
#define GEN_STR(X) #X,
#define MPD_CMDS(X) \
X(MPD_API_QUEUE_CLEAR) \
X(MPD_API_QUEUE_CROP) \
X(MPD_API_QUEUE_SAVE) \
X(MPD_API_QUEUE_LIST) \
X(MPD_API_QUEUE_SEARCH) \
X(MPD_API_QUEUE_RM_TRACK) \
X(MPD_API_QUEUE_RM_RANGE) \
X(MPD_API_QUEUE_MOVE_TRACK) \
X(MPD_API_QUEUE_ADD_TRACK_AFTER) \
X(MPD_API_QUEUE_ADD_TRACK) \
X(MPD_API_QUEUE_ADD_PLAY_TRACK) \
X(MPD_API_QUEUE_REPLACE_TRACK) \
X(MPD_API_QUEUE_ADD_PLAYLIST) \
X(MPD_API_QUEUE_REPLACE_PLAYLIST) \
X(MPD_API_QUEUE_SHUFFLE) \
X(MPD_API_QUEUE_LAST_PLAYED) \
X(MPD_API_PLAYLIST_CLEAR) \
X(MPD_API_PLAYLIST_RENAME) \
X(MPD_API_PLAYLIST_MOVE_TRACK) \
X(MPD_API_PLAYLIST_ADD_TRACK) \
X(MPD_API_PLAYLIST_RM_TRACK) \
X(MPD_API_PLAYLIST_RM) \
X(MPD_API_PLAYLIST_LIST) \
X(MPD_API_PLAYLIST_CONTENT_LIST) \
X(MPD_API_SMARTPLS_UPDATE_ALL) \
X(MPD_API_SMARTPLS_SAVE) \
X(MPD_API_SMARTPLS_GET) \
X(MPD_API_DATABASE_SEARCH_ADV) \
X(MPD_API_DATABASE_SEARCH) \
X(MPD_API_DATABASE_UPDATE) \
X(MPD_API_DATABASE_RESCAN) \
X(MPD_API_DATABASE_FILESYSTEM_LIST) \
X(MPD_API_DATABASE_TAG_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST) \
X(MPD_API_DATABASE_STATS) \
X(MPD_API_DATABASE_SONGDETAILS) \
X(MPD_API_PLAYER_PLAY_TRACK) \
X(MPD_API_PLAYER_VOLUME_SET) \
X(MPD_API_PLAYER_VOLUME_GET) \
X(MPD_API_PLAYER_PAUSE) \
X(MPD_API_PLAYER_PLAY) \
X(MPD_API_PLAYER_STOP) \
X(MPD_API_PLAYER_SEEK) \
X(MPD_API_PLAYER_NEXT) \
X(MPD_API_PLAYER_PREV) \
X(MPD_API_PLAYER_OUTPUT_LIST) \
X(MPD_API_PLAYER_TOGGLE_OUTPUT) \
X(MPD_API_PLAYER_CURRENT_SONG) \
X(MPD_API_PLAYER_STATE) \
X(MPD_API_SETTINGS_GET) \
X(MPD_API_SETTINGS_SET) \
X(MPD_API_WELCOME) \
X(MPD_API_LIKE) \
X(MPD_API_SYSCMD) \
X(MPD_API_COLS_SAVE) \
X(MPD_API_UNKNOWN)
enum mpd_cmd_ids {
MPD_CMDS(GEN_ENUM)
};
enum mpd_conn_states {
MPD_DISCONNECTED,
MPD_FAILURE,
MPD_CONNECTED,
MPD_RECONNECT,
MPD_DISCONNECT
};
struct t_mpd {
// Connection
struct mpd_connection *conn;
enum mpd_conn_states conn_state;
int timeout;
// Reponse Buffer
char buf[MAX_SIZE];
size_t buf_size;
// States
int song_id;
int next_song_id;
int last_song_id;
unsigned queue_version;
unsigned queue_length;
int last_update_sticker_song_id;
int last_last_played_id;
// Features
const unsigned* protocol;
// Supported tags
bool feat_sticker;
bool feat_playlists;
bool feat_tags;
bool feat_library;
bool feat_advsearch;
} mpd;
struct list mpd_tags;
struct list mympd_tags;
struct list mympd_searchtags;
struct list mympd_browsetags;
struct list last_played;
struct list syscmds;
typedef struct {
long mpdport;
const char *mpdhost;
const char *mpdpass;
const char *webport;
bool ssl;
const char *sslport;
const char *sslcert;
const char *sslkey;
const char *user;
bool coverimage;
const char *coverimagename;
long coverimagesize;
bool stickers;
bool mixramp;
const char *taglist;
const char *searchtaglist;
const char *browsetaglist;
bool smartpls;
const char *varlibdir;
const char *etcdir;
unsigned long max_elements_per_page;
bool syscmds;
bool localplayer;
long streamport;
const char *streamurl;
unsigned long last_played_count;
long loglevel;
} t_config;
t_config config;
typedef struct {
long playCount;
long skipCount;
long lastPlayed;
long like;
} t_sticker;
typedef struct {
bool notificationWeb;
bool notificationPage;
int jukeboxMode;
const char *jukeboxPlaylist;
int jukeboxQueueLength;
char *colsQueueCurrent;
char *colsSearch;
char *colsBrowseDatabase;
char *colsBrowsePlaylistsDetail;
char *colsBrowseFilesystem;
char *colsPlayback;
char *colsQueueLastPlayed;
} t_mympd_state;
t_mympd_state mympd_state;
static int is_websocket(const struct mg_connection *nc) {
return nc->flags & MG_F_IS_WEBSOCKET;
}
int randrange(int n);
void mympd_idle(struct mg_mgr *sm, int timeout);
void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask);
void callback_mympd(struct mg_connection *nc, const struct mg_str msg);
void mympd_notify(struct mg_mgr *s);
bool mympd_count_song_id(int song_id, char *name, int value);
bool mympd_count_song_uri(const char *uri, char *name, int value);
bool mympd_like_song_uri(const char *uri, int value);
bool mympd_last_played_song_uri(const char *uri);
bool mympd_last_played_song_id(int song_id);
bool mympd_get_sticker(const char *uri, t_sticker *sticker);
bool mympd_last_played_list(int song_id);
bool mympd_jukebox();
bool mympd_state_get(char *name, char *value);
bool mympd_state_set(const char *name, const char *value);
int mympd_syscmd(char *buffer, char *cmd, int order);
int mympd_smartpls_save(char *smartpltype, char *playlist, char *tag, char *searchstr, int maxentries, int timerange);
int mympd_smartpls_put(char *buffer, char *playlist);
int mympd_smartpls_update_all();
int mympd_smartpls_clear(char *playlist);
int mympd_smartpls_update_sticker(char *playlist, char *sticker, int maxentries);
int mympd_smartpls_update_newest(char *playlist, int timerange);
int mympd_smartpls_update_search(char *playlist, char *tag, char *searchstr);
int mympd_get_updatedb_state(char *buffer);
int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, int *last_song_id, unsigned *queue_version, unsigned *queue_length);
int mympd_put_outputs(char *buffer);
int mympd_put_current_song(char *buffer);
int mympd_put_queue(char *buffer, unsigned int offset, unsigned *queue_version, unsigned *queue_length);
int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter);
int mympd_search(char *buffer, char *searchstr, char *filter, char *plist, unsigned int offset);
int mympd_search_adv(char *buffer, char *expression, char *sort, bool sortdesc, char *grouptag, char *plist, unsigned int offset);
int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr);
int mympd_put_welcome(char *buffer);
int mympd_put_volume(char *buffer);
int mympd_put_stats(char *buffer);
int mympd_put_settings(char *buffer);
int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char *mpdsearchtagtype, char *searchstr, char *filter);
int mympd_put_songs_in_album(char *buffer, char *album, char *search, char *tag);
int mympd_put_playlists(char *buffer, unsigned int offset, char *filter);
int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char *filter);
int mympd_put_songdetails(char *buffer, char *uri);
int mympd_put_last_played_songs(char *buffer, unsigned int offset);
int mympd_queue_crop(char *buffer);
void mympd_disconnect();
#endif #endif

View File

@ -1,564 +0,0 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include <mpd/client.h>
#include "../dist/src/mongoose/mongoose.h"
#include "../dist/src/frozen/frozen.h"
#include "../dist/src/inih/ini.h"
#include "mpd_client.h"
#include "config.h"
static sig_atomic_t s_signal_received = 0;
static struct mg_serve_http_opts s_http_server_opts;
static void signal_handler(int sig_num) {
signal(sig_num, signal_handler); // Reinstantiate signal handler
s_signal_received = sig_num;
}
static void handle_api(struct mg_connection *nc, struct http_message *hm) {
if (!is_websocket(nc))
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n\r\n");
char buf[1000] = {0};
int len = sizeof(buf) - 1 < hm->body.len ? sizeof(buf) - 1 : hm->body.len;
memcpy(buf, hm->body.p, len);
struct mg_str d = {buf, len};
callback_mympd(nc, d);
if (!is_websocket(nc))
mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
}
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch(ev) {
case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("New websocket request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/ws") != 0) {
printf("ERROR: Websocket request not to /ws, closing connection\n");
mg_printf(nc, "%s", "HTTP/1.1 403 FORBIDDEN\r\n\r\n");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
LOG_VERBOSE() printf("New Websocket connection established\n");
struct mg_str d = mg_mk_str("{\"cmd\":\"MPD_API_WELCOME\"}");
callback_mympd(nc, d);
break;
}
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("HTTP request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/api") == 0)
handle_api(nc, hm);
else
mg_serve_http(nc, hm, s_http_server_opts);
break;
}
case MG_EV_CLOSE: {
if (is_websocket(nc)) {
LOG_VERBOSE() printf("Websocket connection closed\n");
}
else {
LOG_VERBOSE() printf("HTTP connection closed\n");
}
break;
}
}
}
static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data) {
char *host;
char host_header[1024];
switch(ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
snprintf(host_header, 1024, "%.*s", host_hdr->len, host_hdr->p);
host = strtok(host_header, ":");
char s_redirect[250];
if (strcmp(config.sslport, "443") == 0)
snprintf(s_redirect, 250, "https://%s/", host);
else
snprintf(s_redirect, 250, "https://%s:%s/", host, config.sslport);
LOG_VERBOSE() printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
}
}
static int inihandler(void* user, const char* section, const char* name, const char* value) {
t_config* p_config = (t_config*)user;
char *crap;
#define MATCH(n) strcmp(name, n) == 0
if (MATCH("mpdhost"))
p_config->mpdhost = strdup(value);
else if (MATCH("mpdport"))
p_config->mpdport = strtol(value, &crap, 10);
else if (MATCH("mpdpass"))
p_config->mpdpass = strdup(value);
else if (MATCH("webport"))
p_config->webport = strdup(value);
else if (MATCH("ssl"))
if (strcmp(value, "true") == 0)
p_config->ssl = true;
else
p_config->ssl = false;
else if (MATCH("sslport"))
p_config->sslport = strdup(value);
else if (MATCH("sslcert"))
p_config->sslcert = strdup(value);
else if (MATCH("sslkey"))
p_config->sslkey = strdup(value);
else if (MATCH("user"))
p_config->user = strdup(value);
else if (MATCH("streamport"))
p_config->streamport = strtol(value, &crap, 10);
else if (MATCH("coverimage"))
if (strcmp(value, "true") == 0)
p_config->coverimage = true;
else
p_config->coverimage = false;
else if (MATCH("coverimagename"))
p_config->coverimagename = strdup(value);
else if (MATCH("coverimagesize"))
p_config->coverimagesize = strtol(value, &crap, 10);
else if (MATCH("varlibdir"))
p_config->varlibdir = strdup(value);
else if (MATCH("stickers"))
if (strcmp(value, "true") == 0)
p_config->stickers = true;
else
p_config->stickers = false;
else if (MATCH("smartpls"))
if (strcmp(value, "true") == 0)
p_config->smartpls = true;
else
p_config->smartpls = false;
else if (MATCH("mixramp"))
if (strcmp(value, "true") == 0)
p_config->mixramp = true;
else
p_config->mixramp = false;
else if (MATCH("taglist"))
p_config->taglist = strdup(value);
else if (MATCH("searchtaglist"))
p_config->searchtaglist = strdup(value);
else if (MATCH("browsetaglist"))
p_config->browsetaglist = strdup(value);
else if (MATCH("max_elements_per_page")) {
p_config->max_elements_per_page = strtol(value, &crap, 10);
if (p_config->max_elements_per_page > MAX_ELEMENTS_PER_PAGE) {
printf("Setting max_elements_per_page to maximal value %d", MAX_ELEMENTS_PER_PAGE);
p_config->max_elements_per_page = MAX_ELEMENTS_PER_PAGE;
}
}
else if (MATCH("syscmds"))
if (strcmp(value, "true") == 0)
p_config->syscmds = true;
else
p_config->syscmds = false;
else if (MATCH("localplayer"))
if (strcmp(value, "true") == 0)
p_config->localplayer = true;
else
p_config->localplayer = false;
else if (MATCH("streamurl"))
p_config->streamurl = strdup(value);
else if (MATCH("last_played_count"))
p_config->last_played_count = strtol(value, &crap, 10);
else if (MATCH("loglevel"))
p_config->loglevel = strtol(value, &crap, 10);
else {
printf("Unkown config line: %s\n", name);
return 0; /* unknown section/name, error */
}
return 1;
}
void read_syscmds() {
DIR *dir;
struct dirent *ent;
char dirname[400];
char *cmd;
long order;
if (config.syscmds == true) {
snprintf(dirname, 400, "%s/syscmds", config.etcdir);
LOG_INFO() printf("Reading syscmds: %s\n", dirname);
if ((dir = opendir (dirname)) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (strncmp(ent->d_name, ".", 1) == 0)
continue;
order = strtol(ent->d_name, &cmd, 10);
if (strcmp(cmd, "") != 0)
list_push(&syscmds, strdup(cmd), order);
}
closedir(dir);
}
}
else
LOG_INFO() printf("Syscmds are disabled\n");
}
void read_statefiles() {
char *crap;
char value[400];
LOG_INFO() printf("Reading states\n");
if (mympd_state_get("notificationWeb", value)) {
if (strcmp(value, "true") == 0)
mympd_state.notificationWeb = true;
else
mympd_state.notificationWeb = false;
}
else {
mympd_state.notificationWeb = false;
mympd_state_set("notificationWeb", "false");
}
if (mympd_state_get("notificationPage", value)) {
if (strcmp(value, "true") == 0)
mympd_state.notificationPage = true;
else
mympd_state.notificationPage = false;
}
else {
mympd_state.notificationPage = true;
mympd_state_set("notificationPage", "true");
}
if (mympd_state_get("jukeboxMode", value))
mympd_state.jukeboxMode = strtol(value, &crap, 10);
else {
mympd_state.jukeboxMode = 0;
mympd_state_set("jukeboxMode", "0");
}
if (mympd_state_get("jukeboxPlaylist", value))
mympd_state.jukeboxPlaylist = strdup(value);
else {
mympd_state.jukeboxPlaylist = strdup("Database");
mympd_state_set("jukeboxPlaylist", "Database");
}
if (mympd_state_get("jukeboxQueueLength", value))
mympd_state.jukeboxQueueLength = strtol(value, &crap, 10);
else {
mympd_state.jukeboxQueueLength = 1;
mympd_state_set("jukeboxQueueLength", "1");
}
if (mympd_state_get("colsQueueCurrent", value))
mympd_state.colsQueueCurrent = strdup(value);
else {
mympd_state.colsQueueCurrent = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsQueueCurrent", mympd_state.colsQueueCurrent);
}
if (mympd_state_get("colsSearch", value))
mympd_state.colsSearch = strdup(value);
else {
mympd_state.colsSearch = strdup("[\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsSearch", mympd_state.colsSearch);
}
if (mympd_state_get("colsBrowseDatabase", value))
mympd_state.colsBrowseDatabase = strdup(value);
else {
mympd_state.colsBrowseDatabase = strdup("[\"Track\",\"Title\",\"Duration\"]");
mympd_state_set("colsBrowseDatabase", mympd_state.colsBrowseDatabase);
}
if (mympd_state_get("colsBrowsePlaylistsDetail", value))
mympd_state.colsBrowsePlaylistsDetail = strdup(value);
else {
mympd_state.colsBrowsePlaylistsDetail = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsBrowsePlaylistsDetail", mympd_state.colsBrowsePlaylistsDetail);
}
if (mympd_state_get("colsBrowseFilesystem", value))
mympd_state.colsBrowseFilesystem = strdup(value);
else {
mympd_state.colsBrowseFilesystem = strdup("[\"Type\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsBrowseFilesystem", mympd_state.colsBrowseFilesystem);
}
if (mympd_state_get("colsPlayback", value))
mympd_state.colsPlayback = strdup(value);
else {
mympd_state.colsPlayback = strdup("[\"Artist\",\"Album\",\"Genre\"]");
mympd_state_set("colsPlayback", mympd_state.colsPlayback);
}
if (mympd_state_get("colsQueueLastPlayed", value))
mympd_state.colsQueueLastPlayed = strdup(value);
else {
mympd_state.colsQueueLastPlayed = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"LastPlayed\"]");
mympd_state_set("colsQueueLastPlayed", mympd_state.colsQueueLastPlayed);
}
}
int read_last_played() {
char cfgfile[400];
char *line;
char *data;
size_t n = 0;
ssize_t read;
long value;
snprintf(cfgfile, 400, "%s/state/last_played", config.varlibdir);
FILE *fp = fopen(cfgfile, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfgfile);
return 0;
}
while ((read = getline(&line, &n, fp)) > 0) {
value = strtol(line, &data, 10);
if (strlen(data) > 2)
data = data + 2;
strtok(data, "\n");
list_push(&last_played, data, value);
}
fclose(fp);
return last_played.length;;
}
bool testdir(char *name, char *dirname) {
DIR* dir = opendir(dirname);
if (dir) {
closedir(dir);
LOG_INFO() printf("%s: \"%s\"\n", name, dirname);
return true;
}
else {
printf("%s: \"%s\" don't exists\n", name, dirname);
return false;
}
}
int main(int argc, char **argv) {
struct mg_mgr mgr;
struct mg_connection *nc;
struct mg_connection *nc_http;
struct mg_bind_opts bind_opts;
const char *err;
char testdirname[400];
//defaults
config.mpdhost = "127.0.0.1";
config.mpdport = 6600;
config.mpdpass = NULL;
config.webport = "80";
config.ssl = true;
config.sslport = "443";
config.sslcert = "/etc/mympd/ssl/server.pem";
config.sslkey = "/etc/mympd/ssl/server.key";
config.user = "mympd";
config.streamport = 8000;
config.streamurl = "";
config.coverimage = true;
config.coverimagename = "folder.jpg";
config.coverimagesize = 240;
config.varlibdir = "/var/lib/mympd";
config.stickers = true;
config.mixramp = true;
config.taglist = "Artist,Album,AlbumArtist,Title,Track,Genre,Date,Composer,Performer";
config.searchtaglist = "Artist,Album,AlbumArtist,Title,Genre,Composer,Performer";
config.browsetaglist = "Artist,Album,AlbumArtist,Genre,Composer,Performer";
config.smartpls = true;
config.max_elements_per_page = 100;
config.last_played_count = 20;
char *etcdir = strdup(argv[1]);
config.etcdir = dirname(etcdir);
config.syscmds = false;
config.localplayer = true;
config.loglevel = 1;
mpd.timeout = 3000;
mpd.last_update_sticker_song_id = -1;
mpd.last_song_id = -1;
mpd.last_last_played_id = -1;
mpd.feat_library = false;
if (argc == 2) {
LOG_INFO() printf("Starting myMPD %s\n", MYMPD_VERSION);
LOG_INFO() printf("Libmpdclient %i.%i.%i\n", LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION, LIBMPDCLIENT_PATCH_VERSION);
LOG_INFO() printf("Parsing config file: %s\n", argv[1]);
if (ini_parse(argv[1], inihandler, &config) < 0) {
printf("Can't load config file \"%s\"\n", argv[1]);
return EXIT_FAILURE;
}
}
else {
printf("myMPD %s\n"
"Copyright (C) 2018 Juergen Mang <mail@jcgames.de>\n"
"https://github.com/jcorporation/myMPD\n"
"Built " __DATE__ " "__TIME__"\n\n"
"Usage: %s /path/to/mympd.conf\n",
MYMPD_VERSION,
argv[0]
);
return EXIT_FAILURE;
}
#ifdef DEBUG
printf("Debug flag enabled, setting loglevel to debug\n");
config.loglevel = 3;
#endif
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
mg_mgr_init(&mgr, NULL);
if (config.ssl == true) {
nc_http = mg_bind(&mgr, config.webport, ev_handler_http);
if (nc_http == NULL) {
printf("Error listening on port %s\n", config.webport);
return EXIT_FAILURE;
}
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.ssl_cert = config.sslcert;
bind_opts.ssl_key = config.sslkey;
bind_opts.error_string = &err;
nc = mg_bind_opt(&mgr, config.sslport, ev_handler, bind_opts);
if (nc == NULL) {
printf("Error listening on port %s: %s\n", config.sslport, err);
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
}
else {
nc = mg_bind(&mgr, config.webport, ev_handler);
if (nc == NULL) {
printf("Error listening on port %s\n", config.webport);
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
}
if (config.user != NULL) {
LOG_INFO() printf("Droping privileges to %s\n", config.user);
struct passwd *pw;
if ((pw = getpwnam(config.user)) == NULL) {
printf("getpwnam() failed, unknown user\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
} else if (setgroups(0, NULL) != 0) {
printf("setgroups() failed\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
} else if (setgid(pw->pw_gid) != 0) {
printf("setgid() failed\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
} else if (setuid(pw->pw_uid) != 0) {
printf("setuid() failed\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
}
if (getuid() == 0) {
printf("myMPD should not be run with root privileges\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
if (!testdir("Document root", SRC_PATH))
return EXIT_FAILURE;
snprintf(testdirname, 400, "%s/library", SRC_PATH);
if (testdir("Link to mpd music_directory", testdirname)) {
LOG_INFO() printf("Enabling featLibrary support\n");
mpd.feat_library = true;
}
else {
LOG_INFO() printf("Disabling coverimage support\n");
config.coverimage = false;
}
snprintf(testdirname, 400, "%s/tmp", config.varlibdir);
if (!testdir("Temp dir", testdirname))
return EXIT_FAILURE;
snprintf(testdirname, 400, "%s/smartpls", config.varlibdir);
if (!testdir("Smartpls dir", testdirname))
return EXIT_FAILURE;
snprintf(testdirname, 400, "%s/state", config.varlibdir);
if (!testdir("State dir", testdirname))
return EXIT_FAILURE;
read_statefiles();
list_init(&syscmds);
read_syscmds();
list_sort_by_value(&syscmds, true);
list_init(&mpd_tags);
list_init(&mympd_tags);
list_init(&last_played);
LOG_INFO() printf("Reading last played songs: %d\n", read_last_played());
if (config.ssl == true)
mg_set_protocol_http_websocket(nc_http);
mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = SRC_PATH;
s_http_server_opts.enable_directory_listing = "no";
LOG_INFO() printf("Listening on http port %s\n", config.webport);
if (config.ssl == true)
LOG_INFO() printf("Listening on ssl port %s\n", config.sslport);
while (s_signal_received == 0) {
mg_mgr_poll(&mgr, 100);
mympd_idle(&mgr, 0);
}
mg_mgr_free(&mgr);
list_free(&mpd_tags);
list_free(&mympd_tags);
mympd_disconnect();
return EXIT_SUCCESS;
}

520
src/mympd_api.c Normal file
View File

@ -0,0 +1,520 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <libgen.h>
#include <dirent.h>
#include <stdbool.h>
#include "list.h"
#include "tiny_queue.h"
#include "global.h"
#include "mympd_api.h"
#include "mpd_client.h"
#include "../dist/src/frozen/frozen.h"
//private definitions
typedef struct t_mympd_state {
//notifications
bool notificationWeb;
bool notificationPage;
//jukebox
enum jukebox_modes jukeboxMode;
char *jukeboxPlaylist;
int jukeboxQueueLength;
bool autoPlay;
//columns
char *colsQueueCurrent;
char *colsSearch;
char *colsBrowseDatabase;
char *colsBrowsePlaylistsDetail;
char *colsBrowseFilesystem;
char *colsPlayback;
char *colsQueueLastPlayed;
//system commands
struct list syscmd_list;
} t_mympd_state;
static void mympd_api(t_config *config, t_mympd_state *mympd_state, t_work_request *request);
static bool mympd_api_read_syscmds(t_config *config, t_mympd_state *mympd_state);
static int mympd_api_syscmd(t_config *config, t_mympd_state *mympd_state, char *buffer, const char *cmd);
static void mympd_api_read_statefiles(t_config *config, t_mympd_state *mympd_state);
static char *state_file_rw_string(t_config *config, const char *name, const char *def_value);
static bool state_file_rw_bool(t_config *config, const char *name, const bool def_value);
static long state_file_rw_long(t_config *config, const char *name, const long def_value);
static bool state_file_write(t_config *config, const char *name, const char *value);
static int mympd_api_put_settings(t_config *config, t_mympd_state *mympd_state, char *buffer);
//public functions
void *mympd_api_loop(void *arg_config) {
t_config *config = (t_config *) arg_config;
//read myMPD states under config.varlibdir
t_mympd_state mympd_state;
mympd_api_read_statefiles(config, &mympd_state);
//push jukebox settings to mpd_client queue
t_work_request *mpd_client_request = (t_work_request *)malloc(sizeof(t_work_request));
mpd_client_request->conn_id = 0;
mpd_client_request->cmd_id = MYMPD_API_SETTINGS_SET;
mpd_client_request->length = snprintf(mpd_client_request->data, 1000,
"{\"cmd\":\"MYMPD_API_SETTINGS_SET\", \"data\":{\"jukeboxMode\": %d, \"jukeboxPlaylist\": \"%s\", \"jukeboxQueueLength\": %d}}",
mympd_state.jukeboxMode,
mympd_state.jukeboxPlaylist,
mympd_state.jukeboxQueueLength
);
tiny_queue_push(mpd_client_queue, mpd_client_request);
//read system command files
list_init(&mympd_state.syscmd_list);
bool rc = mympd_api_read_syscmds(config, &mympd_state);
if (rc == true) {
list_sort_by_value(&mympd_state.syscmd_list, true);
}
while (s_signal_received == 0) {
struct t_work_request *request = tiny_queue_shift(mympd_api_queue, 0);
if (request != NULL) {
mympd_api(config, &mympd_state, request);
}
}
list_free(&mympd_state.syscmd_list);
free(mympd_state.jukeboxPlaylist);
free(mympd_state.colsQueueCurrent);
free(mympd_state.colsSearch);
free(mympd_state.colsBrowseDatabase);
free(mympd_state.colsBrowsePlaylistsDetail);
free(mympd_state.colsBrowseFilesystem);
free(mympd_state.colsPlayback);
free(mympd_state.colsQueueLastPlayed);
return NULL;
}
//private functions
static void mympd_api(t_config *config, t_mympd_state *mympd_state, t_work_request *request) {
//t_work_request *request = (t_work_request *) arg_request;
size_t len = 0;
char buffer[MAX_SIZE];
int je;
char *p_charbuf1;
char p_char[4];
LOG_VERBOSE() printf("MYMPD API request: %.*s\n", request->length, request->data);
if (request->cmd_id == MYMPD_API_SYSCMD) {
if (config->syscmds == true) {
je = json_scanf(request->data, request->length, "{data: {cmd: %Q}}", &p_charbuf1);
if (je == 1) {
len = mympd_api_syscmd(config, mympd_state, buffer, p_charbuf1);
free(p_charbuf1);
}
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"System commands are disabled.\"}");
}
}
else if (request->cmd_id == MYMPD_API_COLS_SAVE) {
je = json_scanf(request->data, request->length, "{data: {table: %Q}}", &p_charbuf1);
if (je == 1) {
char column_list[800];
snprintf(column_list, 800, "%.*s", request->length, request->data);
char *cols = strchr(column_list, '[');
int col_len = strlen(cols);
if (col_len > 1)
cols[col_len - 2] = '\0';
if (strcmp(p_charbuf1, "colsQueueCurrent") == 0) {
free(mympd_state->colsQueueCurrent);
mympd_state->colsQueueCurrent = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsSearch") == 0) {
free(mympd_state->colsSearch);
mympd_state->colsSearch = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsBrowseDatabase") == 0) {
free(mympd_state->colsBrowseDatabase);
mympd_state->colsBrowseDatabase = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsBrowsePlaylistsDetail") == 0) {
free(mympd_state->colsBrowsePlaylistsDetail);
mympd_state->colsBrowsePlaylistsDetail = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsBrowseFilesystem") == 0) {
free(mympd_state->colsBrowseFilesystem);
mympd_state->colsBrowseFilesystem = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsPlayback") == 0) {
free(mympd_state->colsPlayback);
mympd_state->colsPlayback = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsQueueLastPlayed") == 0) {
free(mympd_state->colsQueueLastPlayed);
mympd_state->colsQueueLastPlayed = strdup(cols);
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown table %s\"}", p_charbuf1);
printf("MYMPD_API_COLS_SAVE: Unknown table %s\n", p_charbuf1);
}
if (len == 0) {
if (state_file_write(config, p_charbuf1, cols))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
}
free(p_charbuf1);
}
}
else if (request->cmd_id == MYMPD_API_SETTINGS_SET) {
je = json_scanf(request->data, request->length, "{data: {notificationWeb: %B}}", &mympd_state->notificationWeb);
if (je == 1) {
if (!state_file_write(config, "notificationWeb", (mympd_state->notificationWeb == true ? "true" : "false")))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationWeb.\"}");
}
je = json_scanf(request->data, request->length, "{data: {notificationPage: %B}}", &mympd_state->notificationPage);
if (je == 1) {
if (!state_file_write(config, "notificationPage", (mympd_state->notificationPage == true ? "true" : "false")))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationPage.\"}");
}
je = json_scanf(request->data, request->length, "{data: {autoPlay: %B}}", &mympd_state->autoPlay);
if (je == 1) {
if (!state_file_write(config, "autoPlay", (mympd_state->autoPlay == true ? "true" : "false")))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state autoPlay.\"}");
}
je = json_scanf(request->data, request->length, "{data: {jukeboxMode: %d}}", &mympd_state->jukeboxMode);
if (je == 1) {
snprintf(p_char, 4, "%d", mympd_state->jukeboxMode);
if (!state_file_write(config, "jukeboxMode", p_char))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxMode.\"}");
}
je = json_scanf(request->data, request->length, "{data: {jukeboxPlaylist: %Q}}", &p_charbuf1);
if (je == 1) {
free(mympd_state->jukeboxPlaylist);
mympd_state->jukeboxPlaylist = p_charbuf1;
p_charbuf1 = NULL;
if (!state_file_write(config, "jukeboxPlaylist", mympd_state->jukeboxPlaylist))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxPlaylist.\"}");
}
je = json_scanf(request->data, request->length, "{data: {jukeboxQueueLength: %d}}", &mympd_state->jukeboxQueueLength);
if (je == 1) {
snprintf(p_char, 4, "%d", mympd_state->jukeboxQueueLength);
if (!state_file_write(config, "jukeboxQueueLength", p_char))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxQueueLength.\"}");
}
if (len == 0) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
}
//push settings to mpd_client queue
t_work_request *mpd_client_request = (t_work_request *)malloc(sizeof(t_work_request));
mpd_client_request->conn_id = request->conn_id;
mpd_client_request->cmd_id = request->cmd_id;
mpd_client_request->length = copy_string(mpd_client_request->data, request->data, 1000, request->length);
tiny_queue_push(mpd_client_queue, mpd_client_request);
}
else if (request->cmd_id == MYMPD_API_SETTINGS_GET) {
len = mympd_api_put_settings(config, mympd_state, buffer);
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown cmd_id %u.\"}", request->cmd_id);
printf("ERROR: Unknown cmd_id %u\n", request->cmd_id);
}
if (len == 0) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"No response for cmd_id %u.\"}", request->cmd_id);
printf("ERROR: No response for cmd_id %u\n", request->cmd_id);
}
LOG_DEBUG() fprintf(stderr, "DEBUG: Send http response to connection %lu (first 800 chars):\n%*.*s\n", request->conn_id, 0, 800, buffer);
t_work_result *response = (t_work_result *)malloc(sizeof(t_work_result));
response->conn_id = request->conn_id;
response->length = copy_string(response->data, buffer, MAX_SIZE, len);
tiny_queue_push(web_server_queue, response);
free(request);
}
static bool mympd_api_read_syscmds(t_config *config, t_mympd_state *mympd_state) {
DIR *dir;
struct dirent *ent;
char dirname[400];
char *cmd;
long order;
if (config->syscmds == true) {
snprintf(dirname, 400, "%s/syscmds", config->etcdir);
printf("Reading syscmds: %s\n", dirname);
if ((dir = opendir (dirname)) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (strncmp(ent->d_name, ".", 1) == 0)
continue;
order = strtol(ent->d_name, &cmd, 10);
if (strcmp(cmd, "") != 0) {
list_push(&mympd_state->syscmd_list, cmd, order);
}
else {
printf("ERROR: Can't read syscmd file %s\n", ent->d_name);
}
}
closedir(dir);
}
else {
printf("ERROR: Can't read syscmds\n");
}
}
else {
printf("Syscmds are disabled\n");
}
return true;
}
static int mympd_api_syscmd(t_config *config, t_mympd_state *mympd_state, char *buffer, const char *cmd) {
int len;
char filename[400];
char *line = NULL;
char *crap;
size_t n = 0;
ssize_t read;
const int order = list_get_value(&mympd_state->syscmd_list, cmd);
if (order == -1) {
printf("ERROR: Syscmd not defined: %s\n", cmd);
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"System command not defined\"}");
return len;
}
snprintf(filename, 400, "%s/syscmds/%d%s", config->etcdir, order, cmd);
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't execute cmd %s.\"}", cmd);
printf("ERROR: Can't execute syscmd \"%s\"\n", cmd);
return len;
}
read = getline(&line, &n, fp);
fclose(fp);
if (read > 0) {
strtok_r(line, "\n", &crap);
const int rc = system(line);
if ( rc == 0) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Executed cmd %s.\"}", cmd);
LOG_VERBOSE() printf("Executed syscmd: \"%s\"\n", line);
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Executing cmd %s failed.\"}", cmd);
printf("ERROR: Executing syscmd \"%s\" failed.\n", cmd);
}
} else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't execute cmd %s.\"}", cmd);
printf("ERROR: Can't execute syscmd \"%s\"\n", cmd);
}
free(line);
CHECK_RETURN_LEN();
}
static void mympd_api_read_statefiles(t_config *config, t_mympd_state *mympd_state) {
LOG_INFO() printf("Reading states\n");
mympd_state->notificationWeb = state_file_rw_bool(config, "notificationWeb", false);
mympd_state->notificationPage = state_file_rw_bool(config, "notificationPage", true);
mympd_state->autoPlay = state_file_rw_bool(config, "autoPlay", false);
mympd_state->jukeboxMode = state_file_rw_long(config, "jukeboxMode", JUKEBOX_OFF);
mympd_state->jukeboxPlaylist = state_file_rw_string(config, "jukeboxPlaylist", "Database");
mympd_state->jukeboxQueueLength = state_file_rw_long(config, "jukeboxQueueLength", 1);
mympd_state->colsQueueCurrent = state_file_rw_string(config, "colsQueueCurrent", "[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsSearch = state_file_rw_string(config, "colsSearch", "[\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsBrowseDatabase = state_file_rw_string(config, "colsBrowseDatabase", "[\"Track\",\"Title\",\"Duration\"]");
mympd_state->colsBrowsePlaylistsDetail = state_file_rw_string(config, "colsBrowsePlaylistsDetail", "[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsBrowseFilesystem = state_file_rw_string(config, "colsBrowseFilesystem", "[\"Type\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsPlayback = state_file_rw_string(config, "colsPlayback", "[\"Artist\",\"Album\"]");
mympd_state->colsQueueLastPlayed = state_file_rw_string(config, "colsQueueLastPlayed", "[\"Pos\",\"Title\",\"Artist\",\"Album\",\"LastPlayed\"]");
}
static char *state_file_rw_string(t_config *config, const char *name, const char *def_value) {
char cfg_file[400];
char *line = NULL;
size_t n = 0;
ssize_t read;
if (!validate_string(name)) {
return NULL;
}
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
FILE *fp = fopen(cfg_file, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfg_file);
state_file_write(config, name, def_value);
return NULL;
}
read = getline(&line, &n, fp);
if (read > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: State %s: %s\n", name, line);
}
fclose(fp);
if (read > 0) {
return line;
}
else {
free(line);
return NULL;
}
}
static bool state_file_rw_bool(t_config *config, const char *name, const bool def_value) {
char cfg_file[400];
char *line = NULL;
size_t n = 0;
ssize_t read;
bool value = def_value;
if (!validate_string(name))
return def_value;
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
FILE *fp = fopen(cfg_file, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfg_file);
state_file_write(config, name, def_value == true ? "true" : "false");
return def_value;
}
read = getline(&line, &n, fp);
if (read > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: State %s: %s\n", name, line);
value = strcmp(line, "true") == 0 ? true : false;
}
fclose(fp);
free(line);
return value;
}
static long state_file_rw_long(t_config *config, const char *name, const long def_value) {
char cfg_file[400];
char *line = NULL;
char *crap;
size_t n = 0;
ssize_t read;
long value = def_value;
if (!validate_string(name))
return def_value;
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
FILE *fp = fopen(cfg_file, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfg_file);
char p_value[65];
snprintf(p_value, 65, "%ld", def_value);
state_file_write(config, name, p_value);
return def_value;
}
read = getline(&line, &n, fp);
if (read > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: State %s: %s\n", name, line);
value = strtol(line, &crap, 10);
}
fclose(fp);
free(line);
return value;
}
static bool state_file_write(t_config *config, const char *name, const char *value) {
char tmp_file[400];
char cfg_file[400];
if (!validate_string(name))
return false;
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
snprintf(tmp_file, 400, "%s/tmp/%s", config->varlibdir, name);
FILE *fp = fopen(tmp_file, "w");
if (fp == NULL) {
printf("Error opening %s\n", tmp_file);
return false;
}
fprintf(fp, "%s", value);
fclose(fp);
if (rename(tmp_file, cfg_file) == -1) {
printf("Error renaming file from %s to %s\n", tmp_file, cfg_file);
return false;
}
return true;
}
static int mympd_api_put_settings(t_config *config, t_mympd_state *mympd_state, char *buffer) {
size_t len;
int nr = 0;
struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE);
len = json_printf(&out, "{type: mympdSettings, data: {mpdhost: %Q, mpdport: %d, passwort_set: %B, featSyscmds: %B, "
"featLocalplayer: %B, streamport: %d, streamurl: %Q, featCoverimage: %B, coverimagename: %Q, coverimagesize: %d, featMixramp: %B, "
"maxElementsPerPage: %d, notificationWeb: %B, notificationPage: %B, jukeboxMode: %d, jukeboxPlaylist: %Q, jukeboxQueueLength: %d, "
"autoPlay: %B, backgroundcolor: %Q",
config->mpdhost,
config->mpdport,
config->mpdpass ? "true" : "false",
config->syscmds,
config->localplayer,
config->streamport,
config->streamurl,
config->coverimage,
config->coverimagename,
config->coverimagesize,
config->mixramp,
config->max_elements_per_page,
mympd_state->notificationWeb,
mympd_state->notificationPage,
mympd_state->jukeboxMode,
mympd_state->jukeboxPlaylist,
mympd_state->jukeboxQueueLength,
mympd_state->autoPlay,
config->backgroundcolor
);
if (config->syscmds == true) {
len += json_printf(&out, ", syscmdList: [");
nr = 0;
struct node *current = mympd_state->syscmd_list.list;
while (current != NULL) {
if (nr++)
len += json_printf(&out, ",");
len += json_printf(&out, "%Q", current->data);
current = current->next;
}
len += json_printf(&out, "]");
}
len += json_printf(&out, ", colsQueueCurrent: %s, colsSearch: %s, colsBrowseDatabase: %s, colsBrowsePlaylistsDetail: %s, "
"colsBrowseFilesystem: %s, colsPlayback: %s, colsQueueLastPlayed: %s}}",
mympd_state->colsQueueCurrent,
mympd_state->colsSearch,
mympd_state->colsBrowseDatabase,
mympd_state->colsBrowsePlaylistsDetail,
mympd_state->colsBrowseFilesystem,
mympd_state->colsPlayback,
mympd_state->colsQueueLastPlayed
);
CHECK_RETURN_LEN();
}

View File

@ -1,5 +1,5 @@
/* myMPD /* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de> (c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of: myMPD ist fork of:
@ -22,5 +22,9 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
void sanitize_string(const char *data); #ifndef __MYMPD_API_H__
int validate_path(char *path, const char *basepath); #define __MYMPD_API_H__
void *mympd_api_loop(void *arg_config);
#endif

179
src/tiny_queue.c Normal file
View File

@ -0,0 +1,179 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <pthread.h>
#include <asm/errno.h>
#include "tiny_queue.h"
tiny_queue_t *tiny_queue_create(void) {
struct tiny_queue_t* queue = (struct tiny_queue_t*)malloc(sizeof(struct tiny_queue_t));
queue->head = NULL;
queue->tail = NULL;
queue->length = 0;
queue->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
queue->wakeup = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
return queue;
}
void tiny_queue_free(tiny_queue_t *queue) {
struct tiny_msg_t *current_head = queue->head, *tmp = NULL;
while (current_head != NULL) {
free(current_head->data);
tmp = current_head;
current_head = current_head->next;
free(tmp);
}
free(queue);
}
int tiny_queue_push(tiny_queue_t *queue, void *data) {
int rc = pthread_mutex_lock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_lock: %d\n", rc);
return 0;
}
struct tiny_msg_t* new_node = (struct tiny_msg_t*)malloc(sizeof(struct tiny_msg_t));
new_node->data = data;
new_node->next = NULL;
queue->length++;
if (queue->head == NULL && queue->tail == NULL){
queue->head = queue->tail = new_node;
}
else {
queue->tail->next = new_node;
queue->tail = new_node;
}
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
return 0;
}
rc = pthread_cond_signal(&queue->wakeup);
if (rc != 0) {
printf("Error in pthread_cond_signal: %d\n", rc);
return 0;
}
return 1;
}
int tiny_queue_length(tiny_queue_t *queue, int timeout) {
timeout = timeout * 1000;
int rc = pthread_mutex_lock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_lock: %d\n", rc);
return 0;
}
if (timeout > 0 && queue->length == 0) {
struct timespec max_wait = {0, 0};
clock_gettime(CLOCK_REALTIME, &max_wait);
//timeout in ms
if (max_wait.tv_nsec <= (999999999 - timeout)) {
max_wait.tv_nsec += timeout;
} else {
max_wait.tv_sec += 1;
max_wait.tv_nsec = timeout - (999999999 - max_wait.tv_nsec);
}
rc = pthread_cond_timedwait(&queue->wakeup, &queue->mutex, &max_wait);
if (rc != 0 && rc != ETIMEDOUT) {
printf("Error in pthread_cond_timedwait: %d\n", rc);
}
}
unsigned len = queue->length;
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return len;
}
void *tiny_queue_shift(tiny_queue_t *queue, int timeout) {
timeout = timeout * 1000;
int rc = pthread_mutex_lock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_lock: %d\n", rc);
return 0;
}
if (queue->length == 0) {
if (timeout > 0) {
struct timespec max_wait = {0, 0};
clock_gettime(CLOCK_REALTIME, &max_wait);
//timeout in ms
if (max_wait.tv_nsec <= (999999999 - timeout)) {
max_wait.tv_nsec += timeout;
} else {
max_wait.tv_sec += 1;
max_wait.tv_nsec = timeout - (999999999 - max_wait.tv_nsec);
}
rc = pthread_cond_timedwait(&queue->wakeup, &queue->mutex, &max_wait);
if (rc != 0) {
if (rc != ETIMEDOUT) {
printf("Error in pthread_cond_timedwait: %d\n", rc);
printf("nsec: %ld\n", max_wait.tv_nsec);
}
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return NULL;
}
}
else {
rc = pthread_cond_wait(&queue->wakeup, &queue->mutex);
if (rc != 0) {
printf("Error in pthread_cond_wait: %d\n", rc);
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return NULL;
}
}
}
//queue has entry
if (queue->head != NULL) {
struct tiny_msg_t* current_head = queue->head;
void *data = current_head->data;
if (queue->head == queue->tail) {
queue->head = queue->tail = NULL;
}
else {
queue->head = queue->head->next;
}
free(current_head);
queue->length--;
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return data;
}
else {
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return NULL;
}
}

41
src/tiny_queue.h Normal file
View File

@ -0,0 +1,41 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __TINY_QUEUE_H__
#define __TINY_QUEUE_H__
typedef struct tiny_msg_t {
void *data;
struct tiny_msg_t *next;
} tiny_msg_t;
typedef struct tiny_queue_t {
unsigned length;
struct tiny_msg_t *head;
struct tiny_msg_t *tail;
pthread_mutex_t mutex;
pthread_cond_t wakeup;
} tiny_queue_t;
tiny_queue_t *tiny_queue_create(void);
void tiny_queue_free(tiny_queue_t *queue);
int tiny_queue_push(struct tiny_queue_t *queue, void *data);
void *tiny_queue_shift(struct tiny_queue_t *queue, int timeout);
int tiny_queue_length(struct tiny_queue_t *queue, int timeout);
#endif

View File

@ -1,55 +0,0 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include "validate.h"
void sanitize_string(const char *data) {
static char ok_chars[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"1234567890_-. ";
char *cp = data;
const char *end = data + strlen(data);
for (cp += strspn(cp, ok_chars); cp != end; cp += strspn(cp, ok_chars))
*cp = '_';
}
int validate_path(char *path, const char *basepath) {
char *rpath = NULL;
char *ptr;
ptr = realpath(path, rpath);
if (ptr == NULL)
return 1;
if (strncmp(basepath, ptr, strlen(basepath)) == 0) {
free(rpath);
return 0;
}
else {
free(rpath);
return 1;
}
}

276
src/web_server.c Normal file
View File

@ -0,0 +1,276 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <limits.h>
#include <stdbool.h>
#include <pthread.h>
#include "list.h"
#include "tiny_queue.h"
#include "global.h"
#include "web_server.h"
#include "mpd_client.h"
#include "../dist/src/mongoose/mongoose.h"
#include "../dist/src/frozen/frozen.h"
//private definitions
static int is_websocket(const struct mg_connection *nc);
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data);
static void ev_handler_redirect(struct mg_connection *nc_http, int ev, void *ev_data);
static void send_ws_notify(struct mg_mgr *mgr, t_work_result *response);
static void send_api_response(struct mg_mgr *mgr, t_work_result *response);
static bool handle_api(long conn_id, const char *request, int request_len);
typedef struct t_user_data {
void *config; //pointer to mympd config
long conn_id;
} t_user_data;
//public functions
bool web_server_init(void *arg_mgr, t_config *config) {
struct mg_mgr *mgr = (struct mg_mgr *) arg_mgr;
struct mg_connection *nc_https;
struct mg_connection *nc_http;
struct mg_bind_opts bind_opts_https;
struct mg_bind_opts bind_opts_http;
const char *err_https;
const char *err_http;
t_user_data *user_data = (t_user_data*)malloc(sizeof(t_user_data));
user_data->config = config;
user_data->conn_id = 1;
mg_mgr_init(mgr, NULL);
//bind to webport
memset(&bind_opts_http, 0, sizeof(bind_opts_http));
bind_opts_http.user_data = (void *)user_data;
bind_opts_http.error_string = &err_http;
if (config->ssl == true)
nc_http = mg_bind_opt(mgr, config->webport, ev_handler_redirect, bind_opts_http);
else
nc_http = mg_bind_opt(mgr, config->webport, ev_handler, bind_opts_http);
if (nc_http == NULL) {
printf("Error listening on port %s\n", config->webport);
mg_mgr_free(mgr);
return false;
}
mg_set_protocol_http_websocket(nc_http);
LOG_INFO() printf("Listening on http port %s.\n", config->webport);
//bind to sslport
if (config->ssl == true) {
memset(&bind_opts_https, 0, sizeof(bind_opts_https));
bind_opts_https.user_data = (void *)user_data;
bind_opts_https.error_string = &err_https;
bind_opts_https.ssl_cert = config->sslcert;
bind_opts_https.ssl_key = config->sslkey;
nc_https = mg_bind_opt(mgr, config->sslport, ev_handler, bind_opts_https);
if (nc_https == NULL) {
printf("Error listening on port %s: %s\n", config->sslport, err_https);
mg_mgr_free(mgr);
return false;
}
LOG_INFO() printf("Listening on ssl port %s\n", config->sslport);
mg_set_protocol_http_websocket(nc_https);
}
return mgr;
}
void web_server_free(void *arg_mgr) {
struct mg_mgr *mgr = (struct mg_mgr *) arg_mgr;
mg_mgr_free(mgr);
}
void *web_server_loop(void *arg_mgr) {
struct mg_mgr *mgr = (struct mg_mgr *) arg_mgr;
while (s_signal_received == 0) {
mg_mgr_poll(mgr, 50);
// unsigned web_server_queue_length = tiny_queue_length(web_server_queue, 100);
// if (web_server_queue_length > 0) {
t_work_result *response = tiny_queue_shift(web_server_queue, 50);
if (response != NULL) {
if (response->conn_id == 0) {
//Websocket notify from mpd idle
send_ws_notify(mgr, response);
}
else {
//api response
send_api_response(mgr, response);
}
}
// }
}
return NULL;
}
//private functions
static int is_websocket(const struct mg_connection *nc) {
return nc->flags & MG_F_IS_WEBSOCKET;
}
static void send_ws_notify(struct mg_mgr *mgr, t_work_result *response) {
struct mg_connection *nc;
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
if (!is_websocket(nc))
continue;
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, response->data, response->length);
}
free(response);
}
static void send_api_response(struct mg_mgr *mgr, t_work_result *response) {
struct mg_connection *nc;
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
if (nc->user_data != NULL) {
t_user_data *user_data = (t_user_data *) nc->user_data;
if (user_data->conn_id == response->conn_id) {
mg_send_head(nc, 200, response->length, "Content-Type: application/json");
mg_printf(nc, "%s", response->data);
}
}
}
free(response);
}
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
t_user_data *user_data = (t_user_data *) nc->user_data;
t_config *config = (t_config *) user_data->config;
switch(ev) {
case MG_EV_ACCEPT: {
//increment conn_id
if (user_data->conn_id < LONG_MAX)
user_data->conn_id++;
else
user_data->conn_id = 1;
//replace mgr user_data with connection specific user_data
t_user_data *nc_user_data = (t_user_data*)malloc(sizeof(t_user_data));
nc_user_data->config = config;
nc_user_data->conn_id = user_data->conn_id;
nc->user_data = nc_user_data;
LOG_DEBUG() fprintf(stderr, "DEBUG: New connection id %ld.\n", user_data->conn_id);
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("New websocket request (%ld): %.*s\n", user_data->conn_id, (int)hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/ws") != 0) {
printf("ERROR: Websocket request not to /ws, closing connection\n");
mg_printf(nc, "%s", "HTTP/1.1 403 FORBIDDEN\r\n\r\n");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
LOG_VERBOSE() printf("New Websocket connection established (%ld).\n", user_data->conn_id);
char response[] = "{\"type\": \"welcome\", \"data\": {\"mympdVersion\": \"" MYMPD_VERSION "\"}}";
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, response, strlen(response));
break;
}
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("HTTP request (%ld): %.*s\n", user_data->conn_id, (int)hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/api") == 0) {
bool rc = handle_api(user_data->conn_id, hm->body.p, hm->body.len);
if (rc == false) {
printf("ERROR: Invalid API request.\n");
const char *response = "{\"type\": \"error\", \"data\": \"Invalid API request\"}";
mg_send_head(nc, 200, strlen(response), "Content-Type: application/json");
mg_printf(nc, "%s", response);
}
}
else {
static struct mg_serve_http_opts s_http_server_opts;
s_http_server_opts.document_root = DOC_ROOT;
s_http_server_opts.enable_directory_listing = "no";
mg_serve_http(nc, hm, s_http_server_opts);
}
break;
}
case MG_EV_CLOSE: {
LOG_VERBOSE() fprintf(stderr, "HTTP connection %ld closed.\n", user_data->conn_id);
free(nc->user_data);
break;
}
default: {
break;
}
}
}
static void ev_handler_redirect(struct mg_connection *nc, int ev, void *ev_data) {
char *host;
char *crap;
char host_header[1024];
switch(ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
t_user_data *user_data = (t_user_data *) nc->user_data;
t_config *config = (t_config *) user_data->config;
snprintf(host_header, 1024, "%.*s", (int)host_hdr->len, host_hdr->p);
host = strtok_r(host_header, ":", &crap);
char s_redirect[250];
if (strcmp(config->sslport, "443") == 0)
snprintf(s_redirect, 250, "https://%s/", host);
else
snprintf(s_redirect, 250, "https://%s:%s/", host, config->sslport);
LOG_VERBOSE() printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
default: {
break;
}
}
}
static bool handle_api(long conn_id, const char *request_body, int request_len) {
char *cmd;
LOG_VERBOSE() printf("API request (%ld): %.*s\n", conn_id, request_len, request_body);
const int je = json_scanf(request_body, request_len, "{cmd: %Q}", &cmd);
if (je < 1)
return false;
enum mympd_cmd_ids cmd_id = get_cmd_id(cmd);
if (cmd_id == 0)
return false;
t_work_request *request = (t_work_request*)malloc(sizeof(t_work_request));
request->conn_id = conn_id;
request->cmd_id = cmd_id;
request->length = copy_string(request->data, request_body, 1000, request_len);
if (strncmp(cmd, "MYMPD_API_", 10) == 0)
tiny_queue_push(mympd_api_queue, request);
else
tiny_queue_push(mpd_client_queue, request);
free(cmd);
return true;
}

View File

@ -1,6 +1,6 @@
/* myMPD /* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de> (c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/ympd This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of: myMPD ist fork of:
@ -22,13 +22,11 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef __CONFIG_H__ #ifndef __WEB_SERVER_H__
#define __CONFIG_H__ #define __WEB_SERVER_H__
void *web_server_loop(void *arg_mgr);
bool web_server_init(void *arg_mgr, t_config *config);
void web_server_free(void *arg_mgr);
#define MYMPD_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}
#define MYMPD_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}
#define MYMPD_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}
#define MYMPD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}"
#define SRC_PATH "${ASSETS_PATH}"
#cmakedefine DEBUG
#endif #endif