1
0
mirror of https://github.com/SuperBFG7/ympd synced 2025-02-10 16:10:16 +00:00

Merge pull request #21 from jcorporation/devel

Enable PWA Feature
This commit is contained in:
Jürgen Mang 2018-07-16 19:55:52 +02:00 committed by GitHub
commit f4640a932c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 599 additions and 260 deletions

View File

@ -3,8 +3,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 "3") set(CPACK_PACKAGE_VERSION_MAJOR "3")
set(CPACK_PACKAGE_VERSION_MINOR "1") set(CPACK_PACKAGE_VERSION_MINOR "2")
set(CPACK_PACKAGE_VERSION_PATCH "1") set(CPACK_PACKAGE_VERSION_PATCH "0")
if(CMAKE_BUILD_TYPE MATCHES RELEASE) if(CMAKE_BUILD_TYPE MATCHES RELEASE)
set(ASSETS_PATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/htdocs") set(ASSETS_PATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/htdocs")
@ -12,7 +12,6 @@ if(CMAKE_BUILD_TYPE MATCHES RELEASE)
else() else()
set(ASSETS_PATH "${PROJECT_SOURCE_DIR}/htdocs") set(ASSETS_PATH "${PROJECT_SOURCE_DIR}/htdocs")
set(DEBUG "ON") set(DEBUG "ON")
set(CS_NDEBUG "ON")
endif() endif()
find_package(LibMPDClient REQUIRED) find_package(LibMPDClient REQUIRED)
@ -23,9 +22,13 @@ include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_I
include(CheckCSourceCompiles) include(CheckCSourceCompiles)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_DISABLE_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 -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_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 -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")
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/mympd.c
src/mpd_client.c src/mpd_client.c
@ -34,12 +37,14 @@ set(SOURCES
) )
add_executable(mympd ${SOURCES}) add_executable(mympd ${SOURCES})
target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES})
install(TARGETS mympd DESTINATION bin) install(TARGETS mympd DESTINATION bin)
install(FILES mympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1) install(FILES contrib/mympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1)
install(FILES htdocs/mympd.webmanifest DESTINATION share/${PROJECT_NAME}/htdocs/)
install(FILES htdocs/index.html DESTINATION share/${PROJECT_NAME}/htdocs/) install(FILES htdocs/index.html DESTINATION share/${PROJECT_NAME}/htdocs/)
install(FILES htdocs/player.html DESTINATION share/${PROJECT_NAME}/htdocs/) install(FILES htdocs/player.html DESTINATION share/${PROJECT_NAME}/htdocs/)
install(FILES htdocs/sw.min.js DESTINATION share/${PROJECT_NAME}/htdocs/)
install(FILES htdocs/js/player.min.js DESTINATION share/${PROJECT_NAME}/htdocs/js/) install(FILES htdocs/js/player.min.js DESTINATION share/${PROJECT_NAME}/htdocs/js/)
install(FILES htdocs/js/bootstrap-native-v4.min.js DESTINATION share/${PROJECT_NAME}/htdocs/js/) install(FILES htdocs/js/bootstrap-native-v4.min.js DESTINATION share/${PROJECT_NAME}/htdocs/js/)
install(FILES htdocs/js/mpd.min.js DESTINATION share/${PROJECT_NAME}/htdocs/js/) install(FILES htdocs/js/mpd.min.js DESTINATION share/${PROJECT_NAME}/htdocs/js/)
@ -47,3 +52,4 @@ install(FILES htdocs/css/bootstrap.min.css DESTINATION share/${PROJECT_NAME}/htd
install(FILES htdocs/css/mpd.min.css DESTINATION share/${PROJECT_NAME}/htdocs/css/) install(FILES htdocs/css/mpd.min.css DESTINATION share/${PROJECT_NAME}/htdocs/css/)
install(DIRECTORY htdocs/assets DESTINATION share/${PROJECT_NAME}/htdocs) install(DIRECTORY htdocs/assets DESTINATION share/${PROJECT_NAME}/htdocs)
install(DIRECTORY DESTINATION /var/lib/${PROJECT_NAME}/) install(DIRECTORY DESTINATION /var/lib/${PROJECT_NAME}/)
install(FILES contrib/options DESTINATION /etc/${PROJECT_NAME}/)

View File

@ -1,18 +1,35 @@
myMPD myMPD
==== =====
myMPD is a lightweight MPD web client that runs without a dedicated webserver or interpreter. myMPD is a lightweight MPD web client that runs without a dedicated webserver or interpreter.
It's tuned for minimal resource usage and requires only very litte dependencies. It's tuned for minimal resource usage and requires only very litte dependencies.
myMPD is a fork of ympd. myMPD is a fork of ympd (https://github.com/notandy/ympd).
This fork provides a reworked ui based on Bootstrap 4 and a modernized backend. This fork provides a reworked ui based on Bootstrap 4 and a modernized backend.
Design principles:
- Keep it small and simple
- Uses only mpd as source of truth
- Mobile first UI
- Keep security in mind
Featurelist:
- Browse mpd database by artist
- Browse filesystem and playlists
- Queue management
- Advanced search
- Progressiv Web App enabled
- Local coverart support
myMPD is work in progress. Bugreportes and feature requests are very welcome. Please use the issues function from github (https://github.com/jcorporation/myMPD/issues).
Planed functions are managed in a github project (https://github.com/jcorporation/myMPD/projects/1).
![image](https://jcgames.de/stuff/myMPD/screenshots.gif) ![image](https://jcgames.de/stuff/myMPD/screenshots.gif)
UI Components UI Components
------------- -------------
- Bootstrap 4: https://getbootstrap.com/ - Bootstrap 4: https://getbootstrap.com/
- Material Design Icons: https://material.io/tools/icons/?style=baseline - Material Design Icons: https://material.io/tools/icons/
- Bootstrap Native: http://thednp.github.io/bootstrap.native/ - Bootstrap Native: http://thednp.github.io/bootstrap.native/
Backend Backend
@ -24,32 +41,45 @@ Dependencies
------------ ------------
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/ - libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
- cmake 2.6: http://cmake.org/ - cmake 2.6: http://cmake.org/
- OpenSSL: https://www.openssl.org/
Unix Build Instructions Unix Build Instructions
----------------------- -----------------------
1. install dependencies. cmake and libmpdclient (dev) are available from all major distributions. 1. Install dependencies: cmake, libmpdclient (dev) and OpenSSL (dev) are available from all major distributions.
2. build and install it ```cd /path/to/src; ./mkrelease.sh``` 2. Build and install: ```cd /path/to/src; ./mkrelease.sh```.
3. Link your mpd music directory to ```/usr/share/mympd/htdocs/library``` and put ```folder.jpg``` files in your album directories 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 flags
--------- ---------
``` ```
Usage: ./mympd [OPTION]... Usage: ./mympd [OPTION]...
-h, --host <host> connect to mpd at host [localhost] -h, --mpdhost <host> connect to mpd at host [localhost]
-p, --port <port> connect to mpd at port [6600] -p, --mpdport <port> connect to mpd at port [6600]
-m, --mpdpass <password> specifies the password to use when connecting to mpd
-w, --webport <port> listen port for webserver [80] -w, --webport <port> listen port for webserver [80]
-S, --ssl enable ssl
-W, --sslport <port> listen port for ssl webserver [443]
-C, --sslcert <filename> filename for ssl certificate [/etc/mympd/ssl/server.pem]
-K, --sslkey <filename> filename for ssl key [/etc/mympd/ssl/server.key]
-s, --streamport <port> connect to mpd http stream at port [8000] -s, --streamport <port> connect to mpd http stream at port [8000]
-u, --user <username> drop priviliges to user after socket bind -u, --user <username> drop priviliges to user after socket bind
-m, --mpdpass <password> specifies the password to use when connecting to mpd
-i, --coverimage <filename> filename for coverimage [folder.jpg] -i, --coverimage <filename> filename for coverimage [folder.jpg]
-t, --statefile <filename> filename for mympd state [/var/lib/mympd/mympd.state] -t, --statefile <filename> filename for mympd state [/var/lib/mympd/mympd.state]
-v, --version get version -v, --version get version
--help this help --help this help
``` ```
SSL
---
1. Create ca and certificate ```/path/to/src/contrib/crcert.sh``` (mkrelease.sh do this for you).
2. Start myMPD with ```-S``` (use default certificate under ```/etc/mympd/ssl/```).
3. Import ```/etc/mympd/ssl/ca/ca.pem``` in your browser to trust the certificate.
4. myMPD redirects http requests to https, ensure that myMPD hostname is resolvable.
Copyright Copyright
--------- ---------
ympd: 2013-2014 <andy@ndyk.de> ympd: 2013-2014 <andy@ndyk.de>

100
contrib/crcert.sh Executable file
View File

@ -0,0 +1,100 @@
#!/bin/sh
if [ -d /etc/mympd/ssl ]
then
echo "SSL directory exists, to recreate certificates: \"rm -r /etc/mympd/ssl\""
exit 1
fi
mkdir -p /etc/mympd/ssl/ca/certs
cd /etc/mympd/ssl/ca
echo '01' > serial
touch index.txt
touch index.txt.attr
echo "Creating ca"
cat > ca.cnf << EOL
[req]
distinguished_name = root_ca_distinguished_name
x509_extensions = root_ca_extensions
prompt = no
[root_ca_distinguished_name]
O = myMPD
CN = myMPD_CA
[root_ca_extensions]
basicConstraints = CA:true
[ ca ]
default_ca = mympd_ca
[mympd_ca]
dir = /etc/mympd/ssl/ca
database = /etc/mympd/ssl/ca/index.txt
new_certs_dir = /etc/mympd/ssl/ca/certs/
serial = /etc/mympd/ssl/ca/serial
copy_extensions = copy
policy = local_ca_policy
x509_extensions = local_ca_extensions
default_md = sha256
[ local_ca_policy ]
commonName = supplied
organizationName = supplied
[ local_ca_extensions ]
basicConstraints = CA:false
EOL
openssl req -new -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes -config ca.cnf \
-keyout ca.key -out ca.pem
HOSTNAME=$(hostname)
FQDN=$(hostname -f)
IP=$(getent hosts $HOSTNAME | awk {'print $1'})
cd /etc/mympd/ssl
echo "Creating cert:"
echo "\t$HOSTNAME"
echo "\t$FQDN"
echo "\t$IP"
cat > req.cnf << EOL
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
O = myMPD
CN = $FQDN
[v3_req]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = $HOSTNAME
DNS.2 = $FQDN
DNS.3 = localhost
IP.1 = $IP
IP.2 = 127.0.0.1
EOL
openssl req -new -sha256 -newkey rsa:2048 -days 3650 -nodes -config req.cnf \
-keyout server.key -out server.csr \
-extensions v3_req
echo "Sign cert with ca"
openssl ca -in server.csr -cert ca/ca.pem -keyfile ca/ca.key -config ca/ca.cnf \
-out server.pem -days 3650 -batch
rm server.csr
rm ca/ca.cnf
rm req.cnf

View File

@ -12,14 +12,29 @@ myMPD is a fork of ympd.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\-h\fR, \fB\-\-host HOST\fR \fB\-h\fR, \fB\-\-mpdhost HOST\fR
connect to mpd at host, defaults to localhost connect to mpd at host, defaults to localhost
.TP .TP
\fB\-p\fR, \fB\-\-port PORT\fR \fB\-p\fR, \fB\-\-mpdport PORT\fR
connect to mpd at port, defaults to 6600 connect to mpd at port, defaults to 6600
.TP .TP
\fB\-m\fR, \fB\-\-mpdpass PASSWORD\fR
specifies the password to use when connecting to mpd
.TP
\fB\-w\fR, \fB\-\-webport PORT\fR \fB\-w\fR, \fB\-\-webport PORT\fR
specifies the port for the webserver to listen to, defaults to 8080 listen interface/port for webserver [80]
.TP
\fB\-S\fR, \fB\-\-ssl\fR
enable ssl
.TP
\fB\-W\fR, \fB\-\-sslport PORT\fR
listen interface/port for ssl webserver [443]
.TP
\fB\-C\fR, \fB\-\-sslcert FILENAME\fR
filename for ssl certificate [/etc/mympd/ssl/server.pem]
.TP
\fB\-K\fR, \fB\-\-sslkey FILENAME\fR
filename for ssl key [/etc/mympd/ssl/server.key]
.TP .TP
\fB-s\fR, \fB\-\-streamport PORT \fB-s\fR, \fB\-\-streamport PORT
connect to mpd http stream at port [8000] connect to mpd http stream at port [8000]
@ -27,9 +42,6 @@ connect to mpd http stream at port [8000]
\fB\-u\fR, \fB\-\-user USERNAME\fR \fB\-u\fR, \fB\-\-user USERNAME\fR
drop privileges to the provided username after socket binding drop privileges to the provided username after socket binding
.TP .TP
\fB\-m\fR, \fB\-\-mpdpass PASSWORD\fR
specifies the password to use when connecting to mpd
.TP
\fB-i\fR, \fB\-\-coverimage FILENAME\fR \fB-i\fR, \fB\-\-coverimage FILENAME\fR
filename for coverimage [folder.jpg] filename for coverimage [folder.jpg]
.TP .TP

View File

@ -1,8 +0,0 @@
#Copy this file to /etc/default/mympd
MPD_HOST=localhost
MPD_PORT=6600
MPD_PASSWORD=
WEB_PORT=80
MYMPD_USER=nobody
COVERIMAGE=--coverimage folder.jpg
STATEFILE=--statefile /var/lib/mympd/mympd.state

View File

@ -3,15 +3,8 @@ Description=myMPD server daemon
Requires=network.target local-fs.target Requires=network.target local-fs.target
[Service] [Service]
Environment=MPD_HOST=localhost EnvironmentFile=/etc/mympd/options
Environment=MPD_PORT=6600 ExecStart=/usr/bin/mympd --user $MYMPD_USER --webport $WEB_PORT --mpdhost $MPD_HOST --mpdport $MPD_PORT $MPD_PASSWORD $COVERIMAGE $STATEFILE $SSL
Environment=MPD_PASSWORD=
Environment=WEB_PORT=80
Environment=MYMPD_USER=nobody
Environment=COVERIMAGE=--coverimage folder.jpg
Environment=STATEFILE=--statefile /var/lib/mympd/mympd.state
EnvironmentFile=/etc/default/mympd
ExecStart=/usr/bin/mympd --user $MYMPD_USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $COVERIMAGE $STATEFILE
Type=simple Type=simple
[Install] [Install]

9
contrib/options Normal file
View File

@ -0,0 +1,9 @@
#myMPD startup options
MPD_HOST=localhost
MPD_PORT=6600
#MPD_PASSWORD=--mpdpass PASSWORD
WEB_PORT=80
#SSL=-S -W 443 -C /etc/mympd/ssl/server.pem -K /etc/mympd/ssl/server.key
MYMPD_USER=nobody
COVERIMAGE=--coverimage folder.jpg
STATEFILE=--statefile /var/lib/mympd/mympd.state

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -36,10 +36,6 @@ button {
min-width: 50px; min-width: 50px;
} }
#search {
width: 200px;
}
.card { .card {
min-height:350px; min-height:350px;
} }

View File

@ -1 +1 @@
html{position:relative;min-height:100%}body{margin-bottom:60px}footer{position:absolute;bottom:0}body{padding-top:50px;padding-bottom:50px;background-color:#888}button{overflow:hidden}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}#counter{font-size:22px;margin-left:10px;min-width:50px}#search{width:200px}.card{min-height:350px}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}.tblnum,.tblaction{width:30px}.album-cover{background-size:cover;border:1px solid black;border-radius:5px;overflow:hidden;margin-bottom:20px;width:240px;height:240px;background-color:#eee}.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.eot);src:local('Material Icons'),local('MaterialIcons-Regular');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-small{font-size:16px}main{padding-top:20px}.color-darkgrey{color:#6c757d}.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}#progressBar{width:100%;margin-top:8px}#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-top{height:250px;overflow:hidden;display:block}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!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} html{position:relative;min-height:100%}body{margin-bottom:60px}footer{position:absolute;bottom:0}body{padding-top:50px;padding-bottom:50px;background-color:#888}button{overflow:hidden}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}#counter{font-size:22px;margin-left:10px;min-width:50px}.card{min-height:350px}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}.tblnum,.tblaction{width:30px}.album-cover{background-size:cover;border:1px solid black;border-radius:5px;overflow:hidden;margin-bottom:20px;width:240px;height:240px;background-color:#eee}.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.eot);src:local('Material Icons'),local('MaterialIcons-Regular');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-small{font-size:16px}main{padding-top:20px}.color-darkgrey{color:#6c757d}.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}#progressBar{width:100%;margin-top:8px}#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-top{height:250px;overflow:hidden;display:block}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!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}

View File

@ -1,17 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>myMPD</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="myMPD - fast and lightweight MPD webclient"> <meta name="description" content="myMPD - fast and lightweight MPD webclient">
<meta name="author" content="mail@jcgames.de"> <meta name="author" content="mail@jcgames.de">
<title>myMPD</title> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="theme-color" content="#343a40">
<link href="css/bootstrap.min.css" rel="stylesheet"> <link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/mpd.css" rel="stylesheet"> <link href="css/mpd.css" rel="stylesheet">
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon"> <link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
<meta name="apple-mobile-web-app-capable" content="yes" /> <link rel="manifest" href="mympd.webmanifest">
<meta name="apple-mobile-web-app-status-bar-style" content="black"/> <link rel="apple-touch-icon" href="assets/appicon-167.png">
<link rel="apple-touch-icon" href="assets/appicon.png"/>
</head> </head>
<body> <body>
<header> <header>
@ -21,15 +23,12 @@
<span class="material-icons header-logo">play_circle_outline</span>myMPD <span class="material-icons header-logo">play_circle_outline</span>myMPD
</a> </a>
<div class="dropdown-menu bg-dark"> <div class="dropdown-menu bg-dark">
<form id="search" class="px-4 py-3" role="search">
<input id="inputSearch" type="text" class="form-control" placeholder="Search">
</form>
<div class="dropdown-divider"></div>
<a id="nav-addstream" class="dropdown-item text-light bg-dark" href="#" data-toggle="modal" data-target="#modalAddstream">Add Stream</a> <a id="nav-addstream" class="dropdown-item text-light bg-dark" href="#" data-toggle="modal" data-target="#modalAddstream">Add Stream</a>
<a id="nav-updatedb" class="dropdown-item text-light bg-dark" href="#" data-href="{'cmd':'updateDB','options':[]}">Update Database</a> <a id="nav-updatedb" class="dropdown-item text-light bg-dark" href="#" data-href="{'cmd':'updateDB','options':[]}">Update Database</a>
<a id="nav-localplayer" class="dropdown-item text-light bg-dark" href="#" data-href="{'cmd':'openLocalPlayer','options':[]}">Local Player</a> <a id="nav-localplayer" class="dropdown-item text-light bg-dark" href="#" data-href="{'cmd':'openLocalPlayer','options':[]}">Local Player</a>
<a id="nav-settings" class="dropdown-item text-light bg-dark" href="#" data-toggle="modal" data-target="#modalSettings">Settings</a> <a id="nav-settings" class="dropdown-item text-light bg-dark" href="#" data-toggle="modal" data-target="#modalSettings">Settings</a>
<a id="nav-about" class="dropdown-item text-light bg-dark" href="#" data-toggle="modal" data-target="#modalAbout">About</a> <a id="nav-about" class="dropdown-item text-light bg-dark" href="#" data-toggle="modal" data-target="#modalAbout">About</a>
<a id="btnAdd" class="dropdown-item text-light bg-dark hide" href="#">Add2HomeScreen</a>
</div> </div>
</div> </div>
<div class="btn-toolbar col-auto pl-0 pr-0" role="toolbar"> <div class="btn-toolbar col-auto pl-0 pr-0" role="toolbar">
@ -139,7 +138,7 @@
</div> </div>
<div class="table-responsive-md"> <div class="table-responsive-md">
<table id="QueueList" class="table table-hover table-sm"> <table id="QueueList" class="table table-hover table-sm" data-version="">
<col class="tblnum"/> <col class="tblnum"/>
<col class="tbltitle"/> <col class="tbltitle"/>
<col class="tblartist"/> <col class="tblartist"/>
@ -379,15 +378,15 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="btn-toolbar card-toolbar" id="SearchButtons" role="toolbar"> <div class="btn-toolbar card-toolbar" id="SearchButtons" role="toolbar">
<form id="search2" role="search"> <form id="search" role="search">
<div class="input-group mr-2"> <div class="input-group mr-2">
<input type="text" class="form-control" placeholder="Search" id="searchstr2"/> <input type="text" class="form-control" placeholder="Search" id="searchstr"/>
<div class="input-group-append"> <div class="input-group-append">
<button title="Select tags to search" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown"> <button title="Select tags to search" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<span class="material-icons">search</span> <span class="material-icons">search</span>
<span id="searchtags2desc">Any Tag</span> <span id="searchtagsdesc">Any Tag</span>
</button> </button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="searchtags2"> <div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="searchtags">
<h6 class="dropdown-header text-light">Search in Tag</h6> <h6 class="dropdown-header text-light">Search in Tag</h6>
<button type="button" class="btn btn-secondary active btn-block">Any Tag</button> <button type="button" class="btn btn-secondary active btn-block">Any Tag</button>
<button type="button" class="btn btn-secondary btn-block">Title</button> <button type="button" class="btn btn-secondary btn-block">Title</button>
@ -582,31 +581,26 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h4><a class="text-success" href="https://github.com/jcorporation/ympd">myMPD</a>&nbsp;&ndash;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4> <h4><a class="text-success" rel="noreferrer" href="https://github.com/jcorporation/ympd">myMPD</a>&nbsp;&ndash;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
<p>myMPD is a lightweight MPD web client that runs without a dedicated webserver or interpreter. It's tuned for minimal resource usage and requires only very litte dependencies. myMPD is a fork of <a class="text-success" href="http://www.ympd.org">ympd</a>.</p> <p>myMPD is a lightweight MPD web client that runs without a dedicated webserver or interpreter. It's tuned for minimal resource usage and requires only very litte dependencies. myMPD is a fork of <a class="text-success" href="http://www.ympd.org">ympd</a>.</p>
<ul> <ul>
<li>Version: <span id="mympdVersion"></span></li> <li>Version: <span id="mympdVersion"></span></li>
<li>Homepage: <a class="text-success" target="_blank" href="https://github.com/jcorporation/mympd">https://github.com/jcorporation/mympd</a></li> <li>Homepage: <a class="text-success" target="_blank" rel="noreferrer" href="https://github.com/jcorporation/mympd">https://github.com/jcorporation/mympd</a></li>
<li>Autor: Jürgen Mang &lt;<a class="text-success" href="mailto:mail@jcgames.de">mail@jcgames.de</a>&gt;</li> <li>Autor: Jürgen Mang &lt;<a class="text-success" href="mailto:mail@jcgames.de">mail@jcgames.de</a>&gt;</li>
</ul> </ul>
<hr/>
<h5>Database Statistics</h5>
<table class="table table-sm"> <table class="table table-sm">
<tbody> <tbody>
<tr><td colspan="2" class="pt-3"><h5>Database Statistics</h5></td></tr>
<tr><th>Artists</th><td id="mpdstats_artists"></td></tr> <tr><th>Artists</th><td id="mpdstats_artists"></td></tr>
<tr><th>Albums</th><td id="mpdstats_albums"></td></tr> <tr><th>Albums</th><td id="mpdstats_albums"></td></tr>
<tr><th>Songs</th><td id="mpdstats_songs"></td></tr> <tr><th>Songs</th><td id="mpdstats_songs"></td></tr>
<tr><th>DB Play Time</th><td id="mpdstats_dbplaytime"></td></tr> <tr><th>DB Play Time</th><td id="mpdstats_dbplaytime"></td></tr>
<tr><th>DB Updated</th><td id="mpdstats_dbupdated"></td></tr> <tr><th>DB Updated</th><td id="mpdstats_dbupdated"></td></tr>
</tbody> <tr><td colspan="2" class="pt-3"><h5>Play Statistics</h5></td></tr>
</table>
<hr/>
<h5>Play Statistics</h5>
<table class="table table-sm">
<tbody>
<tr><th>MPD Version</th><td id="mpdVersion"></td></tr>
<tr><th>Uptime</th><td id="mpdstats_uptime"></td></tr> <tr><th>Uptime</th><td id="mpdstats_uptime"></td></tr>
<tr><th>Play Time</th><td id="mpdstats_playtime"></td></tr> <tr><th>Play Time</th><td id="mpdstats_playtime"></td></tr>
<tr><td colspan="2" class="pt-3"><h5>MPD</h5></td></tr>
<tr><th>Protocol Version</th><td id="mpdVersion"></td></tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -673,7 +667,7 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="savequeueLabel"><span class="material-icons title-icon">music_note</span> Song Details</h5> <h5 class="modal-title"><span class="material-icons title-icon">music_note</span> Song Details</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>

View File

@ -29,6 +29,7 @@ var current_song = new Object();
var playstate = ''; var playstate = '';
var settings = {}; var settings = {};
var alertTimeout; var alertTimeout;
let deferredPrompt;
var app = {}; var app = {};
app.apps = { "Playback": { "state": "0/-/", "scrollPos": 0 }, app.apps = { "Playback": { "state": "0/-/", "scrollPos": 0 },
@ -66,6 +67,7 @@ domCache.btnNext = document.getElementById('btnNext');
domCache.progressBar = document.getElementById('progressBar'); domCache.progressBar = document.getElementById('progressBar');
domCache.volumeBar = document.getElementById('volumeBar'); domCache.volumeBar = document.getElementById('volumeBar');
domCache.outputs = document.getElementById('outputs'); domCache.outputs = document.getElementById('outputs');
domCache.btnAdd = document.getElementById('btnAdd');
var modalConnectionError = new Modal(document.getElementById('modalConnectionError')); var modalConnectionError = new Modal(document.getElementById('modalConnectionError'));
var modalSettings = new Modal(document.getElementById('modalSettings')); var modalSettings = new Modal(document.getElementById('modalSettings'));
@ -77,14 +79,14 @@ var mainMenu = new Dropdown(document.getElementById('mainMenu'));
function appPrepare(scrollPos) { function appPrepare(scrollPos) {
if (app.current.app != app.last.app || app.current.tab != app.last.tab || app.current.view != app.last.view) { if (app.current.app != app.last.app || app.current.tab != app.last.tab || app.current.view != app.last.view) {
//Hide all cards + nav //Hide all cards + nav
for (var i = 0; i < domCache.navbarBottomBtnsLen; i ++) { for (var i = 0; i < domCache.navbarBottomBtnsLen; i++) {
domCache.navbarBottomBtns[i].classList.remove('active'); domCache.navbarBottomBtns[i].classList.remove('active');
} }
document.getElementById('cardPlayback').classList.add('hide'); document.getElementById('cardPlayback').classList.add('hide');
document.getElementById('cardQueue').classList.add('hide'); document.getElementById('cardQueue').classList.add('hide');
document.getElementById('cardBrowse').classList.add('hide'); document.getElementById('cardBrowse').classList.add('hide');
document.getElementById('cardSearch').classList.add('hide'); document.getElementById('cardSearch').classList.add('hide');
for (var i = 0; i < domCache.panelHeadingBrowseLen; i ++) { for (var i = 0; i < domCache.panelHeadingBrowseLen; i++) {
domCache.panelHeadingBrowse[i].classList.remove('active'); domCache.panelHeadingBrowse[i].classList.remove('active');
} }
document.getElementById('cardBrowsePlaylists').classList.add('hide'); document.getElementById('cardBrowsePlaylists').classList.add('hide');
@ -168,13 +170,15 @@ function appRoute() {
} }
else if (app.current.app == 'Queue' ) { else if (app.current.app == 'Queue' ) {
document.getElementById('QueueList').classList.add('opacity05'); document.getElementById('QueueList').classList.add('opacity05');
/*
if (app.last.app != app.current.app) { if (app.last.app != app.current.app) {
if (app.current.search.length < 2) { if (app.current.search.length < 2) {
setPagination(app.current.page); setPagination(app.current.page);
} }
} }
*/
var btns = document.getElementById('searchqueuetag').getElementsByTagName('button'); var btns = document.getElementById('searchqueuetag').getElementsByTagName('button');
for (var i = 0; i < btns.length; i ++) { for (var i = 0; i < btns.length; i++) {
btns[i].classList.remove('active'); btns[i].classList.remove('active');
if (btns[i].innerText == app.current.filter) { if (btns[i].innerText == app.current.filter) {
btns[i].classList.add('active'); btns[i].classList.add('active');
@ -211,7 +215,7 @@ function appRoute() {
var pathArray = app.current.search.split('/'); var pathArray = app.current.search.split('/');
var pathArrayLen = pathArray.length; var pathArrayLen = pathArray.length;
var fullPath = ''; var fullPath = '';
for (var i = 0; i < pathArrayLen; i ++) { for (var i = 0; i < pathArrayLen; i++) {
if (pathArrayLen -1 == i) { if (pathArrayLen -1 == i) {
breadcrumbs += '<li class="breadcrumb-item active">' + pathArray[i] + '</li>'; breadcrumbs += '<li class="breadcrumb-item active">' + pathArray[i] + '</li>';
break; break;
@ -224,7 +228,7 @@ function appRoute() {
elBrowseBreadcrumb.innerHTML = breadcrumbs; elBrowseBreadcrumb.innerHTML = breadcrumbs;
var breadcrumbItems = elBrowseBreadcrumb.getElementsByTagName('a'); var breadcrumbItems = elBrowseBreadcrumb.getElementsByTagName('a');
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 + '/' + this.getAttribute('data-uri'));
}, false); }, false);
@ -232,17 +236,17 @@ function appRoute() {
doSetFilterLetter('BrowseFilesystemFilter'); doSetFilterLetter('BrowseFilesystemFilter');
} }
else if (app.current.app == 'Search') { else if (app.current.app == 'Search') {
document.getElementById('searchstr2').focus(); document.getElementById('searchstr').focus();
document.getElementById('SearchList').classList.add('opacity05'); document.getElementById('SearchList').classList.add('opacity05');
if (app.last.app != app.current.app) { if (app.last.app != app.current.app) {
if (app.current.search != '') if (app.current.search != '')
document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML= document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML=
'<tr><td><span class="material-icons">search</span></td>' + '<tr><td><span class="material-icons">search</span></td>' +
'<td colspan="5">Searching...</td></tr>'; '<td colspan="5">Searching...</td></tr>';
else // else
setPagination(app.current.page); // setPagination(app.current.page);
document.getElementById('searchstr2').value = app.current.search; // document.getElementById('searchstr').value = app.current.search;
} }
if (app.current.search.length >= 2) { if (app.current.search.length >= 2) {
@ -251,17 +255,17 @@ function appRoute() {
document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML = ''; document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML = '';
document.getElementById('searchAddAllSongs').setAttribute('disabled', 'disabled'); document.getElementById('searchAddAllSongs').setAttribute('disabled', 'disabled');
document.getElementById('panel-heading-search').innerText = ''; document.getElementById('panel-heading-search').innerText = '';
setPagination(app.current.page); // setPagination(app.current.page);
document.getElementById('SearchList').classList.remove('opacity05'); document.getElementById('SearchList').classList.remove('opacity05');
} }
var btns = document.getElementById('searchtags2').getElementsByTagName('button'); var btns = document.getElementById('searchtags').getElementsByTagName('button');
var btnsLen = btns.length; var btnsLen = btns.length;
for (var i = 0; i < btnsLen; i ++) { for (var i = 0; i < btnsLen; i++) {
btns[i].classList.remove('active'); btns[i].classList.remove('active');
if (btns[i].innerText == app.current.filter) { if (btns[i].innerText == app.current.filter) {
btns[i].classList.add('active'); btns[i].classList.add('active');
document.getElementById('searchtags2desc').innerText = btns[i].innerText; document.getElementById('searchtagsdesc').innerText = btns[i].innerText;
} }
} }
} }
@ -313,23 +317,13 @@ function appInit() {
addStream(); addStream();
}); });
document.getElementById('mainMenu').addEventListener('shown.bs.dropdown', function () {
var si = document.getElementById('inputSearch');
si.value = '';
si.focus();
});
document.getElementById('inputSearch').addEventListener('click', function(event) {
event.stopPropagation();
});
addFilterLetter('BrowseFilesystemFilterLetters'); addFilterLetter('BrowseFilesystemFilterLetters');
addFilterLetter('BrowseDatabaseFilterLetters'); addFilterLetter('BrowseDatabaseFilterLetters');
addFilterLetter('BrowsePlaylistsFilterLetters'); addFilterLetter('BrowsePlaylistsFilterLetters');
var hrefs = document.querySelectorAll('button[data-href], a[data-href]'); var hrefs = document.querySelectorAll('button[data-href], a[data-href]');
var hrefsLen = hrefs.length; var hrefsLen = hrefs.length;
for (var i = 0; i < hrefsLen; i ++) { for (var i = 0; i < hrefsLen; i++) {
hrefs[i].addEventListener('click', function(event) { hrefs[i].addEventListener('click', function(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -348,7 +342,7 @@ function appInit() {
var pd = document.querySelectorAll('.pages'); var pd = document.querySelectorAll('.pages');
var pdLen = pd.length; var pdLen = pd.length;
for (var i = 0; i < pdLen; i ++) { for (var i = 0; i < pdLen; i++) {
pd[i].addEventListener('click', function(event) { pd[i].addEventListener('click', function(event) {
if (event.target.nodeName == 'BUTTON') { if (event.target.nodeName == 'BUTTON') {
gotoPage(event.target.getAttribute('data-page')); gotoPage(event.target.getAttribute('data-page'));
@ -418,7 +412,7 @@ function appInit() {
} }
}, false); }, false);
document.getElementById('searchtags2').addEventListener('click', function(event) { document.getElementById('searchtags').addEventListener('click', function(event) {
if (event.target.nodeName == 'BUTTON') if (event.target.nodeName == 'BUTTON')
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + event.target.innerText + '/' + app.current.search); appGoto(app.current.app, app.current.tab, app.current.view, '0/' + event.target.innerText + '/' + app.current.search);
}, false); }, false);
@ -432,19 +426,7 @@ function appInit() {
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + event.target.innerText + '/' + app.current.search); appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + event.target.innerText + '/' + app.current.search);
}, false); }, false);
document.getElementById('inputSearch').addEventListener('keypress', function (event) {
if ( event.which == 13 )
mainMenu.toggle();
}, false);
document.getElementById('search').addEventListener('submit', function () { document.getElementById('search').addEventListener('submit', function () {
var searchStr = document.getElementById('inputSearch').value;
appGoto('Search', undefined, undefined, app.current.page + '/Any Tag/' + searchStr);
document.getElementById('searchstr2').value = searchStr;
return false;
}, false);
document.getElementById('search2').addEventListener('submit', function () {
return false; return false;
}, false); }, false);
@ -452,7 +434,7 @@ function appInit() {
return false; return false;
}, false); }, false);
document.getElementById('searchstr2').addEventListener('keyup', function (event) { document.getElementById('searchstr').addEventListener('keyup', function (event) {
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + this.value); appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + this.value);
}, false); }, false);
@ -476,6 +458,51 @@ function appInit() {
} }
event.preventDefault(); event.preventDefault();
}, false); }, false);
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
});
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
// Update UI notify the user they can add to home screen
domCache.btnAdd.classList.remove('hide');
});
domCache.btnAdd.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
domCache.btnAdd.classList.add('hide');
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted')
console.log('User accepted the A2HS prompt');
else
console.log('User dismissed the A2HS prompt');
deferredPrompt = null;
});
});
window.addEventListener('appinstalled', (evt) => {
console.log('appinstalled');
});
} }
function webSocketConnect() { function webSocketConnect() {
@ -651,7 +678,7 @@ function getSettings() {
function parseOutputnames(obj) { function parseOutputnames(obj) {
var btns = ''; var btns = '';
var outputsLen = obj.data.outputs.length; var outputsLen = obj.data.outputs.length;
for (var i = 0; i < outputsLen; i ++) { for (var i = 0; i < outputsLen; i++) {
btns += '<button id="btnoutput' + obj.data.outputs[i].id +'" data-output-id="' + obj.data.outputs[i].id + '" class="btn btn-secondary btn-block">'+ btns += '<button id="btnoutput' + obj.data.outputs[i].id +'" data-output-id="' + obj.data.outputs[i].id + '" class="btn btn-secondary btn-block">'+
'<span class="material-icons float-left">volume_up</span> ' + obj.data.outputs[i].name + '</button>'; '<span class="material-icons float-left">volume_up</span> ' + obj.data.outputs[i].name + '</button>';
} }
@ -746,7 +773,7 @@ function parseState(obj) {
// Set outputs state // Set outputs state
var outputsLen = obj.data.outputs.length; var outputsLen = obj.data.outputs.length;
for (var i = 0; i < outputsLen; i ++) { for (var i = 0; i < outputsLen; i++) {
toggleBtn('btnoutput' + obj.data.outputs[i].id, obj.data.outputs[i].state); toggleBtn('btnoutput' + obj.data.outputs[i].id, obj.data.outputs[i].state);
} }
@ -756,8 +783,13 @@ function parseState(obj) {
function getQueue() { function getQueue() {
if (app.current.search.length >= 2) if (app.current.search.length >= 2)
sendAPI({"cmd": "MPD_API_SEARCH_QUEUE", "data": {"mpdtag":app.current.filter, "offset":app.current.page, "searchstr": app.current.search}}, parseQueue); sendAPI({"cmd": "MPD_API_SEARCH_QUEUE", "data": {"mpdtag":app.current.filter, "offset":app.current.page, "searchstr": app.current.search}}, parseQueue);
else else {
var queue_version = document.getElementById('QueueList').getAttribute('data-version');
if (last_state && queue_version != last_state.data.queue_version)
sendAPI({"cmd": "MPD_API_GET_QUEUE", "data": {"offset": app.current.page}}, parseQueue); sendAPI({"cmd": "MPD_API_GET_QUEUE", "data": {"offset": app.current.page}}, parseQueue);
else
document.getElementById('QueueList').classList.remove('opacity05');
}
} }
function parseQueue(obj) { function parseQueue(obj) {
@ -772,9 +804,11 @@ function parseQueue(obj) {
document.getElementById('panel-heading-queue').innerText = ''; document.getElementById('panel-heading-queue').innerText = '';
var nrItems = obj.data.length; var nrItems = obj.data.length;
var tbody = document.getElementById(app.current.app + 'List').getElementsByTagName('tbody')[0]; var table = document.getElementById(app.current.app + 'List');
table.setAttribute('data-version', obj.queue_version);
var tbody = table.getElementsByTagName('tbody')[0];
var tr = tbody.getElementsByTagName('tr'); var tr = tbody.getElementsByTagName('tr');
for (var i = 0; i < nrItems; i ++) { for (var i = 0; i < nrItems; i++) {
if (tr[i]) if (tr[i])
if (tr[i].getAttribute('data-trackid') == obj.data[i].id) if (tr[i].getAttribute('data-trackid') == obj.data[i].id)
continue; continue;
@ -833,7 +867,7 @@ function parseFilesystem(obj) {
var nrItems = obj.data.length; var nrItems = obj.data.length;
var tbody = document.getElementById(app.current.app + (app.current.tab==undefined ? '' : app.current.tab) + 'List').getElementsByTagName('tbody')[0]; var tbody = document.getElementById(app.current.app + (app.current.tab==undefined ? '' : app.current.tab) + 'List').getElementsByTagName('tbody')[0];
var tr = tbody.getElementsByTagName('tr'); var tr = tbody.getElementsByTagName('tr');
for (var i = 0; i < nrItems; i ++) { for (var i = 0; i < nrItems; i++) {
var uri = encodeURI(obj.data[i].uri); var uri = encodeURI(obj.data[i].uri);
if (tr[i]) if (tr[i])
if (tr[i].getAttribute('data-uri') == uri) if (tr[i].getAttribute('data-uri') == uri)
@ -890,7 +924,7 @@ function parsePlaylists(obj) {
var nrItems = obj.data.length; var nrItems = obj.data.length;
var tbody = document.getElementById(app.current.app + app.current.tab + 'List').getElementsByTagName('tbody')[0]; var tbody = document.getElementById(app.current.app + app.current.tab + 'List').getElementsByTagName('tbody')[0];
var tr = tbody.getElementsByTagName('tr'); var tr = tbody.getElementsByTagName('tr');
for (var i = 0; i < nrItems; i ++) { for (var i = 0; i < nrItems; i++) {
var uri = encodeURI(obj.data[i].uri); var uri = encodeURI(obj.data[i].uri);
if (tr[i]) if (tr[i])
if (tr[i].getAttribute('data-uri') == uri) if (tr[i].getAttribute('data-uri') == uri)
@ -932,7 +966,7 @@ function parseListDBtags(obj) {
var nrItems = obj.data.length; var nrItems = obj.data.length;
var tbody = document.getElementById(app.current.app + app.current.tab + app.current.view + 'List').getElementsByTagName('tbody')[0]; var tbody = document.getElementById(app.current.app + app.current.tab + app.current.view + 'List').getElementsByTagName('tbody')[0];
var tr = tbody.getElementsByTagName('tr'); var tr = tbody.getElementsByTagName('tr');
for (var i = 0; i < nrItems; i ++) { for (var i = 0; i < nrItems; i++) {
var uri = encodeURI(obj.data[i].value); var uri = encodeURI(obj.data[i].value);
if (tr[i]) if (tr[i])
if (tr[i].getAttribute('data-uri') == uri) if (tr[i].getAttribute('data-uri') == uri)
@ -1017,7 +1051,7 @@ function parseListTitles(obj) {
var titleList = ''; var titleList = '';
var nrItems = obj.data.length; var nrItems = obj.data.length;
for (var i = 0; i < nrItems; i ++) { for (var i = 0; i < nrItems; i++) {
titleList += '<tr data-type="song" data-name="' + obj.data[i].title + '" data-uri="' + encodeURI(obj.data[i].uri) + '">' + titleList += '<tr data-type="song" data-name="' + obj.data[i].title + '" data-uri="' + encodeURI(obj.data[i].uri) + '">' +
'<td>' + obj.data[i].track + '</td><td>' + obj.data[i].title + '</td>' + '<td>' + obj.data[i].track + '</td><td>' + obj.data[i].title + '</td>' +
'<td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>' + '<td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>' +
@ -1047,12 +1081,12 @@ function setPagination(number) {
if (totalPages == 0) if (totalPages == 0)
totalPages = 1; totalPages = 1;
var p = ['PaginationTop', 'PaginationBottom']; var p = ['PaginationTop', 'PaginationBottom'];
for (var i = 0; i < 2; i ++) { for (var i = 0; i < 2; i++) {
document.getElementById(cat + p[i] + 'Page').innerText = (app.current.page / settings.max_elements_per_page + 1) + ' / ' + totalPages; document.getElementById(cat + p[i] + 'Page').innerText = (app.current.page / settings.max_elements_per_page + 1) + ' / ' + totalPages;
if (totalPages > 1) { if (totalPages > 1) {
document.getElementById(cat + p[i] + 'Page').removeAttribute('disabled'); document.getElementById(cat + p[i] + 'Page').removeAttribute('disabled');
var pl = ''; var pl = '';
for (var j = 0; j < totalPages; j ++) { for (var j = 0; j < totalPages; j++) {
pl += '<button data-page="' + (j * settings.max_elements_per_page) + '" type="button" class="mr-1 mb-1 btn-sm btn btn-secondary">' + pl += '<button data-page="' + (j * settings.max_elements_per_page) + '" type="button" class="mr-1 mb-1 btn-sm btn btn-secondary">' +
( j + 1) + '</button>'; ( j + 1) + '</button>';
} }
@ -1136,7 +1170,7 @@ function parseSongDetails(obj) {
modal.getElementsByTagName('h1')[0].innerText = obj.data.title; modal.getElementsByTagName('h1')[0].innerText = obj.data.title;
var tr = modal.getElementsByTagName('tr'); var tr = modal.getElementsByTagName('tr');
var trLen = tr.length; var trLen = tr.length;
for (var i = 0; i < trLen; i ++) { for (var i = 0; i < trLen; i++) {
var key = tr[i].getAttribute('data-name'); var key = tr[i].getAttribute('data-name');
var value = obj.data[key]; var value = obj.data[key];
if (key == 'duration') { if (key == 'duration') {
@ -1389,6 +1423,9 @@ function showNotification(notificationTitle,notificationText,notificationHtml,no
if (!document.getElementById('alertBox')) { if (!document.getElementById('alertBox')) {
alertBox = document.createElement('div'); alertBox = document.createElement('div');
alertBox.setAttribute('id', 'alertBox'); alertBox.setAttribute('id', 'alertBox');
alertBox.addEventListener('click', function() {
hideNotification();
}, false);
} }
else { else {
alertBox = document.getElementById('alertBox'); alertBox = document.getElementById('alertBox');
@ -1400,12 +1437,17 @@ function showNotification(notificationTitle,notificationText,notificationHtml,no
if (alertTimeout) if (alertTimeout)
clearTimeout(alertTimeout); clearTimeout(alertTimeout);
alertTimeout = setTimeout(function() { alertTimeout = setTimeout(function() {
if (document.getElementById('alertBox')) hideNotification();
}, 3000);
}
}
function hideNotification() {
if (document.getElementById('alertBox')) {
document.getElementById('alertBox').classList.remove('alertBoxActive'); document.getElementById('alertBox').classList.remove('alertBoxActive');
setTimeout(function() { setTimeout(function() {
document.getElementById('alertBox').remove(); document.getElementById('alertBox').remove();
}, 600); }, 600);
}, 3000);
} }
} }
@ -1468,7 +1510,7 @@ function doSetFilterLetter(x) {
if (filter != '-') { if (filter != '-') {
var btns = document.getElementById(x + 'Letters').getElementsByTagName('button'); var btns = document.getElementById(x + 'Letters').getElementsByTagName('button');
var btnsLen = btns.length; var btnsLen = btns.length;
for (var i = 0; i < btnsLen; i ++) { for (var i = 0; i < btnsLen; i++) {
if (btns[i].innerText == filter) { if (btns[i].innerText == filter) {
btns[i].classList.add('active'); btns[i].classList.add('active');
break; break;
@ -1480,7 +1522,7 @@ function doSetFilterLetter(x) {
function addFilterLetter(x) { function addFilterLetter(x) {
var filter = '<button class="mr-1 mb-1 btn btn-sm btn-secondary material-icons material-icons-small">delete</button>' + var filter = '<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>'; '<button class="mr-1 mb-1 btn btn-sm btn-secondary">#</button>';
for (i = 65; i <= 90; i ++) { for (i = 65; i <= 90; i++) {
filter += '<button class="mr-1 mb-1 btn-sm btn btn-secondary">' + String.fromCharCode(i) + '</button>'; filter += '<button class="mr-1 mb-1 btn-sm btn btn-secondary">' + String.fromCharCode(i) + '</button>';
} }
var letters = document.getElementById(x); var letters = document.getElementById(x);

57
htdocs/js/mpd.min.js vendored
View File

@ -5,10 +5,10 @@ $jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[
$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.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=function(a,b,c,e){if(b){c=$jscomp.global;a=a.split(".");for(e=0;e<a.length-1;e++){var d=a[e];d in c||(c[d]={});c=c[d]}a=a[a.length-1];e=c[a];b=b(e);b!=e&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}}; $jscomp.polyfill=function(a,b,c,e){if(b){c=$jscomp.global;a=a.split(".");for(e=0;e<a.length-1;e++){var d=a[e];d in c||(c[d]={});c=c[d]}a=a[a.length-1];e=c[a];b=b(e);b!=e&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};
$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 e="";a;)if(a&1&&(e+=b),a>>>=1)b+=b;return e}},"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 e="";a;)if(a&1&&(e+=b),a>>>=1)b+=b;return e}},"es6","es3");
var socket,last_song="",last_state,current_song={},playstate="",settings={},alertTimeout,app={apps:{Playback:{state:"0/-/",scrollPos:0},Queue:{state:"0/Any Tag/",scrollPos:0},Browse:{active:"Database",tabs:{Filesystem:{state:"0/-/",scrollPos:0},Playlists:{state:"0/-/",scrollPos:0},Database:{active:"Artist",views:{Artist:{state:"0/-/",scrollPos:0},Album:{state:"0/-/",scrollPos:0}}}}},Search:{state:"0/Any Tag/",scrollPos:0}},current:{app:"Playback",tab:void 0,view:void 0,page:0,filter:"",search:"", var socket,last_song="",last_state,current_song={},playstate="",settings={},alertTimeout,deferredPrompt,app={apps:{Playback:{state:"0/-/",scrollPos:0},Queue:{state:"0/Any Tag/",scrollPos:0},Browse:{active:"Database",tabs:{Filesystem:{state:"0/-/",scrollPos:0},Playlists:{state:"0/-/",scrollPos:0},Database:{active:"Artist",views:{Artist:{state:"0/-/",scrollPos:0},Album:{state:"0/-/",scrollPos:0}}}}},Search:{state:"0/Any Tag/",scrollPos:0}},current:{app:"Playback",tab:void 0,view:void 0,page:0,filter:"",
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.panelHeadingBrowse=document.getElementById("panel-heading-browse").getElementsByTagName("a");domCache.panelHeadingBrowseLen=domCache.panelHeadingBrowse.length;domCache.counter=document.getElementById("counter");domCache.volumePrct=document.getElementById("volumePrct"); 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.panelHeadingBrowse=document.getElementById("panel-heading-browse").getElementsByTagName("a");domCache.panelHeadingBrowseLen=domCache.panelHeadingBrowse.length;domCache.counter=document.getElementById("counter");
domCache.volumeControl=document.getElementById("volumeControl");domCache.volumeIcon=document.getElementById("volumeIcon");domCache.btnPlay=document.getElementById("btnPlay");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.volumePrct=document.getElementById("volumePrct");domCache.volumeControl=document.getElementById("volumeControl");domCache.volumeIcon=document.getElementById("volumeIcon");domCache.btnPlay=document.getElementById("btnPlay");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");
var modalConnectionError=new Modal(document.getElementById("modalConnectionError")),modalSettings=new Modal(document.getElementById("modalSettings")),modalAddstream=new Modal(document.getElementById("modalAddstream")),modalSavequeue=new Modal(document.getElementById("modalSavequeue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),mainMenu=new Dropdown(document.getElementById("mainMenu")); domCache.btnAdd=document.getElementById("btnAdd");var modalConnectionError=new Modal(document.getElementById("modalConnectionError")),modalSettings=new Modal(document.getElementById("modalSettings")),modalAddstream=new Modal(document.getElementById("modalAddstream")),modalSavequeue=new Modal(document.getElementById("modalSavequeue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),mainMenu=new Dropdown(document.getElementById("mainMenu"));
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.panelHeadingBrowseLen;b++)domCache.panelHeadingBrowse[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.panelHeadingBrowseLen;b++)domCache.panelHeadingBrowse[b].classList.remove("active");
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).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+ 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).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)}} "Nav"+app.current.tab).classList.add("active"));scrollTo(a)}}
@ -16,26 +16,26 @@ function appGoto(a,b,c,e){var d=document.body.scrollTop?document.body.scrollTop:
(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==e?app.apps[a].tabs[b].views[c].state:e)):a="/"+a+"/"+b+"!"+(void 0==e?app.apps[a].tabs[b].state:e)):a="/"+a+"!"+(void 0==e?app.apps[a].state:e);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==e?app.apps[a].tabs[b].views[c].state:e)):a="/"+a+"/"+b+"!"+(void 0==e?app.apps[a].tabs[b].state:e)):a="/"+a+"!"+(void 0==e?app.apps[a].state:e);location.hash=a}
function appRoute(){if(params=decodeURI(location.hash).match(/^#\/(\w+)\/?(\w+)?\/?(\w+)?!((\d+)\/([^\/]+)\/(.*))$/)){app.current.app=params[1];app.current.tab=params[2];app.current.view=params[3];app.apps[app.current.app].state?(app.apps[app.current.app].state=params[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=params[4],app.apps[app.current.app].active=app.current.tab,app.current.scrollPos= function appRoute(){if(params=decodeURI(location.hash).match(/^#\/(\w+)\/?(\w+)?\/?(\w+)?!((\d+)\/([^\/]+)\/(.*))$/)){app.current.app=params[1];app.current.tab=params[2];app.current.view=params[3];app.apps[app.current.app].state?(app.apps[app.current.app].state=params[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=params[4],app.apps[app.current.app].active=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=params[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(params[5]);app.current.filter= 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=params[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(params[5]);app.current.filter=
params[6];app.current.search=params[7];appPrepare(app.current.scrollPos);if("Playback"==app.current.app)sendAPI({cmd:"MPD_API_GET_CURRENT_SONG"},songChange);else if("Queue"==app.current.app){document.getElementById("QueueList").classList.add("opacity05");app.last.app!=app.current.app&&2>app.current.search.length&&setPagination(app.current.page);for(var a=document.getElementById("searchqueuetag").getElementsByTagName("button"),b=0;b<a.length;b++)a[b].classList.remove("active"),a[b].innerText==app.current.filter&& params[6];app.current.search=params[7];appPrepare(app.current.scrollPos);if("Playback"==app.current.app)sendAPI({cmd:"MPD_API_GET_CURRENT_SONG"},songChange);else if("Queue"==app.current.app){document.getElementById("QueueList").classList.add("opacity05");for(var a=document.getElementById("searchqueuetag").getElementsByTagName("button"),b=0;b<a.length;b++)a[b].classList.remove("active"),a[b].innerText==app.current.filter&&(a[b].classList.add("active"),document.getElementById("searchqueuetagdesc").innerText=
(a[b].classList.add("active"),document.getElementById("searchqueuetagdesc").innerText=a[b].innerText);getQueue()}else if("Browse"==app.current.app&&"Playlists"==app.current.tab)document.getElementById("BrowsePlaylistsList").classList.add("opacity05"),sendAPI({cmd:"MPD_API_GET_PLAYLISTS",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Database"==app.current.tab&&"Artist"==app.current.view)document.getElementById("BrowseDatabaseArtistList").classList.add("opacity05"), a[b].innerText);getQueue()}else if("Browse"==app.current.app&&"Playlists"==app.current.tab)document.getElementById("BrowsePlaylistsList").classList.add("opacity05"),sendAPI({cmd:"MPD_API_GET_PLAYLISTS",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Database"==app.current.tab&&"Artist"==app.current.view)document.getElementById("BrowseDatabaseArtistList").classList.add("opacity05"),sendAPI({cmd:"MPD_API_GET_ARTISTS",
sendAPI({cmd:"MPD_API_GET_ARTISTS",data:{offset:app.current.page,filter:app.current.filter}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter");else if("Browse"==app.current.app&&"Database"==app.current.tab&&"Album"==app.current.view)document.getElementById("BrowseDatabaseAlbumCards").classList.add("opacity05"),sendAPI({cmd:"MPD_API_GET_ARTISTALBUMS",data:{offset:app.current.page,filter:app.current.filter,albumartist:app.current.search}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter"); data:{offset:app.current.page,filter:app.current.filter}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter");else if("Browse"==app.current.app&&"Database"==app.current.tab&&"Album"==app.current.view)document.getElementById("BrowseDatabaseAlbumCards").classList.add("opacity05"),sendAPI({cmd:"MPD_API_GET_ARTISTALBUMS",data:{offset:app.current.page,filter:app.current.filter,albumartist:app.current.search}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter");else if("Browse"==app.current.app&&
else if("Browse"==app.current.app&&"Filesystem"==app.current.tab){document.getElementById("BrowseFilesystemList").classList.add("opacity05");sendAPI({cmd:"MPD_API_GET_FILESYSTEM",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("BrowseFilesystemAddAllSongs").setAttribute("disabled","disabled");a='<li class="breadcrumb-item"><a data-uri="">root</a></li>'; "Filesystem"==app.current.tab){document.getElementById("BrowseFilesystemList").classList.add("opacity05");sendAPI({cmd:"MPD_API_GET_FILESYSTEM",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("BrowseFilesystemAddAllSongs").setAttribute("disabled","disabled");a='<li class="breadcrumb-item"><a data-uri="">root</a></li>';
var c=app.current.search.split("/"),e=c.length,d="";for(b=0;b<e;b++){if(e-1==b){a+='<li class="breadcrumb-item active">'+c[b]+"</li>";break}d+=c[b];a+='<li class="breadcrumb-item"><a data-uri="'+d+'">'+c[b]+"</a></li>";d+="/"}b=document.getElementById("BrowseBreadcrumb");b.innerHTML=a;a=b.getElementsByTagName("a");c=a.length;for(b=0;b<c;b++)a[b].addEventListener("click",function(){appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else if("Search"== var c=app.current.search.split("/"),e=c.length,d="";for(b=0;b<e;b++){if(e-1==b){a+='<li class="breadcrumb-item active">'+c[b]+"</li>";break}d+=c[b];a+='<li class="breadcrumb-item"><a data-uri="'+d+'">'+c[b]+"</a></li>";d+="/"}b=document.getElementById("BrowseBreadcrumb");b.innerHTML=a;a=b.getElementsByTagName("a");c=a.length;for(b=0;b<c;b++)a[b].addEventListener("click",function(){appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else if("Search"==
app.current.app)for(document.getElementById("searchstr2").focus(),document.getElementById("SearchList").classList.add("opacity05"),app.last.app!=app.current.app&&(""!=app.current.search?document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="5">Searching...</td></tr>':setPagination(app.current.page),document.getElementById("searchstr2").value=app.current.search),2<=app.current.search.length?sendAPI({cmd:"MPD_API_SEARCH", app.current.app)for(document.getElementById("searchstr").focus(),document.getElementById("SearchList").classList.add("opacity05"),app.last.app!=app.current.app&&""!=app.current.search&&(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="5">Searching...</td></tr>'),2<=app.current.search.length?sendAPI({cmd:"MPD_API_SEARCH",data:{mpdtag:app.current.filter,offset:app.current.page,searchstr:app.current.search}},
data:{mpdtag:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseSearch):(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML="",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",setPagination(app.current.page),document.getElementById("SearchList").classList.remove("opacity05")),a=document.getElementById("searchtags2").getElementsByTagName("button"),c= parseSearch):(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML="",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",document.getElementById("SearchList").classList.remove("opacity05")),a=document.getElementById("searchtags").getElementsByTagName("button"),c=a.length,b=0;b<c;b++)a[b].classList.remove("active"),a[b].innerText==app.current.filter&&(a[b].classList.add("active"),
a.length,b=0;b<c;b++)a[b].classList.remove("active"),a[b].innerText==app.current.filter&&(a[b].classList.add("active"),document.getElementById("searchtags2desc").innerText=a[b].innerText);else appGoto("Playback");app.last.app=app.current.app;app.last.tab=app.current.tab;app.last.view=app.current.view}else appGoto("Playback")} document.getElementById("searchtagsdesc").innerText=a[b].innerText);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(){getSettings();sendAPI({cmd:"MPD_API_GET_OUTPUTNAMES"},parseOutputnames);webSocketConnect();domCache.volumeBar.value=0;domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_SET_VOLUME",data:{volume:domCache.volumeBar.value}})},!1);domCache.progressBar.value=0;domCache.progressBar.addEventListener("change",function(a){current_song&&0<=current_song.currentSongId&&sendAPI({cmd:"MPD_API_SET_SEEK",data:{songid:current_song.currentSongId,seek:Math.ceil(domCache.progressBar.value/ function appInit(){getSettings();sendAPI({cmd:"MPD_API_GET_OUTPUTNAMES"},parseOutputnames);webSocketConnect();domCache.volumeBar.value=0;domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_SET_VOLUME",data:{volume:domCache.volumeBar.value}})},!1);domCache.progressBar.value=0;domCache.progressBar.addEventListener("change",function(a){current_song&&0<=current_song.currentSongId&&sendAPI({cmd:"MPD_API_SET_SEEK",data:{songid:current_song.currentSongId,seek:Math.ceil(domCache.progressBar.value/
100*current_song.totalTime)}})},!1);document.getElementById("modalAbout").addEventListener("shown.bs.modal",function(){sendAPI({cmd:"MPD_API_GET_STATS"},parseStats)});document.getElementById("modalSettings").addEventListener("shown.bs.modal",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")}); 100*current_song.totalTime)}})},!1);document.getElementById("modalAbout").addEventListener("shown.bs.modal",function(){sendAPI({cmd:"MPD_API_GET_STATS"},parseStats)});document.getElementById("modalSettings").addEventListener("shown.bs.modal",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("modalAddstream").addEventListener("shown.bs.modal",function(){document.getElementById("streamurl").focus()});document.getElementById("addstreamFrm").addEventListener("submit",function(){addStream()});document.getElementById("mainMenu").addEventListener("shown.bs.dropdown",function(){var a=document.getElementById("inputSearch");a.value="";a.focus()});document.getElementById("inputSearch").addEventListener("click",function(a){a.stopPropagation()});addFilterLetter("BrowseFilesystemFilterLetters"); document.getElementById("modalAddstream").addEventListener("shown.bs.modal",function(){document.getElementById("streamurl").focus()});document.getElementById("addstreamFrm").addEventListener("submit",function(){addStream()});addFilterLetter("BrowseFilesystemFilterLetters");addFilterLetter("BrowseDatabaseFilterLetters");addFilterLetter("BrowsePlaylistsFilterLetters");for(var a=document.querySelectorAll("button[data-href], a[data-href]"),b=a.length,c=0;c<b;c++)a[c].addEventListener("click",function(a){a.preventDefault();
addFilterLetter("BrowseDatabaseFilterLetters");addFilterLetter("BrowsePlaylistsFilterLetters");for(var a=document.querySelectorAll("button[data-href], a[data-href]"),b=a.length,c=0;c<b;c++)a[c].addEventListener("click",function(a){a.preventDefault();a.stopPropagation();a=JSON.parse(this.getAttribute("data-href").replace(/'/g,'"'));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))}}, a.stopPropagation();a=JSON.parse(this.getAttribute("data-href").replace(/'/g,'"'));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))}},!1);a=document.querySelectorAll(".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("outputs").addEventListener("click",
!1);a=document.querySelectorAll(".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("outputs").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&a.stopPropagation();sendAPI({cmd:"MPD_API_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("QueueList").addEventListener("click", function(a){"BUTTON"==a.target.nodeName&&a.stopPropagation();sendAPI({cmd:"MPD_API_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("QueueList").addEventListener("click",function(a){"TD"==a.target.nodeName?sendAPI({cmd:"MPD_API_PLAY_TRACK",data:{track:a.target.parentNode.getAttribute("data-trackid")}}):"A"==a.target.nodeName&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("BrowseFilesystemList").addEventListener("click",
function(a){"TD"==a.target.nodeName?sendAPI({cmd:"MPD_API_PLAY_TRACK",data:{track:a.target.parentNode.getAttribute("data-trackid")}}):"A"==a.target.nodeName&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("BrowseFilesystemList").addEventListener("click",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"))); 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"==
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&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("BrowsePlaylistsList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")), a.target.nodeName&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("BrowsePlaylistsList").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&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("BrowseDatabaseArtistList").addEventListener("click",function(a){"TD"==a.target.nodeName&&appGoto("Browse","Database",
a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("BrowseDatabaseArtistList").addEventListener("click",function(a){"TD"==a.target.nodeName&&appGoto("Browse","Database","Album","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")): "Album","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&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("searchtags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,
"A"==a.target.nodeName&&(a.preventDefault(),showMenu(a.target))},!1);document.getElementById("searchtags2").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,"0/"+a.target.innerText+"/"+app.current.search)},!1);document.getElementById("searchqueuestr").addEventListener("keyup",function(a){appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+this.value)},!1);document.getElementById("searchqueuetag").addEventListener("click", "0/"+a.target.innerText+"/"+app.current.search)},!1);document.getElementById("searchqueuestr").addEventListener("keyup",function(a){appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+this.value)},!1);document.getElementById("searchqueuetag").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+a.target.innerText+"/"+app.current.search)},!1);document.getElementById("search").addEventListener("submit",
function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+a.target.innerText+"/"+app.current.search)},!1);document.getElementById("inputSearch").addEventListener("keypress",function(a){13==a.which&&mainMenu.toggle()},!1);document.getElementById("search").addEventListener("submit",function(){var a=document.getElementById("inputSearch").value;appGoto("Search",void 0,void 0,app.current.page+"/Any Tag/"+a);document.getElementById("searchstr2").value= function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit",function(){return!1},!1);document.getElementById("searchstr").addEventListener("keyup",function(a){appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+this.value)},!1);window.addEventListener("hashchange",appRoute,!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName){switch(a.which){case 37:clickPrev();break;case 39:clickNext();break;case 32:clickPlay();break;default:return}a.preventDefault()}},
a;return!1},!1);document.getElementById("search2").addEventListener("submit",function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit",function(){return!1},!1);document.getElementById("searchstr2").addEventListener("keyup",function(a){appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+this.value)},!1);window.addEventListener("hashchange",appRoute,!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName){switch(a.which){case 37:clickPrev(); !1);"serviceWorker"in navigator&&window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.js",{scope:"/"}).then(function(a){console.log("ServiceWorker registration successful with scope: ",a.scope)},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")});
break;case 39:clickNext();break;case 32:clickPlay();break;default:return}a.preventDefault()}},!1)} 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("appinstalled")})}
function webSocketConnect(){socket=new WebSocket(getWsUrl());try{socket.onopen=function(){console.log("connected");showNotification("Connected to myMPD","","","success");modalConnectionError.hide();appRoute()},socket.onmessage=function(a){if(a.data!==last_state&&0!=a.data.length){try{var b=JSON.parse(a.data)}catch(c){console.log("Invalid JSON data received: "+a.data)}switch(b.type){case "state":parseState(b);break;case "disconnected":showNotification("myMPD lost connection to MPD","","","danger"); function webSocketConnect(){socket=new WebSocket(getWsUrl());try{socket.onopen=function(){console.log("connected");showNotification("Connected to myMPD","","","success");modalConnectionError.hide();appRoute()},socket.onmessage=function(a){if(a.data!==last_state&&0!=a.data.length){try{var b=JSON.parse(a.data)}catch(c){console.log("Invalid JSON data received: "+a.data)}switch(b.type){case "state":parseState(b);break;case "disconnected":showNotification("myMPD lost connection to MPD","","","danger");
break;case "update_queue":"Queue"===app.current.app&&getQueue();break;case "song_change":songChange(b);break;case "error":showNotification(b.data,"","","danger")}}},socket.onclose=function(){console.log("disconnected");modalConnectionError.show();setTimeout(function(){console.log("reconnect");webSocketConnect()},3E3)}}catch(a){alert("Error: "+a)}} break;case "update_queue":"Queue"===app.current.app&&getQueue();break;case "song_change":songChange(b);break;case "error":showNotification(b.data,"","","danger")}}},socket.onclose=function(){console.log("disconnected");modalConnectionError.show();setTimeout(function(){console.log("reconnect");webSocketConnect()},3E3)}}catch(a){alert("Error: "+a)}}
function getWsUrl(){var a=document.URL;if("https"==a.substring(0,5)){var b="wss://";a=a.substr(8)}else b="ws://","http"==a.substring(0,4)&&(a=a.substr(7));a=a.split("#");var c=/\/$/.test(a[0])?"":"/";return b+a[0]+c+"ws"} function getWsUrl(){var a=document.URL;if("https"==a.substring(0,5)){var b="wss://";a=a.substr(8)}else b="ws://","http"==a.substring(0,4)&&(a=a.substr(7));a=a.split("#");var c=/\/$/.test(a[0])?"":"/";return b+a[0]+c+"ws"}
@ -50,11 +50,12 @@ function parseState(a){if(JSON.stringify(a)!==JSON.stringify(last_state)){1==a.d
0==a.data.queue_length?domCache.btnPlay.setAttribute("disabled","disabled"):domCache.btnPlay.removeAttribute("disabled");-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.volumeIcon.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume;current_song.totalTime= 0==a.data.queue_length?domCache.btnPlay.setAttribute("disabled","disabled"):domCache.btnPlay.removeAttribute("disabled");-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.volumeIcon.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume;current_song.totalTime=
a.data.totalTime;current_song.currentSongId=a.data.currentsongid;var b=Math.floor(a.data.totalTime/60),c=a.data.totalTime-60*b,e=Math.floor(a.data.elapsedTime/60),d=a.data.elapsedTime-60*e;domCache.progressBar.value=Math.floor(100*a.data.elapsedTime/a.data.totalTime);b=e+":"+(10>d?"0":"")+d+" / "+b+":"+(10>c?"0":"")+c;domCache.counter.innerText=b;last_state&&(c=document.getElementById("queueTrackId"+last_state.data.currentsongid))&&(e=c.getElementsByTagName("td"),e[4].innerText=c.getAttribute("data-duration"), a.data.totalTime;current_song.currentSongId=a.data.currentsongid;var b=Math.floor(a.data.totalTime/60),c=a.data.totalTime-60*b,e=Math.floor(a.data.elapsedTime/60),d=a.data.elapsedTime-60*e;domCache.progressBar.value=Math.floor(100*a.data.elapsedTime/a.data.totalTime);b=e+":"+(10>d?"0":"")+d+" / "+b+":"+(10>c?"0":"")+c;domCache.counter.innerText=b;last_state&&(c=document.getElementById("queueTrackId"+last_state.data.currentsongid))&&(e=c.getElementsByTagName("td"),e[4].innerText=c.getAttribute("data-duration"),
e[0].classList.remove("material-icons"),e[0].innerText=c.getAttribute("data-songpos"),c.classList.remove("font-weight-bold"));if(c=document.getElementById("queueTrackId"+a.data.currentsongid))e=c.getElementsByTagName("td"),e[4].innerText=b,e[0].classList.add("material-icons"),e[0].innerText="play_arrow",c.classList.add("font-weight-bold");void 0!=last_state&&a.data.queue_version==last_state.data.queue_version||sendAPI({cmd:"MPD_API_GET_CURRENT_SONG"},songChange);b=a.data.outputs.length;for(c=0;c< e[0].classList.remove("material-icons"),e[0].innerText=c.getAttribute("data-songpos"),c.classList.remove("font-weight-bold"));if(c=document.getElementById("queueTrackId"+a.data.currentsongid))e=c.getElementsByTagName("td"),e[4].innerText=b,e[0].classList.add("material-icons"),e[0].innerText="play_arrow",c.classList.add("font-weight-bold");void 0!=last_state&&a.data.queue_version==last_state.data.queue_version||sendAPI({cmd:"MPD_API_GET_CURRENT_SONG"},songChange);b=a.data.outputs.length;for(c=0;c<
b;c++)toggleBtn("btnoutput"+a.data.outputs[c].id,a.data.outputs[c].state);last_state=a}}function getQueue(){2<=app.current.search.length?sendAPI({cmd:"MPD_API_SEARCH_QUEUE",data:{mpdtag:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseQueue):sendAPI({cmd:"MPD_API_GET_QUEUE",data:{offset:app.current.page}},parseQueue)} b;c++)toggleBtn("btnoutput"+a.data.outputs[c].id,a.data.outputs[c].state);last_state=a}}
function parseQueue(a){if("Queue"===app.current.app){0<a.totalTime?document.getElementById("panel-heading-queue").innerText=a.totalEntities+" Songs \u2013 "+beautifyDuration(a.totalTime):0<a.totalEntities?document.getElementById("panel-heading-queue").innerText=a.totalEntities+" Songs":document.getElementById("panel-heading-queue").innerText="";for(var b=a.data.length,c=document.getElementById(app.current.app+"List").getElementsByTagName("tbody")[0],e=c.getElementsByTagName("tr"),d=0;d<b;d++)if(!e[d]|| function getQueue(){if(2<=app.current.search.length)sendAPI({cmd:"MPD_API_SEARCH_QUEUE",data:{mpdtag:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseQueue);else{var a=document.getElementById("QueueList").getAttribute("data-version");last_state&&a!=last_state.data.queue_version?sendAPI({cmd:"MPD_API_GET_QUEUE",data:{offset:app.current.page}},parseQueue):document.getElementById("QueueList").classList.remove("opacity05")}}
e[d].getAttribute("data-trackid")!=a.data[d].id){var f=Math.floor(a.data[d].duration/60),g=a.data[d].duration-60*f;f=f+":"+(10>g?"0":"")+g;g=document.createElement("tr");g.setAttribute("data-trackid",a.data[d].id);g.setAttribute("id","queueTrackId"+a.data[d].id);g.setAttribute("data-songpos",a.data[d].pos+1);g.setAttribute("data-duration",f);g.setAttribute("data-uri",a.data[d].uri);g.innerHTML="<td>"+(a.data[d].pos+1)+"</td><td>"+a.data[d].title+"</td><td>"+a.data[d].artist+"</td><td>"+a.data[d].album+ function parseQueue(a){if("Queue"===app.current.app){0<a.totalTime?document.getElementById("panel-heading-queue").innerText=a.totalEntities+" Songs \u2013 "+beautifyDuration(a.totalTime):0<a.totalEntities?document.getElementById("panel-heading-queue").innerText=a.totalEntities+" Songs":document.getElementById("panel-heading-queue").innerText="";var b=a.data.length,c=document.getElementById(app.current.app+"List");c.setAttribute("data-version",a.queue_version);c=c.getElementsByTagName("tbody")[0];
"</td><td>"+f+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';d<e.length?e[d].replaceWith(g):c.append(g)}for(d=e.length-1;d>=b;d--)e[d].remove();"queuesearch"==a.type&&0==b?c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="5">No results, please refine your search!</td></tr>':"queue"==a.type&&0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="5">Empty queue</td></tr>');setPagination(a.totalEntities); for(var e=c.getElementsByTagName("tr"),d=0;d<b;d++)if(!e[d]||e[d].getAttribute("data-trackid")!=a.data[d].id){var f=Math.floor(a.data[d].duration/60),g=a.data[d].duration-60*f;f=f+":"+(10>g?"0":"")+g;g=document.createElement("tr");g.setAttribute("data-trackid",a.data[d].id);g.setAttribute("id","queueTrackId"+a.data[d].id);g.setAttribute("data-songpos",a.data[d].pos+1);g.setAttribute("data-duration",f);g.setAttribute("data-uri",a.data[d].uri);g.innerHTML="<td>"+(a.data[d].pos+1)+"</td><td>"+a.data[d].title+
document.getElementById("QueueList").classList.remove("opacity05")}}function parseSearch(a){"Search"===app.current.app&&(document.getElementById("panel-heading-search").innerHTML=a.totalEntities+" Songs found",0<a.totalEntities?document.getElementById("searchAddAllSongs").removeAttribute("disabled"):document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),parseFilesystem(a))} "</td><td>"+a.data[d].artist+"</td><td>"+a.data[d].album+"</td><td>"+f+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';d<e.length?e[d].replaceWith(g):c.append(g)}for(d=e.length-1;d>=b;d--)e[d].remove();"queuesearch"==a.type&&0==b?c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="5">No results, please refine your search!</td></tr>':"queue"==a.type&&0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="5">Empty queue</td></tr>');
setPagination(a.totalEntities);document.getElementById("QueueList").classList.remove("opacity05")}}function parseSearch(a){"Search"===app.current.app&&(document.getElementById("panel-heading-search").innerHTML=a.totalEntities+" Songs found",0<a.totalEntities?document.getElementById("searchAddAllSongs").removeAttribute("disabled"):document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),parseFilesystem(a))}
function parseFilesystem(a){if("Browse"===app.current.app||"Filesystem"===app.current.tab||"Search"===app.current.app){for(var b=a.data.length,c=document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+"List").getElementsByTagName("tbody")[0],e=c.getElementsByTagName("tr"),d=0;d<b;d++){var f=encodeURI(a.data[d].uri);if(!e[d]||e[d].getAttribute("data-uri")!=f){var g=document.createElement("tr");g.setAttribute("data-type",a.data[d].type);g.setAttribute("data-uri",f);g.setAttribute("data-name", function parseFilesystem(a){if("Browse"===app.current.app||"Filesystem"===app.current.tab||"Search"===app.current.app){for(var b=a.data.length,c=document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+"List").getElementsByTagName("tbody")[0],e=c.getElementsByTagName("tr"),d=0;d<b;d++){var f=encodeURI(a.data[d].uri);if(!e[d]||e[d].getAttribute("data-uri")!=f){var g=document.createElement("tr");g.setAttribute("data-type",a.data[d].type);g.setAttribute("data-uri",f);g.setAttribute("data-name",
a.data[d].name);switch(a.data[d].type){case "dir":g.innerHTML='<td><span class="material-icons">folder_open</span></td><td colspan="4">'+a.data[d].name+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';break;case "song":f=Math.floor(a.data[d].duration/60);var h=a.data[d].duration-60*f;g.innerHTML='<td><span class="material-icons">music_note</span></td><td>'+a.data[d].title+"</td><td>"+a.data[d].artist+"</td><td>"+a.data[d].album+"</td><td>"+f+":"+(10>h?"0":"")+h+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>'; a.data[d].name);switch(a.data[d].type){case "dir":g.innerHTML='<td><span class="material-icons">folder_open</span></td><td colspan="4">'+a.data[d].name+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';break;case "song":f=Math.floor(a.data[d].duration/60);var h=a.data[d].duration-60*f;g.innerHTML='<td><span class="material-icons">music_note</span></td><td>'+a.data[d].title+"</td><td>"+a.data[d].artist+"</td><td>"+a.data[d].album+"</td><td>"+f+":"+(10>h?"0":"")+h+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';
break;case "plist":g.innerHTML='<td><span class="material-icons">list</span></td><td colspan="4">'+a.data[d].name+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>'}d<e.length?e[d].replaceWith(g):c.append(g)}}for(d=e.length-1;d>=b;d--)e[d].remove();setPagination(a.totalEntities);0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="5">No results</td></tr>');document.getElementById(app.current.app+(void 0==app.current.tab?"": break;case "plist":g.innerHTML='<td><span class="material-icons">list</span></td><td colspan="4">'+a.data[d].name+'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>'}d<e.length?e[d].replaceWith(g):c.append(g)}}for(d=e.length-1;d>=b;d--)e[d].remove();setPagination(a.totalEntities);0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="5">No results</td></tr>');document.getElementById(app.current.app+(void 0==app.current.tab?"":
@ -94,8 +95,8 @@ mixrampdb:document.getElementById("inputMixrampdb").value,mixrampdelay:document.
function addAllFromBrowse(){sendAPI({cmd:"MPD_API_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")}function addAllFromSearch(){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_SEARCH_ADD",data:{filter:app.current.filter,searchstr:app.current.search}}),showNotification("Added "+parseInt(document.getElementById("panel-heading-search").innerText)+" songs from search","","","success"))} function addAllFromBrowse(){sendAPI({cmd:"MPD_API_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")}function addAllFromSearch(){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_SEARCH_ADD",data:{filter:app.current.filter,searchstr:app.current.search}}),showNotification("Added "+parseInt(document.getElementById("panel-heading-search").innerText)+" songs from search","","","success"))}
function scrollTo(a){document.body.scrollTop=a;document.documentElement.scrollTop=a}function gotoPage(a){switch(a){case "next":app.current.page+=settings.max_elements_per_page;break;case "prev":app.current.page-=settings.max_elements_per_page;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 scrollTo(a){document.body.scrollTop=a;document.documentElement.scrollTop=a}function gotoPage(a){switch(a){case "next":app.current.page+=settings.max_elements_per_page;break;case "prev":app.current.page-=settings.max_elements_per_page;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 addStream(){var a=document.getElementById("streamurl");""!=a.value&&sendAPI({cmd:"MPD_API_ADD_TRACK",data:{uri:a.value}});a.value="";modalAddstream.hide()}function saveQueue(){var a=document.getElementById("playlistname");""!=a.value&&sendAPI({cmd:"MPD_API_SAVE_QUEUE",data:{plist:a.value}});a.value="";modalSavequeue.hide()} function addStream(){var a=document.getElementById("streamurl");""!=a.value&&sendAPI({cmd:"MPD_API_ADD_TRACK",data:{uri:a.value}});a.value="";modalAddstream.hide()}function saveQueue(){var a=document.getElementById("playlistname");""!=a.value&&sendAPI({cmd:"MPD_API_SAVE_QUEUE",data:{plist:a.value}});a.value="";modalSavequeue.hide()}
function showNotification(a,b,c,e){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.classList.add("alert","alert-"+e),b.innerHTML="<div><strong>"+a+"</strong>"+c+"</div>",document.getElementsByTagName("main")[0].append(b),document.getElementById("alertBox").classList.add("alertBoxActive"), function showNotification(a,b,c,e){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.add("alert","alert-"+e),b.innerHTML="<div><strong>"+a+"</strong>"+c+"</div>",document.getElementsByTagName("main")[0].append(b),
alertTimeout&&clearTimeout(alertTimeout),alertTimeout=setTimeout(function(){document.getElementById("alertBox")&&document.getElementById("alertBox").classList.remove("alertBoxActive");setTimeout(function(){document.getElementById("alertBox").remove()},600)},3E3))}function notificationsSupported(){return"Notification"in window} 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(){document.getElementById("alertBox").remove()},600))}function notificationsSupported(){return"Notification"in window}
function songChange(a){var b=a.data.title+a.data.artist+a.data.album+a.data.uri+a.data.currentsongid;if(last_song!=b){var c="",e="",d="myMPD: ";document.getElementById("album-cover").style.backgroundImage='url("'+a.data.cover+'")';"undefined"!=typeof a.data.artist&&0<a.data.artist.length&&"-"!=a.data.artist?(c+=a.data.artist,e+="<br/>"+a.data.artist,d+=a.data.artist+" - ",document.getElementById("artist").innerText=a.data.artist):document.getElementById("artist").innerText="";"undefined"!=typeof a.data.album&& function songChange(a){var b=a.data.title+a.data.artist+a.data.album+a.data.uri+a.data.currentsongid;if(last_song!=b){var c="",e="",d="myMPD: ";document.getElementById("album-cover").style.backgroundImage='url("'+a.data.cover+'")';"undefined"!=typeof a.data.artist&&0<a.data.artist.length&&"-"!=a.data.artist?(c+=a.data.artist,e+="<br/>"+a.data.artist,d+=a.data.artist+" - ",document.getElementById("artist").innerText=a.data.artist):document.getElementById("artist").innerText="";"undefined"!=typeof a.data.album&&
0<a.data.album.length&&"-"!=a.data.album?(c+=" - "+a.data.album,e+="<br/>"+a.data.album,document.getElementById("album").innerText=a.data.album):document.getElementById("album").innerText="";"undefined"!=typeof a.data.title&&0<a.data.title.length?(d+=a.data.title,document.getElementById("currenttrack").innerText=a.data.title):document.getElementById("currenttrack").innerText="";document.title=d;if(d=document.getElementById("queueTrackId"+a.data.currentsongid))d.getElementsByTagName("td")[1].innerText= 0<a.data.album.length&&"-"!=a.data.album?(c+=" - "+a.data.album,e+="<br/>"+a.data.album,document.getElementById("album").innerText=a.data.album):document.getElementById("album").innerText="";"undefined"!=typeof a.data.title&&0<a.data.title.length?(d+=a.data.title,document.getElementById("currenttrack").innerText=a.data.title):document.getElementById("currenttrack").innerText="";document.title=d;if(d=document.getElementById("queueTrackId"+a.data.currentsongid))d.getElementsByTagName("td")[1].innerText=
a.data.title;showNotification(a.data.title,c,e,"success");last_song=b}}function doSetFilterLetter(a){var b=document.getElementById(a+"Letters").querySelector(".active");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,e=0;e<c;e++)if(a[e].innerText==b){a[e].classList.add("active");break}}} a.data.title;showNotification(a.data.title,c,e,"success");last_song=b}}function doSetFilterLetter(a){var b=document.getElementById(a+"Letters").querySelector(".active");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,e=0;e<c;e++)if(a[e].innerText==b){a[e].classList.add("active");break}}}

21
htdocs/mympd.webmanifest Normal file
View File

@ -0,0 +1,21 @@
{
"background_color": "#343a40",
"theme_color": "#343a40",
"description": "myMPD - fast and lightweight MPD webclient",
"display": "standalone",
"icons": [
{
"src": "assets/appicon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/appicon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"name": "myMPD",
"short_name": "myMPD",
"start_url": "/index.html"
}

56
htdocs/sw.js Normal file
View File

@ -0,0 +1,56 @@
var CACHE_NAME = 'myMPD-cache-v1';
var urlsToCache = [
'/',
'/player.html',
'/css/bootstrap.min.css',
'/css/mpd.css',
'/js/bootstrap-native-v4.min.js',
'/js/mpd.js',
'/js/player.js',
'/assets/appicon-167.png',
'/assets/appicon-192.png',
'/assets/appicon-512.png',
'/assets/coverimage-httpstream.png',
'/assets/coverimage-notavailable.png',
'/assets/favicon.ico',
'/assets/MaterialIcons-Regular.eot',
'/assets/MaterialIcons-Regular.ttf',
'/assets/MaterialIcons-Regular.woff',
'/assets/MaterialIcons-Regular.woff2'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
// Cache hit - return response
if (response)
return response
else
return fetch(event.request);
})
);
});
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['myMPD-cache-v1'];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1)
return caches.delete(cacheName);
})
);
})
);
});

14
htdocs/sw.min.js vendored Normal file
View File

@ -0,0 +1,14 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,f){a!=Array.prototype&&a!=Object.prototype&&(a[b]=f.value)};$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var a=0;return function(b){return $jscomp.SYMBOL_PREFIX+(b||"")+a++}}();
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(a){var b=0;return $jscomp.iteratorPrototype(function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}})};
$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};$jscomp.iteratorFromArray=function(a,b){$jscomp.initSymbolIterator();a instanceof String&&(a+="");var f=0,e={next:function(){if(f<a.length){var c=f++;return{value:b(c,a[c]),done:!1}}e.next=function(){return{done:!0,value:void 0}};return e.next()}};e[Symbol.iterator]=function(){return e};return e};
$jscomp.polyfill=function(a,b,f,e){if(b){f=$jscomp.global;a=a.split(".");for(e=0;e<a.length-1;e++){var c=a[e];c in f||(f[c]={});f=f[c]}a=a[a.length-1];e=f[a];b=b(e);b!=e&&null!=b&&$jscomp.defineProperty(f,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Array.prototype.keys",function(a){return a?a:function(){return $jscomp.iteratorFromArray(this,function(a){return a})}},"es6","es3");
$jscomp.makeIterator=function(a){$jscomp.initSymbolIterator();var b=a[Symbol.iterator];return b?b.call(a):$jscomp.arrayIterator(a)};$jscomp.FORCE_POLYFILL_PROMISE=!1;
$jscomp.polyfill("Promise",function(a){function b(){this.batch_=null}function f(d){return d instanceof c?d:new c(function(a,b){a(d)})}if(a&&!$jscomp.FORCE_POLYFILL_PROMISE)return a;b.prototype.asyncExecute=function(a){null==this.batch_&&(this.batch_=[],this.asyncExecuteBatch_());this.batch_.push(a);return this};b.prototype.asyncExecuteBatch_=function(){var a=this;this.asyncExecuteFunction(function(){a.executeBatch_()})};var e=$jscomp.global.setTimeout;b.prototype.asyncExecuteFunction=function(a){e(a,
0)};b.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var a=this.batch_;this.batch_=[];for(var g=0;g<a.length;++g){var b=a[g];a[g]=null;try{b()}catch(k){this.asyncThrow_(k)}}}this.batch_=null};b.prototype.asyncThrow_=function(a){this.asyncExecuteFunction(function(){throw a;})};var c=function(a){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];var d=this.createResolveAndReject_();try{a(d.resolve,d.reject)}catch(h){d.reject(h)}};c.prototype.createResolveAndReject_=
function(){function a(a){return function(d){c||(c=!0,a.call(b,d))}}var b=this,c=!1;return{resolve:a(this.resolveTo_),reject:a(this.reject_)}};c.prototype.resolveTo_=function(a){if(a===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(a instanceof c)this.settleSameAsPromise_(a);else{a:switch(typeof a){case "object":var d=null!=a;break a;case "function":d=!0;break a;default:d=!1}d?this.resolveToNonPromiseObj_(a):this.fulfill_(a)}};c.prototype.resolveToNonPromiseObj_=function(a){var d=
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)}}:
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_NAME="myMPD-cache-v1",urlsToCache="/ /player.html /css/bootstrap.min.css /css/mpd.css /js/bootstrap-native-v4.min.js /js/mpd.js /js/player.js /assets/appicon-167.png /assets/appicon-192.png /assets/appicon-512.png /assets/coverimage-httpstream.png /assets/coverimage-notavailable.png /assets/favicon.ico /assets/MaterialIcons-Regular.eot /assets/MaterialIcons-Regular.ttf /assets/MaterialIcons-Regular.woff /assets/MaterialIcons-Regular.woff2".split(" ");
self.addEventListener("install",function(a){a.waitUntil(caches.open(CACHE_NAME).then(function(a){console.log("Opened cache");return a.addAll(urlsToCache)}))});self.addEventListener("fetch",function(a){a.respondWith(caches.match(a.request).then(function(b){return b?b:fetch(a.request)}))});self.addEventListener("activate",function(a){var b=["myMPD-cache-v1"];a.waitUntil(caches.keys().then(function(a){return Promise.all(a.map(function(a){if(-1===b.indexOf(a))return caches.delete(a)}))}))});

View File

@ -2,26 +2,35 @@
if [ -f buildtools/closure-compiler.jar ] if [ -f buildtools/closure-compiler.jar ]
then then
echo "Minifying javascript"
[ htdocs/js/player.js -nt htdocs/js/player.min.js ] && \ [ htdocs/js/player.js -nt htdocs/js/player.min.js ] && \
java -jar buildtools/closure-compiler.jar htdocs/js/player.js > htdocs/js/player.min.js java -jar buildtools/closure-compiler.jar htdocs/js/player.js > htdocs/js/player.min.js
[ htdocs/js/mpd.js -nt htdocs/js/mpd.min.js ] && \ [ htdocs/js/mpd.js -nt htdocs/js/mpd.min.js ] && \
java -jar buildtools/closure-compiler.jar htdocs/js/mpd.js > htdocs/js/mpd.min.js java -jar buildtools/closure-compiler.jar htdocs/js/mpd.js > htdocs/js/mpd.min.js
[ htdocs/sw.js -nt htdocs/sw.min.js ] && \
java -jar buildtools/closure-compiler.jar htdocs/sw.js > htdocs/sw.min.js
else else
echo "buildtools/closure-compiler.jar not found, using non-minified files"
[ htdocs/js/player.js -nt htdocs/js/player.min.js ] && \ [ htdocs/js/player.js -nt htdocs/js/player.min.js ] && \
cp htdocs/js/player.js htdocs/js/player.min.js cp htdocs/js/player.js htdocs/js/player.min.js
[ htdocs/js/mpd.js -nt htdocs/js/mpd.min.js ] && \ [ htdocs/js/mpd.js -nt htdocs/js/mpd.min.js ] && \
cp htdocs/js/mpd.js htdocs/js/mpd.min.js cp htdocs/js/mpd.js htdocs/js/mpd.min.js
[ htdocs/sw.js -nt htdocs/sw.min.js ] && \
cp htdocs/sw.js htdocs/sw.min.js
fi fi
if [ -f buildtools/closure-stylesheets.jar ] if [ -f buildtools/closure-stylesheets.jar ]
then then
echo "Minifying stylesheets"
[ htdocs/css/mpd.css -nt htdocs/css/mpd.min.css ] && \ [ htdocs/css/mpd.css -nt htdocs/css/mpd.min.css ] && \
java -jar buildtools/closure-stylesheets.jar htdocs/css/mpd.css > htdocs/css/mpd.min.css java -jar buildtools/closure-stylesheets.jar htdocs/css/mpd.css > htdocs/css/mpd.min.css
else else
echo "buildtools/closure-stylesheets.jar not found, using non-minified files"
[ htdocs/css/mpd.css -nt htdocs/css/mpd.min.css ] && \ [ htdocs/css/mpd.css -nt htdocs/css/mpd.min.css ] && \
cp htdocs/css/mpd.css htdocs/css/mpd.min.css cp htdocs/css/mpd.css htdocs/css/mpd.min.css
fi fi
echo "Compiling and installing mympd"
[ -d release ] || mkdir release [ -d release ] || mkdir release
cd release cd release
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=RELEASE .. cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=RELEASE ..
@ -29,15 +38,45 @@ make
sudo make install sudo make install
cd .. cd ..
echo "Replacing javascript and stylesheets with minified files"
sudo sed -i -e 's/mpd\.css/mpd\.min\.css/' -e 's/mpd\.js/mpd\.min\.js/' /usr/share/mympd/htdocs/index.html sudo sed -i -e 's/mpd\.css/mpd\.min\.css/' -e 's/mpd\.js/mpd\.min\.js/' /usr/share/mympd/htdocs/index.html
sudo sed -i -e 's/mpd\.css/mpd\.min\.css/' -e 's/player\.js/player\.min\.js/' /usr/share/mympd/htdocs/player.html sudo sed -i -e 's/mpd\.css/mpd\.min\.css/' -e 's/player\.js/player\.min\.js/' /usr/share/mympd/htdocs/player.html
sudo sed -i -e 's/mpd\.css/mpd\.min\.css/' -e 's/mpd\.js/mpd\.min\.js/' -e 's/player\.js/player\.min\.js/' /usr/share/mympd/htdocs/sw.min.js
sudo sed -i -e 's/\/sw\.js/\/sw\.min\.js/' /usr/share/mympd/htdocs/js/mpd.min.js
echo "Minifying html"
perl -i -pe 's/^\s*//gm; s/\s*$//gm' /usr/share/mympd/htdocs/index.html
perl -i -pe 's/^\s*//gm; s/\s*$//gm' /usr/share/mympd/htdocs/player.html
echo "Fixing ownership of /var/lib/mympd"
sudo chown nobody /var/lib/mympd sudo chown nobody /var/lib/mympd
[ -d /etc/systemd/system ] && \ echo "Trying to link musicdir to library"
sudo cp -v contrib/mympd.service /etc/systemd/system/ if [ -f /etc/mpd.conf ]
then
LIBRARY=$(grep music /etc/mpd.conf | awk {'print $2'})
[ "$LIBRARY" != "" ] && [ ! -e /usr/share/mympd/htdocs/library ] && ln -s $LIBRARY /usr/share/mympd/htdocs/library
else
echo "/etc/mpd.conf not found, you must link your musicdir manually to /usr/share/mympd/htdocs/library"
fi
[ -d /etc/default ] && \ echo "Installing systemd service"
sudo cp -v contrib/mympd.default /etc/default/mympd if [ -d /etc/systemd/system ]
then
if [ contrib/mympd.service -nt /etc/systemd/system/mympd.service ]
then
sudo cp -v contrib/mympd.service /etc/systemd/system/
systemctl daemon-reload
fi
systemctl enable mympd
fi
if [ -d /etc/mympd/ssl ]
then
echo "Certificates already created"
else
echo "Creating certificates"
contrib/crcert.sh
fi
echo "myMPD installed" echo "myMPD installed"
echo "Edit /etc/mympd/options before starting mympd"

View File

@ -9479,7 +9479,6 @@ static struct mg_ws_proto_data *mg_ws_get_proto_data(struct mg_connection *nc) {
*/ */
static void mg_ws_close(struct mg_connection *nc, const void *data, static void mg_ws_close(struct mg_connection *nc, const void *data,
size_t len) { size_t len) {
printf("SEND: %.*s\n",len,data);
if ((int) len == ~0) { if ((int) len == ~0) {
len = strlen((const char *) data); len = strlen((const char *) data);
} }

View File

@ -4682,7 +4682,7 @@ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
#ifdef __GNUC__ #ifdef __GNUC__
__attribute__((deprecated)); __attribute__((deprecated));
#endif #endif
;
/* /*
* Gets and parses the Authorization: Basic header * Gets and parses the Authorization: Basic header

View File

@ -80,7 +80,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
case MPD_API_SET_SETTINGS: case MPD_API_SET_SETTINGS:
json_scanf(msg.p, msg.len, "{ data: { notificationWeb: %d, notificationPage: %d} }", &state.a, &state.b); json_scanf(msg.p, msg.len, "{ data: { notificationWeb: %d, notificationPage: %d} }", &state.a, &state.b);
char tmpfile[200]; char tmpfile[200];
snprintf(tmpfile,200,"%s.tmp",mpd.statefile); snprintf(tmpfile,200,"%s.tmp", mpd.statefile);
json_fprintf(tmpfile, "{ notificationWeb: %d, notificationPage: %d}", state.a, state.b); json_fprintf(tmpfile, "{ notificationWeb: %d, notificationPage: %d}", state.a, state.b);
rename(tmpfile,mpd.statefile); rename(tmpfile,mpd.statefile);
@ -113,8 +113,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
mpd_run_mixrampdelay(mpd.conn, float_buf); mpd_run_mixrampdelay(mpd.conn, float_buf);
je = json_scanf(msg.p, msg.len, "{ data: { replaygain:%Q } }", &p_charbuf1); je = json_scanf(msg.p, msg.len, "{ data: { replaygain:%Q } }", &p_charbuf1);
if (je == 1) if (je == 1) {
{
mpd_send_command(mpd.conn, "replay_gain_mode", p_charbuf1, NULL); mpd_send_command(mpd.conn, "replay_gain_mode", p_charbuf1, NULL);
struct mpd_pair *pair; struct mpd_pair *pair;
while ((pair = mpd_recv_pair(mpd.conn)) != NULL) { while ((pair = mpd_recv_pair(mpd.conn)) != NULL) {
@ -168,8 +167,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
break; break;
case MPD_API_MOVE_TRACK: case MPD_API_MOVE_TRACK:
je = json_scanf(msg.p, msg.len, "{ data: { track:%u, pos:%u } }", &uint_buf1, &uint_buf2); je = json_scanf(msg.p, msg.len, "{ data: { track:%u, pos:%u } }", &uint_buf1, &uint_buf2);
if (je == 2) if (je == 2) {
{
uint_buf1 -= 1; uint_buf1 -= 1;
uint_buf2 -= 1; uint_buf2 -= 1;
mpd_run_move(mpd.conn, uint_buf1, uint_buf2); mpd_run_move(mpd.conn, uint_buf1, uint_buf2);
@ -354,7 +352,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
} }
free(cmd); free(cmd);
if(mpd.conn_state == MPD_CONNECTED && mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) if (mpd.conn_state == MPD_CONNECTED && mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS)
{ {
#ifdef DEBUG #ifdef DEBUG
fprintf(stdout,"Error: %s\n",mpd_connection_get_error_message(mpd.conn)); fprintf(stdout,"Error: %s\n",mpd_connection_get_error_message(mpd.conn));
@ -367,8 +365,8 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
mpd.conn_state = MPD_FAILURE; mpd.conn_state = MPD_FAILURE;
} }
if(n > 0) { if (n > 0) {
if(is_websocket(nc)) { if (is_websocket(nc)) {
#ifdef DEBUG #ifdef DEBUG
fprintf(stdout,"Send websocket response:\n %s\n",mpd.buf); fprintf(stdout,"Send websocket response:\n %s\n",mpd.buf);
#endif #endif
@ -383,21 +381,19 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
} }
} }
int mympd_close_handler(struct mg_connection *c) int mympd_close_handler(struct mg_connection *c) {
{
/* Cleanup session data */ /* Cleanup session data */
if(c->user_data) if (c->user_data)
free(c->user_data); free(c->user_data);
return 0; return 0;
} }
static int mympd_notify_callback(struct mg_connection *c, const char *param) { static int mympd_notify_callback(struct mg_connection *c, const char *param) {
size_t n; size_t n;
if(!is_websocket(c)) if (!is_websocket(c))
return 0; return 0;
if(param) if (param) {
{
/* error message? */ /* error message? */
n=snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",param); n=snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",param);
#ifdef DEBUG #ifdef DEBUG
@ -419,15 +415,13 @@ static int mympd_notify_callback(struct mg_connection *c, const char *param) {
#endif #endif
mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n); mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n);
} }
else else {
{
#ifdef DEBUG #ifdef DEBUG
fprintf(stdout,"Notify: %s\n",mpd.buf); fprintf(stdout,"Notify: %s\n",mpd.buf);
#endif #endif
mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, strlen(mpd.buf)); mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, strlen(mpd.buf));
if(s->song_id != mpd.song_id) if(s->song_id != mpd.song_id) {
{
n=mympd_put_current_song(mpd.buf); n=mympd_put_current_song(mpd.buf);
#ifdef DEBUG #ifdef DEBUG
fprintf(stdout,"Notify: %s\n",mpd.buf); fprintf(stdout,"Notify: %s\n",mpd.buf);
@ -436,8 +430,7 @@ static int mympd_notify_callback(struct mg_connection *c, const char *param) {
s->song_id = mpd.song_id; s->song_id = mpd.song_id;
} }
if(s->queue_version != mpd.queue_version) if(s->queue_version != mpd.queue_version) {
{
n=snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"update_queue\"}"); n=snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"update_queue\"}");
#ifdef DEBUG #ifdef DEBUG
fprintf(stdout,"Notify: update_queue\n"); fprintf(stdout,"Notify: update_queue\n");
@ -445,14 +438,11 @@ static int mympd_notify_callback(struct mg_connection *c, const char *param) {
mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n); mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n);
s->queue_version = mpd.queue_version; s->queue_version = mpd.queue_version;
} }
} }
return 0; return 0;
} }
void mympd_poll(struct mg_mgr *s) void mympd_poll(struct mg_mgr *s) {
{
switch (mpd.conn_state) { switch (mpd.conn_state) {
case MPD_DISCONNECTED: case MPD_DISCONNECTED:
/* Try to connect */ /* Try to connect */
@ -466,19 +456,16 @@ void mympd_poll(struct mg_mgr *s)
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) { if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) {
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn)); fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
{
mympd_notify_callback(c, mpd_connection_get_error_message(mpd.conn)); mympd_notify_callback(c, mpd_connection_get_error_message(mpd.conn));
} }
mpd.conn_state = MPD_FAILURE; mpd.conn_state = MPD_FAILURE;
return; return;
} }
if(mpd.password && !mpd_run_password(mpd.conn, mpd.password)) if(mpd.password && !mpd_run_password(mpd.conn, mpd.password)) {
{
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn)); fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
{
mympd_notify_callback(c, mpd_connection_get_error_message(mpd.conn)); mympd_notify_callback(c, mpd_connection_get_error_message(mpd.conn));
} }
mpd.conn_state = MPD_FAILURE; mpd.conn_state = MPD_FAILURE;
@ -503,19 +490,17 @@ void mympd_poll(struct mg_mgr *s)
case MPD_CONNECTED: case MPD_CONNECTED:
mpd.buf_size = mympd_put_state(mpd.buf, &mpd.song_id, &mpd.next_song_id, &mpd.queue_version); mpd.buf_size = mympd_put_state(mpd.buf, &mpd.song_id, &mpd.next_song_id, &mpd.queue_version);
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
{
mympd_notify_callback(c, NULL); mympd_notify_callback(c, NULL);
} }
break; break;
} }
} }
char* mympd_get_tag(struct mpd_song const *song, enum mpd_tag_type tag) char* mympd_get_tag(struct mpd_song const *song, enum mpd_tag_type tag) {
{
char *str; char *str;
str = (char *)mpd_song_get_tag(song, tag, 0); str = (char *)mpd_song_get_tag(song, tag, 0);
if (str == NULL){ if (str == NULL) {
if (tag == MPD_TAG_TITLE) if (tag == MPD_TAG_TITLE)
str = basename((char *)mpd_song_get_uri(song)); str = basename((char *)mpd_song_get_uri(song));
else else
@ -524,8 +509,7 @@ char* mympd_get_tag(struct mpd_song const *song, enum mpd_tag_type tag)
return str; return str;
} }
int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsigned *queue_version) int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsigned *queue_version) {
{
struct mpd_status *status; struct mpd_status *status;
const struct mpd_audio_format *audioformat; const struct mpd_audio_format *audioformat;
struct mpd_output *output; struct mpd_output *output;
@ -593,8 +577,7 @@ int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsi
return len; return len;
} }
int mympd_put_welcome(char *buffer) int mympd_put_welcome(char *buffer) {
{
int len; int len;
struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE); struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE);
@ -604,8 +587,7 @@ int mympd_put_welcome(char *buffer)
return len; return len;
} }
int mympd_put_settings(char *buffer) int mympd_put_settings(char *buffer) {
{
struct mpd_status *status; struct mpd_status *status;
char *replaygain; char *replaygain;
int len; int len;
@ -667,8 +649,7 @@ int mympd_put_settings(char *buffer)
} }
int mympd_put_outputnames(char *buffer) int mympd_put_outputnames(char *buffer) {
{
struct mpd_output *output; struct mpd_output *output;
int len; int len;
int nr; int nr;
@ -697,8 +678,7 @@ int mympd_put_outputnames(char *buffer)
return len; return len;
} }
int mympd_get_cover(const char *uri, char *cover, int cover_len) int mympd_get_cover(const char *uri, char *cover, int cover_len) {
{
char *path=strdup(uri); char *path=strdup(uri);
int len; int len;
if (strncasecmp("http:",path,5) == 0 ) { if (strncasecmp("http:",path,5) == 0 ) {
@ -716,8 +696,7 @@ int mympd_get_cover(const char *uri, char *cover, int cover_len)
return len; return len;
} }
int mympd_put_current_song(char *buffer) int mympd_put_current_song(char *buffer) {
{
struct mpd_song *song; struct mpd_song *song;
int len; int len;
struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE); struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE);
@ -750,8 +729,7 @@ int mympd_put_current_song(char *buffer)
return len; return len;
} }
int mympd_put_songdetails(char *buffer, char *uri) int mympd_put_songdetails(char *buffer, char *uri) {
{
struct mpd_entity *entity; struct mpd_entity *entity;
const struct mpd_song *song; const struct mpd_song *song;
int len; int len;
@ -783,8 +761,7 @@ int mympd_put_songdetails(char *buffer, char *uri)
return len; return len;
} }
int mympd_put_queue(char *buffer, unsigned int offset) int mympd_put_queue(char *buffer, unsigned int offset) {
{
struct mpd_entity *entity; struct mpd_entity *entity;
unsigned long totalTime = 0; unsigned long totalTime = 0;
unsigned long entity_count = 0; unsigned long entity_count = 0;
@ -821,19 +798,19 @@ int mympd_put_queue(char *buffer, unsigned int offset)
mpd_entity_free(entity); mpd_entity_free(entity);
} }
len += json_printf(&out, "],totalTime: %d, totalEntities: %d, offset: %d, returnedEntities: %d }", len += json_printf(&out, "],totalTime: %d, totalEntities: %d, offset: %d, returnedEntities: %d, queue_version: %d }",
totalTime, totalTime,
entity_count, entity_count,
offset, offset,
entities_returned entities_returned,
mpd.queue_version
); );
if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n");
return len; return len;
} }
int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter) int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter) {
{
struct mpd_entity *entity; struct mpd_entity *entity;
const struct mpd_playlist *pl; const struct mpd_playlist *pl;
unsigned int entity_count = 0; unsigned int entity_count = 0;
@ -941,8 +918,7 @@ int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter
return len; return len;
} }
int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char *mpdsearchtagtype, char *searchstr, char *filter) int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char *mpdsearchtagtype, char *searchstr, char *filter) {
{
struct mpd_pair *pair; struct mpd_pair *pair;
unsigned long entity_count = 0; unsigned long entity_count = 0;
unsigned long entities_returned = 0; unsigned long entities_returned = 0;
@ -994,8 +970,7 @@ int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char *
return len; return len;
} }
int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album) int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album) {
{
struct mpd_song *song; struct mpd_song *song;
unsigned long entity_count = 0; unsigned long entity_count = 0;
unsigned long entities_returned = 0; unsigned long entities_returned = 0;
@ -1046,8 +1021,7 @@ int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album)
return len; return len;
} }
int mympd_put_playlists(char *buffer, unsigned int offset, char *filter) int mympd_put_playlists(char *buffer, unsigned int offset, char *filter) {
{
struct mpd_playlist *pl; struct mpd_playlist *pl;
unsigned int entity_count = 0; unsigned int entity_count = 0;
unsigned int entities_returned = 0; unsigned int entities_returned = 0;
@ -1093,8 +1067,7 @@ int mympd_put_playlists(char *buffer, unsigned int offset, char *filter)
return len; return len;
} }
int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr) int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr) {
{
struct mpd_song *song; struct mpd_song *song;
unsigned long entity_count = 0; unsigned long entity_count = 0;
unsigned long entities_returned = 0; unsigned long entities_returned = 0;
@ -1147,8 +1120,7 @@ int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *sear
return len; return len;
} }
int mympd_search_add(char *buffer,char *mpdtagtype, char *searchstr) int mympd_search_add(char *buffer,char *mpdtagtype, char *searchstr) {
{
struct mpd_song *song; struct mpd_song *song;
int len; int len;
struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE); struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE);
@ -1178,8 +1150,7 @@ int mympd_search_add(char *buffer,char *mpdtagtype, char *searchstr)
return len; return len;
} }
int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr) int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr) {
{
struct mpd_song *song; struct mpd_song *song;
unsigned long entity_count = 0; unsigned long entity_count = 0;
unsigned long entities_returned = 0; unsigned long entities_returned = 0;
@ -1232,8 +1203,7 @@ int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char
return len; return len;
} }
int mympd_get_stats(char *buffer) int mympd_get_stats(char *buffer) {
{
struct mpd_stats *stats = mpd_run_stats(mpd.conn); struct mpd_stats *stats = mpd_run_stats(mpd.conn);
const unsigned *version = mpd_connection_get_server_version(mpd.conn); const unsigned *version = mpd_connection_get_server_version(mpd.conn);
char mpd_version[20]; char mpd_version[20];
@ -1262,8 +1232,7 @@ int mympd_get_stats(char *buffer)
return len; return len;
} }
void mympd_disconnect() void mympd_disconnect() {
{
mpd.conn_state = MPD_DISCONNECT; mpd.conn_state = MPD_DISCONNECT;
mympd_poll(NULL); mympd_poll(NULL);
} }

View File

@ -37,6 +37,7 @@
extern char *optarg; extern char *optarg;
static sig_atomic_t s_signal_received = 0; static sig_atomic_t s_signal_received = 0;
static struct mg_serve_http_opts s_http_server_opts; static struct mg_serve_http_opts s_http_server_opts;
char s_redirect[250];
static void signal_handler(int sig_num) { static void signal_handler(int sig_num) {
signal(sig_num, signal_handler); // Reinstantiate signal handler signal(sig_num, signal_handler); // Reinstantiate signal handler
@ -44,14 +45,14 @@ static void signal_handler(int sig_num) {
} }
static void handle_api(struct mg_connection *nc, struct http_message *hm) { static void handle_api(struct mg_connection *nc, struct http_message *hm) {
if(!is_websocket(nc)) { 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"); 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}; char buf[1000] = {0};
memcpy(buf, hm->body.p,sizeof(buf) - 1 < hm->body.len ? sizeof(buf) - 1 : hm->body.len); memcpy(buf, hm->body.p,sizeof(buf) - 1 < hm->body.len ? sizeof(buf) - 1 : hm->body.len);
struct mg_str d = {buf, strlen(buf)}; struct mg_str d = {buf, strlen(buf)};
callback_mympd(nc, d); callback_mympd(nc, d);
if(!is_websocket(nc)) { if (!is_websocket(nc)) {
mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */ mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
} }
} }
@ -82,7 +83,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
case MG_EV_CLOSE: { case MG_EV_CLOSE: {
if (is_websocket(nc)) { if (is_websocket(nc)) {
#ifdef DEBUG #ifdef DEBUG
fprintf(stdout,"Websocket connection closed\n"); printf("Websocket connection closed\n");
#endif #endif
mympd_close_handler(nc); mympd_close_handler(nc);
} }
@ -96,35 +97,59 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
} }
} }
int main(int argc, char **argv) static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data) {
{ switch(ev) {
case MG_EV_HTTP_REQUEST: {
printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
}
}
int main(int argc, char **argv) {
int n, option_index = 0; int n, option_index = 0;
struct mg_mgr mgr; struct mg_mgr mgr;
struct mg_connection *nc; struct mg_connection *nc;
struct mg_connection *nc_http;
unsigned int current_timer = 0, last_timer = 0; unsigned int current_timer = 0, last_timer = 0;
char *run_as_user = NULL; char *run_as_user = NULL;
char *webport = "80"; char *webport = "80";
char *sslport = "443";
mpd.port = 6600; mpd.port = 6600;
strcpy(mpd.host, "127.0.0.1"); strcpy(mpd.host, "127.0.0.1");
streamport = 8000; streamport = 8000;
strcpy(coverimage, "folder.jpg"); strcpy(coverimage, "folder.jpg");
mpd.statefile="/var/lib/mympd/mympd.state"; mpd.statefile = "/var/lib/mympd/mympd.state";
struct mg_bind_opts bind_opts;
const char *err;
bool ssl = false;
char *s_ssl_cert = "/etc/mympd/ssl/server.pem";
char *s_ssl_key = "/etc/mympd/ssl/server.key";
char hostname[1024];
hostname[1023] = '\0';
gethostname(hostname, 1023);
static struct option long_options[] = { static struct option long_options[] = {
{"host", required_argument, 0, 'h'}, {"mpdhost", required_argument, 0, 'h'},
{"port", required_argument, 0, 'p'}, {"mpdport", required_argument, 0, 'p'},
{"webport", required_argument, 0, 'w'},
{"user", required_argument, 0, 'u'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 0 },
{"mpdpass", required_argument, 0, 'm'}, {"mpdpass", required_argument, 0, 'm'},
{"webport", required_argument, 0, 'w'},
{"ssl", no_argument, 0, 'S'},
{"sslport", required_argument, 0, 'W'},
{"sslcert", required_argument, 0, 'C'},
{"sslkey", required_argument, 0, 'K'},
{"user", required_argument, 0, 'u'},
{"streamport", required_argument, 0, 's'}, {"streamport", required_argument, 0, 's'},
{"coverimage", required_argument, 0, 'i'}, {"coverimage", required_argument, 0, 'i'},
{"statefile", required_argument, 0, 't'}, {"statefile", required_argument, 0, 't'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 0 },
{0, 0, 0, 0 } {0, 0, 0, 0 }
}; };
while((n = getopt_long(argc, argv, "D:h:p:w:u:vm:s:i:c:t:", while((n = getopt_long(argc, argv, "h:p:w:SW:C:K:u:vm:s:i:t:",
long_options, &option_index)) != -1) { long_options, &option_index)) != -1) {
switch (n) { switch (n) {
case 't': case 't':
@ -139,6 +164,18 @@ int main(int argc, char **argv)
case 'w': case 'w':
webport = strdup(optarg); webport = strdup(optarg);
break; break;
case 'S':
ssl = true;
break;
case 'W':
sslport = strdup(optarg);
break;
case 'C':
s_ssl_cert = strdup(optarg);
break;
case 'K':
s_ssl_key = strdup(optarg);
break;
case 'u': case 'u':
run_as_user = strdup(optarg); run_as_user = strdup(optarg);
break; break;
@ -155,7 +192,7 @@ int main(int argc, char **argv)
case 'v': case 'v':
fprintf(stdout, "myMPD %d.%d.%d\n" fprintf(stdout, "myMPD %d.%d.%d\n"
"Copyright (C) 2018 Juergen Mang <mail@jcgames.de>\n" "Copyright (C) 2018 Juergen Mang <mail@jcgames.de>\n"
"Built " __DATE__ " "__TIME__ "\n", "Built " __DATE__ " "__TIME__"\n",
MYMPD_VERSION_MAJOR, MYMPD_VERSION_MINOR, MYMPD_VERSION_PATCH); MYMPD_VERSION_MAJOR, MYMPD_VERSION_MINOR, MYMPD_VERSION_PATCH);
return EXIT_SUCCESS; return EXIT_SUCCESS;
break; break;
@ -163,7 +200,11 @@ int main(int argc, char **argv)
fprintf(stderr, "Usage: %s [OPTION]...\n\n" fprintf(stderr, "Usage: %s [OPTION]...\n\n"
" -h, --host <host>\t\tconnect to mpd at host [localhost]\n" " -h, --host <host>\t\tconnect to mpd at host [localhost]\n"
" -p, --port <port>\t\tconnect to mpd at port [6600]\n" " -p, --port <port>\t\tconnect to mpd at port [6600]\n"
" -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\n" " -w, --webport [ip:]<port>\tlisten interface/port for webserver [80]\n"
" -S, --ssl\tenable ssl\n"
" -W, --sslport [ip:]<port>\tlisten interface/port for ssl webserver [443]\n"
" -C, --sslcert <filename>\tfilename for ssl certificate [/etc/mympd/ssl/server.pem]\n"
" -K, --sslkey <filename>\tfilename for ssl key [/etc/mympd/ssl/server.key]\n"
" -u, --user <username>\t\tdrop priviliges to user after socket bind\n" " -u, --user <username>\t\tdrop priviliges to user after socket bind\n"
" -v, --version\t\t\tget version\n" " -v, --version\t\t\tget version\n"
" -m, --mpdpass <password>\tspecifies the password to use when connecting to mpd\n" " -m, --mpdpass <password>\tspecifies the password to use when connecting to mpd\n"
@ -184,11 +225,30 @@ int main(int argc, char **argv)
mg_mgr_init(&mgr, NULL); mg_mgr_init(&mgr, NULL);
if (ssl == true) {
snprintf(s_redirect, 200, "https://%s:%s/", hostname, sslport);
nc_http = mg_bind(&mgr, webport, ev_handler_http);
if (nc_http == NULL) {
fprintf(stderr, "Error starting server on port %s\n", webport );
return EXIT_FAILURE;
}
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.ssl_cert = s_ssl_cert;
bind_opts.ssl_key = s_ssl_key;
bind_opts.error_string = &err;
nc = mg_bind_opt(&mgr, sslport, ev_handler, bind_opts);
if (nc == NULL) {
fprintf(stderr, "Error starting server on port %s: %s\n", sslport, err);
return EXIT_FAILURE;
}
}
else {
nc = mg_bind(&mgr, webport, ev_handler); nc = mg_bind(&mgr, webport, ev_handler);
if (nc == NULL) { if (nc == NULL) {
fprintf(stderr, "Error starting server on port %s\n", webport); fprintf(stderr, "Error starting server on port %s\n", webport );
return EXIT_FAILURE; return EXIT_FAILURE;
} }
}
if(run_as_user != NULL) { if(run_as_user != NULL) {
printf("Droping privileges\n"); printf("Droping privileges\n");
@ -197,7 +257,7 @@ int main(int argc, char **argv)
printf("Unknown user\n"); printf("Unknown user\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} else if (setgid(pw->pw_gid) != 0) { } else if (setgid(pw->pw_gid) != 0) {
printf("setgid() failed"); printf("setgid() failed\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} else if (setuid(pw->pw_uid) != 0) { } else if (setuid(pw->pw_uid) != 0) {
printf("setuid() failed\n"); printf("setuid() failed\n");
@ -211,15 +271,21 @@ int main(int argc, char **argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (ssl == true)
mg_set_protocol_http_websocket(nc_http);
mg_set_protocol_http_websocket(nc); mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = SRC_PATH; s_http_server_opts.document_root = SRC_PATH;
s_http_server_opts.enable_directory_listing = "no";
printf("myMPD started on http port %s\n", webport);
if (ssl == true)
printf("myMPD started on ssl port %s\n", sslport);
printf("myMPD started on port %s\n", webport);
while (s_signal_received == 0) { while (s_signal_received == 0) {
mg_mgr_poll(&mgr, 200); mg_mgr_poll(&mgr, 200);
current_timer = time(NULL); current_timer = time(NULL);
if(current_timer - last_timer) if (current_timer - last_timer) {
{
last_timer = current_timer; last_timer = current_timer;
mympd_poll(&mgr); mympd_poll(&mgr);
} }