1
0
mirror of https://github.com/SuperBFG7/ympd synced 2024-10-31 20:16:17 +00:00

Compare commits

...

149 Commits

Author SHA1 Message Date
SuperBFG7
297691ab5b move panel header to list panel 2022-08-10 17:30:46 +02:00
SuperBFG7
b41753220f don't show queue length when browsing 2022-08-10 17:30:24 +02:00
SuperBFG7
7a725e648d Revert "Fix handling of multiple client sessions (browsers)"
This reverts commit ac7ad51a0e.
2021-08-24 14:13:23 +02:00
SuperBFG7
ac7ad51a0e Fix handling of multiple client sessions (browsers) 2021-08-24 14:12:06 +02:00
SuperBFG7
3c70301789 Added authentication for webservice connection. 2021-08-23 17:23:48 +02:00
SuperBFG7
f5080c8419 linting 2021-07-29 11:57:55 +02:00
SuperBFG7
7b5496e06c fix handling of special characters in paths 2021-06-24 10:16:21 +02:00
SuperBFG7
4d30fff4bc hide volume controls when volume is not supported by the current output 2021-06-21 09:28:34 +02:00
SuperBFG7
2b461aafa2 Only show love button when either mpdas or mpdscribble channel is
present and use the correct channel to send the message of love
2021-06-17 20:50:15 +02:00
SuperBFG7
fd727cc20b linted 2021-06-17 11:43:45 +02:00
SuperBFG7
ebf5b3894d add mpdscribble support for love button 2021-06-17 10:07:52 +02:00
SuperBFG7
8669750a4b Browse: rename root to Library 2021-06-17 09:57:36 +02:00
SuperBFG7
28844275e6 Enable volume slider again
Add volume number
Move love button to song info box
2021-06-17 09:55:45 +02:00
SuperBFG7
86586fbcee Fix position only on big screens 2021-06-17 09:51:36 +02:00
SuperBFG7
886e390d09 Separate song info box and queue list
Fix position of song info box and control buttons (don't scroll out of screen)
2021-06-17 08:53:22 +02:00
SuperBFG7
d5294675c4 Removed standalone player 2021-06-16 17:27:12 +02:00
SuperBFG7
883ea7ca06 Added bash script to run linters to tools
Ran prettier on .clang-format
2021-06-16 17:18:25 +02:00
SuperBFG7
61e385a35d Merge branch 'tandell-linter' 2021-06-15 10:01:41 +02:00
Andrew Carr
9a0cf4a43b Configured and ran clang-format on src files 2021-05-23 04:09:25 +00:00
Andrew Carr
b35e705066 Configured and ran Prettier on client files 2021-05-23 03:15:19 +00:00
SuperBFG7
9d1a3ccfb2 Merge branch 'tandell-remove-dirble-2' 2021-05-21 10:24:07 +02:00
Andrew Carr
3fe238b169 Dirble option and functionality removed
Dirble is no longer available and per the author/old-owner, will probably not
be coming back.

This commit removes the configuration option and the code used to access
dirble. Everything else still appears to be functional.

re: https://dev.to/confact/what-happened-with-dirble-k3g
2021-05-21 01:38:19 +00:00
SuperBFG7
d1416d8f34 Added option for automatic playback of mpd stream.
When activated in settings, ympd will atempt to play the mpd stream
(ogg/mp3) locally in the browser whenever mpd is playing.

This allows to run a headless instance on a PC with sound and start/stop
playback also from other instances or mpd clients.
e.g. run ympd with autoplay on your private PC with decent loudspeakers,
    but control it with another instance on your office laptop without
    the need to switch PCs.
2021-03-23 19:25:39 +01:00
SuperBFG7
5e6d090bd8 Merge branch 'dafanz-master' 2021-03-09 13:03:22 +01:00
SuperBFG7
bd95651d61 removed two obsolete lines 2021-03-09 13:03:04 +01:00
Dafan Zhai
efa4d4443f Fixed invisible "Add all" button in browse page 2021-03-07 20:07:57 +01:00
Daniel Schregenberger
df5a48f99f
Merge pull request #31 from SuperBFG7/compile
added forward declarations
2020-06-02 12:44:06 +02:00
SuperBFG7
2268e0f0f3 added forward declarations 2020-06-02 12:41:52 +02:00
Daniel Schregenberger
91700a2496
Merge pull request #29 from SuperBFG7/harden
Harden
2019-04-22 11:53:51 +02:00
Daniel Schregenberger
e6e08d6159
Merge branch 'master' into harden 2019-04-22 11:53:24 +02:00
SuperBFG7
fff184dca2 moved default user definition to service file 2019-04-22 11:51:09 +02:00
SuperBFG7
aeceb91551 moved definition of default user to service file 2019-04-22 11:47:22 +02:00
Clément Pit-Claudel
0917b467e8
Harden ympd.service
This offers a measure of protection against potential ympd vulnerabilities.  See
https://www.freedesktop.org/software/systemd/man/systemd.exec.html for
documentation.
2019-03-10 16:43:39 +00:00
SuperBFG7
b9c268b847 fix layout 2018-06-03 12:11:26 +02:00
SuperBFG7
6340dcde72 Merge branch 'filter' 2018-06-03 12:06:07 +02:00
SuperBFG7
a3ae8cd802 fix filtering with pagination 2018-06-03 11:57:13 +02:00
SuperBFG7
f89c49e09c fix filtering of songs 2018-05-12 09:53:33 +02:00
Daniel Schregenberger
9bf518ed07
Merge pull request #25 from SuperBFG7/filter
make A-Z filters work also on songs and playlists
2018-05-11 21:12:39 +02:00
SuperBFG7
db72139490 make A-Z filters work also on songs and playlists 2018-05-11 21:10:22 +02:00
Daniel Schregenberger
2db7b711f7
Merge pull request #24 from SuperBFG7/filter
filtering on all levels and fix playlist filter
2018-05-11 17:19:57 +02:00
Daniel Schregenberger
5ffdc5e50a
Merge branch 'master' into filter 2018-05-11 17:19:50 +02:00
SuperBFG7
b0ec3be02f filtering on all levels and fix playlist filter 2018-05-11 17:18:17 +02:00
Daniel Schregenberger
93ec197f79
Merge pull request #23 from SuperBFG7/filter
add filtering on all levels and fix playlist filter
2018-05-11 16:59:55 +02:00
Daniel Schregenberger
3774a35f4c
Merge branch 'master' into filter 2018-05-11 16:59:07 +02:00
SuperBFG7
61ad51ffb7 add filtering on all levels and fix playlist filter 2018-05-11 16:49:12 +02:00
Daniel Schregenberger
1e96a921c5
Merge pull request #22 from SuperBFG7/revert-18-master
Revert "Typo error prevend webradio playlists to show."
2018-05-11 15:35:00 +02:00
Daniel Schregenberger
4be2db91e4
Revert "Typo error prevend webradio playlists to show." 2018-05-11 15:34:38 +02:00
Daniel Schregenberger
86a924cddd
Merge pull request #21 from SuperBFG7/revert-20-master
Revert "typo fix as per emilot commit"
2018-05-11 15:06:04 +02:00
Daniel Schregenberger
1588e21c08
Revert "typo fix as per emilot commit" 2018-05-11 15:05:31 +02:00
Daniel Schregenberger
2f4c737f09
Merge pull request #20 from archphile/master
typo fix as per emilot commit
2018-05-10 13:29:27 +02:00
Daniel Schregenberger
68cbe37a84
Merge pull request #19 from andreevskya/DisplayCurrentTrackInPageTitle
[Issue 128] Current track in page title
2018-05-10 12:03:04 +02:00
Daniel Schregenberger
ffe1305c3c
Merge pull request #18 from Emilot/master
Typo error prevend webradio playlists to show.
2018-05-10 12:01:36 +02:00
Archphile
870da6f9a1
typo fix as per emilot commit
Please see: https://github.com/notandy/ympd/pull/175
2018-05-10 10:22:44 +03:00
Emilot
b787c782d1
Typo error prevend webradio playlists to show. 2018-05-10 02:43:57 +03:00
andreevksya
ac55e8a604 [Issue 128] Current track in page title 2018-05-09 20:27:33 +00:00
SuperBFG7
e1cb953e94 layout improvements 2018-04-23 13:38:58 +02:00
SuperBFG7
ebd357a24a fix layout 2018-04-22 20:48:18 +02:00
SuperBFG7
d08092b069 Merge remote-tracking branch 'eb041592/queue_nowrap' 2018-04-22 18:59:05 +02:00
SuperBFG7
265e5f76a1 Merge remote-tracking branch 'eb041592/queue_time' 2018-04-22 18:57:02 +02:00
SuperBFG7
63be6f0de0 Merge remote-tracking branch 'upstream/master' 2018-04-22 18:33:10 +02:00
eb041592
c9e200a3ef Prevent (almost) any wrapping to keep the interface "clean". 2018-04-10 21:18:35 +02:00
eb041592
2cb99de852 Revert some changes: There's no need to print the artist/album for each song while browsing. 2018-04-05 00:59:39 +02:00
eb041592
18417b9ed5 Reset #panel-heading-info on update of #panel-heading. 2018-04-04 23:06:19 +02:00
eb041592
c226026a51 Treat album while browsing the same way as in the queue. 2018-04-04 23:06:19 +02:00
eb041592
84b3ea71a4 Switch queue's line break before album from HTML to CSS. 2018-04-04 23:06:19 +02:00
eb041592
0faf70ad05 Add queue's total time to panel header. 2018-04-04 23:06:19 +02:00
eb041592
54e334a2a0 Added additional "totalTime" field to "obj" for the queue's total time. 2018-04-04 23:06:19 +02:00
Andy
612f8fc0b2
Merge pull request #158 from eb041592/master
Update FreeBSD RC script
2018-03-29 20:37:43 +02:00
Andy
17b97c8f6b
Merge pull request #159 from eb041592/unclutter_interface
Unclutter Interface
2018-03-29 20:36:25 +02:00
Andy
a7d9bf85f5
Merge pull request #160 from eb041592/verbose_lists
Add artist and album to queue and search results
2018-03-29 20:36:03 +02:00
Andy
0e58a0d5c7
Merge pull request #162 from archphile/master
fixed a typo
2018-03-29 20:34:21 +02:00
Andy
eea4a382a4
Merge pull request #163 from ffontaine/master
ympd is implemented only in C
2018-03-29 20:32:23 +02:00
SuperBFG7
2fa653711d fix defaults 2018-03-25 13:52:41 +02:00
SuperBFG7
26b2ca3408 add option "localport" to exclude ports from auth
usefull if ympd listens on two ports: one (local) for intranet usage and
one for internet usage
2018-03-25 13:45:16 +02:00
Daniel Schregenberger
5e834e56ff
Merge pull request #16 from ffontaine/master
ympd is implemented only in C
2018-03-12 17:51:19 +01:00
Fabrice Fontaine
f53c0956d4 ympd is implemented only in C
By default, CMake assumes that the project is using both C and C++. By
explicitly passing 'C' as argument of the project() macro, we tell
CMake that only C is used, which prevents CMake from checking if a C++
compiler exists.

Patch applied to buildroot since 2014:
https://git.buildroot.net/buildroot/commit/package/ympd?id=40aa523af26963321443a2d96c64ce128577ca77

Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
2018-03-08 18:02:26 +01:00
Archphile
d4865aca9d
fixed a typo
Replaced $DIRBLE_API_TOKE with $DIRBLE_API_TOKEN in order for the service to be functional.
2018-03-05 13:54:46 +02:00
eb041592
b4084034d0 Reverted some formatting; aligned CSS 2018-02-05 01:03:48 +01:00
eb041592
bd9775de8e Align the labels for the trash mode buttons with display:inline-block 2018-02-05 00:38:20 +01:00
eb041592
b3037d6117 Hide Dirble app, if no API token is given/available 2018-02-04 13:43:37 +01:00
eb041592
b091e47d2f Removed default for dirble_api_token from ympd.c 2018-02-04 01:50:28 +01:00
eb041592
037e7db1da Minor formatting 2018-02-04 01:11:41 +01:00
eb041592
2e9d477ee6 Hide output button(s) if there is only one configured output 2018-02-04 00:30:45 +01:00
eb041592
73a844eb71 Fixed error with "... added" notifications 2018-02-03 16:34:22 +01:00
eb041592
1b0484a285 Extended text field for stream url across entire width of dialog 2018-02-03 15:06:59 +01:00
eb041592
226e3b5999 Moved button for notifications to settings 2018-02-03 14:51:48 +01:00
SuperBFG7
17436bff40 fix browse for directories and playlists 2018-02-03 13:02:02 +01:00
SuperBFG7
7af873b793 remove password from service file 2018-02-03 12:51:20 +01:00
eb041592
fe0277bfbe Rearranged artist and album information in queue and search results. 2018-02-03 12:03:33 +01:00
eb041592
3fe7d9c44c Some more spelling 2018-02-02 02:40:40 +01:00
eb041592
cf572b705e Added artist and album to both queue and search results; minor spelling 2018-02-02 02:14:31 +01:00
eb041592
80834b989f Update FreeBSD RC script
- removed /usr/sbin/daemon's option "-t" (first appeared in FreeBSD 11, leading to an error in previous versions; since the [t]itle defaults to the daemonized invocation anyways, this shouldn't make a difference for FreeBSD 11+)
- removed /usr/sbin/daemon's option "-u" and the respective variable in the script (ympd has to be started as root to be able to bind to port 80)
- allow for additional arguments via ympd_flags="" in rc.conf (which would again allow to drop privileges after binding to the port)
2018-01-28 22:54:17 +01:00
SuperBFG7
fec13b6937 Revert "remove password since it breaks empty passwords"
This reverts commit 839b9aa9de.
2018-01-26 15:47:16 +01:00
Daniel Schregenberger
2a0e4fb22a
Merge pull request #15 from SuperBFG7/notandy-master
Notandy master
2018-01-26 15:43:58 +01:00
Daniel Schregenberger
7a31b441b6
Merge pull request #14 from notandy/master
fixed mpd connection with empty mpdpass introduced by #148
2018-01-26 15:42:49 +01:00
Andrew Karpow
88b2aa70c8 fixed mpd connection with empty mpdpass introduced by #148 2018-01-26 15:06:34 +01:00
SuperBFG7
839b9aa9de remove password since it breaks empty passwords 2018-01-26 14:22:50 +01:00
SuperBFG7
f1495ff712 fixed typo 2018-01-26 14:16:03 +01:00
SuperBFG7
94fed08434 Merge branch 'notandy-master' 2018-01-26 13:59:08 +01:00
SuperBFG7
1e93d2dc82 Merge branch 'master' into notandy-master 2018-01-26 13:51:20 +01:00
Daniel Schregenberger
3dac7ce3dc
Merge pull request #12 from notandy/master
Merge recent commits to notandy/ympd master
2018-01-26 13:23:23 +01:00
Andrew Karpow
8c1115dc63 Merge branch 'master' of github.com:notandy/ympd 2018-01-22 05:02:09 +01:00
Andrew Karpow
d314ac08df Merge branch 'lesderid-master' 2018-01-22 05:01:40 +01:00
Andrew Karpow
ceee8bd90a Merge branch 'master' of https://github.com/lesderid/ympd into lesderid-master 2018-01-22 04:58:17 +01:00
Andy
68429e39d5
Merge pull request #157 from florianheinemann/patch-1
Update README.md to highlight OpenSSL dependency
2018-01-22 04:55:07 +01:00
Andrew Karpow
b0253a01ab Merge branch 'SuperBFG7-send-message' 2018-01-22 04:50:43 +01:00
Andrew Karpow
ba4322c684 Merge branch 'send-message' of https://github.com/SuperBFG7/ympd into SuperBFG7-send-message 2018-01-22 04:50:22 +01:00
Andrew Karpow
95f8ccd7a4 update travis image 2018-01-22 04:45:59 +01:00
Andrew Karpow
265577c5ab Merge branch 'BenjaminHae-sortable' 2018-01-22 04:40:52 +01:00
Andy
91e3535891
Merge pull request #156 from znetstar/master
Adds a Dockerfile
2018-01-22 04:35:21 +01:00
Andy
393d6063da
Merge pull request #149 from SuperBFG7/delete
add ability to delete ranges of the playlist
2018-01-22 04:34:54 +01:00
Andy
a99b0a5ddc
Merge pull request #134 from Kernald/fix-double-slashes
Fix double slashes in websocket URL
2018-01-22 04:33:54 +01:00
Andy
e436fc93b5
Merge pull request #130 from govertbuijs/master
Added crude filtering for browsing db
2018-01-22 04:32:57 +01:00
Andy
b21fccc240
Merge pull request #150 from azrdev/no-overwrite-cflags
Don't overwrite CFLAGS
2018-01-22 04:26:09 +01:00
Andy
f7aade410d
Merge pull request #148 from unknownloner/master
Allow MPD Password to be configured in command line options
2018-01-22 04:23:51 +01:00
Andy
e0050641df
Merge pull request #145 from felixjogris/master
added FreeBSD RC script
2018-01-22 04:22:36 +01:00
Florian Heinemann
bba35bf682
Update README.md to highlight OpenSSL dependency 2018-01-21 22:06:40 +01:00
Zachary Boyd
02ab7df3c6 Adds a Dockerfile for building a lightweight Docker container based on alpine 2017-12-23 12:11:47 -08:00
SuperBFG7
5589013a95 shorter browse item in menu 2017-12-18 15:16:37 +01:00
SuperBFG7
06ded692a5 Revert "Merge branch 'lmanul-master'"
This reverts commit f118c53ecb, reversing
changes made to 604d54a0bb.
2017-12-18 15:10:21 +01:00
SuperBFG7
f118c53ecb Merge branch 'lmanul-master' 2017-12-18 15:00:56 +01:00
SuperBFG7
d9182dd2f4 merge branch lmanul-master 2017-12-18 15:00:28 +01:00
SuperBFG7
604d54a0bb fix player 2017-07-31 11:56:02 +02:00
azrdev
c1f1adbc28 Don't overwrite CFLAGS
Preserve user/system-configured C_FLAGS.

This broke the rpm package build on fedora: They enable a hardened config (i.e. ASLR) by default, which adds -pie and -fPIC to linker and compiler flags. Overwriting C_FLAGS removed the compiler spec, but not the linker one, leading to an error like:

/bin/cc  -std=gnu99 -Wall -ggdb -pedantic  -specs=/usr/lib/rpm/redhat/redhat-hardened-ld CMakeFiles/mkdata.dir/tools/mkdata.c.o  -o mkdata -rdynamic 
bin/ld: CMakeFiles/mkdata.dir/tools/mkdata.c.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
CMakeFiles/mkdata.dir/tools/mkdata.c.o: error adding symbols: Bad value

With this fix, the error is gone:

/bin/cc  -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -std=gnu99 -Wall -g -ggdb -pedantic  -specs=/usr/lib/rpm/redhat/redhat-hardened-ld CMakeFiles/mkdata.dir/tools/mkdata.c.o  -o mkdata -rdynamic
2017-05-14 07:37:46 +02:00
SuperBFG7
fac60b18a1 Merge branch 'delete' 2017-04-08 11:31:41 +02:00
SuperBFG7
eb803d1da5 add ability to delete ranges of the playlist 2017-04-08 11:25:40 +02:00
Galen Alderson
fa4cb48830 Fix segfault when using short password option (-m) 2017-03-29 12:17:01 -04:00
SuperBFG7
269c55b9c8 Merge remote-tracking branch 'origin/send-message' 2017-03-24 11:02:25 +01:00
SuperBFG7
dfb9adb78c added missing include 2017-03-24 11:01:24 +01:00
SuperBFG7
6727b8b0ad uncommented love button 2017-03-18 13:44:04 +01:00
SuperBFG7
4f64061286 Merge remote-tracking branch 'origin/send-message' 2017-03-18 13:41:32 +01:00
SuperBFG7
152defeaaa add a button to love tracks on last.fm using mpdas 2017-03-18 13:32:17 +01:00
SuperBFG7
07c6df23bc added API command to send messages to MPD channels 2017-03-18 13:31:26 +01:00
SuperBFG7
afab4e5653 Merge branch 'fix-double-slashes' 2017-03-18 10:39:25 +01:00
SuperBFG7
66ff05e8b5 Merge branch 'fix-double-slashes' of git://github.com/Kernald/ympd into fix-double-slashes 2017-03-18 10:38:46 +01:00
SuperBFG7
6c75d5bc6c Merge branch 'eolianoe-patch-1' 2017-03-18 10:35:24 +01:00
SuperBFG7
40c335cfca Merge branch 'patch-1' of git://github.com/eolianoe/ympd into eolianoe-patch-1 2017-03-18 10:34:57 +01:00
SuperBFG7
8abcee0049 fix README 2017-03-18 10:27:52 +01:00
unknownln
e5a88b1ffe quote password 2017-02-18 12:05:24 -05:00
unknownln
ec74ef463b config for system services 2017-02-18 12:00:28 -05:00
unknownln
a6ba0d640c Update documentation for new option 2017-02-18 11:51:03 -05:00
unknownln
f79dea44a2 Add option to set MPD password 2017-02-18 11:42:20 -05:00
Felix J. Ogris
40906e2bee added FreeBSD RC script 2017-02-06 20:19:09 +01:00
Les De Ridder
d9ec70a9da
Make the Dirble API token configurable and use HTTPS
Fixes #140
2016-09-11 18:23:42 +02:00
Marc Plano-Lesay
15edcc645c Fix double slashes in websocket URL 2016-07-07 15:22:15 +02:00
Govert Buijs
306840fa1d Added crude filtering for browsing db 2016-06-08 23:20:27 +02:00
eolianoe
d2d037fe97 Display the right short option for --version 2016-04-17 11:37:04 +02:00
SuperBFG7
f586db33d1 fix browsing of playlists 2016-02-16 12:03:50 +01:00
Andrew Karpow
ba40ad4515 Merge branch 'sortable' of https://github.com/BenjaminHae/ympd into BenjaminHae-sortable 2016-02-13 21:31:23 +01:00
25 changed files with 2479 additions and 1490 deletions

168
.clang-format Normal file
View File

@ -0,0 +1,168 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: true
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
- Regex: '^<.*'
Priority: 2
SortPriority: 0
- Regex: '.*'
Priority: 3
SortPriority: 0
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
---

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Build Artifacts
build/

13
.prettierignore Normal file
View File

@ -0,0 +1,13 @@
# Used by Prettier to ignore certain files and folders completely.
# See https://prettier.io/docs/en/ignore.html for details.
# Ignore build and system artifacts
build/
cmake/
contrib/
# Ignore all 3rd party libraries
**/bootstrap*
**/jquery*
**/modernizr*
**/sammy*

20
.prettierrc.json Normal file
View File

@ -0,0 +1,20 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"overrides": [
{
"files": ["*.html"],
"options": {
"tabWidth": 2
}
},
{
"files": ["*.css"],
"options": {
"tabWidth": 2
}
}
]
}

View File

@ -1,18 +1,17 @@
language: c language: c
sudo: required sudo: required
dist: precise dist: trusty
compiler: compiler:
- gcc - gcc
- clang - clang
before_install: before_install:
- sudo apt-get -qq update - sudo apt-get -qq update
- sudo apt-get install -y libmpdclient-dev cmake - sudo apt-get install -y libmpdclient-dev cmake
- mkdir build - mkdir build
- cd build - cd build
- cmake -D CMAKE_BUILD_TYPE=DEBUG .. - cmake -D CMAKE_BUILD_TYPE=DEBUG ..
script: make script: make

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 2.6)
project (ympd) project (ympd 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 "1") set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "2") set(CPACK_PACKAGE_VERSION_MINOR "2")
@ -24,8 +24,8 @@ include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_I
include(CheckCSourceCompiles) include(CheckCSourceCompiles)
set(CMAKE_C_FLAGS "-std=gnu99 -Wall") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall")
set(CMAKE_C_FLAGS_DEBUG "-ggdb -pedantic") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -pedantic")
if(WITH_IPV6) if(WITH_IPV6)
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_IPV6) set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_IPV6)
endif() endif()

32
DEVELOPMENT.md Normal file
View File

@ -0,0 +1,32 @@
# Development Notes
## Code Formatting
The project has been formatted with [prettier.io](https://prettier.io/) for the HTML, JavaScript, CSS, and Markdown files. See the configuration file [.prettierrc.json](./.prettierrc.json) and the ignore file [.prettierignore](./.prettierignore) for details. If `prettier` is installed globally, there's no need to provide the various `npm`-type dependencies in the project. Various editors may provide plugins that can use this configuration without having to install `npm` and `prettier` manually.
Manual Usage:
```bash
> npx prettier --write .
```
The C source and header files have been formatted with `clang-format`. There's no easy way to manually execute the formatter on all of the C files at the same time. The clang format is based off of the 'Google' style with ajdustments to make the changes not as disruptive. See [.clang-format](./.clang-format) file for the formatting rules. Various editors should be able to automatically format the source on save.
The only files formatted are the non-third party library files.
Manual Usage:
```bash
> clang-format -i -style=file <filename>
```
Manually formatted files:
- http_server.c
- http_server.h
- json_encode.h
- mpd_client.c
- mpd_client.h
- ympd.c
For help with the rules, see [Clang Format Configurator](https://zed0.co.uk/clang-format-configurator/) for an interactive tool and [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) for the rules reference.

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM alpine:3.5
WORKDIR /app/build
COPY . /app
RUN apk add --no-cache g++ make cmake libmpdclient-dev openssl-dev
RUN cmake ..
RUN make
FROM alpine:3.5
RUN apk add --no-cache libmpdclient openssl
EXPOSE 8080
COPY --from=0 /app/build/ympd /usr/bin/ympd
COPY --from=0 /app/build/mkdata /usr/bin/mkdata
CMD ympd

View File

@ -4,60 +4,56 @@ ympd
Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
http://www.ympd.org http://www.ympd.org
![ScreenShot](http://www.ympd.org/assets/ympd_github.png) ![ScreenShot](http://www.ympd.org/assets/ympd_github.png)
Dependencies ## Dependencies
------------
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
- cmake 2.6: http://cmake.org/
Unix Build Instructions - libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
----------------------- - cmake 2.6: http://cmake.org/
- OpenSSL: https://www.openssl.org/
1. install dependencies, cmake and libmpdclient are available from all major distributions. ## Unix Build Instructions
2. create build directory ```cd /path/to/src; mkdir build; cd build```
3. create makefile ```cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr``` 1. install dependencies. cmake, libmpdclient (dev), and OpenSSL (dev) are available from all major distributions.
4. build ```make``` 2. create build directory `cd /path/to/src; mkdir build; cd build`
5. install ```sudo make install``` or just run with ```./ympd``` 3. create makefile `cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr`
4. build `make`
5. install `sudo make install` or just run with `./ympd`
## Run flags
Run flags
---------
``` ```
Usage: ./ympd [OPTION]... Usage: ./ympd [OPTION]...
-d, --digest <htdigest> path to htdigest file for authorization -D, --digest <htdigest> path to htdigest file for authorization
(realm ympd) [no authorization] (realm ympd) [no authorization]
-h, --host <host> connect to mpd at host [localhost] -h, --host <host> connect to mpd at host [localhost]
-p, --port <port> connect to mpd at port [6600] -p, --port <port> connect to mpd at port [6600]
-w, --webport [ip:]<port> listen interface/port for webserver [8080] -w, --webport [ip:]<port> listen interface/port for webserver [8080]
-u, --user <username> drop priviliges to user after socket bind -u, --user <username> drop priviliges to user after socket bind
-V, --version get version -V, --version get version
--help this help --help this help
``` ```
SSL Support ## SSL Support
-----------
To run ympd with SSL support: To run ympd with SSL support:
- create a certificate (key and cert in the same file), example: - create a certificate (key and cert in the same file), example:
``` ```
# openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1000 -nodes # openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1000 -nodes
# cat key.pem cert.pem > ssl.pem # cat key.pem cert.pem > ssl.pem
``` ```
- tell ympd to use a webport using SSL and where to find the certificate:
- tell ympd to use a webport using SSL and where to find the certificate:
``` ```
# ./ympd -w "ssl://8081:/path/to/ssl.pem" # ./ympd -w "ssl://8081:/path/to/ssl.pem"
```
Dirble support ## Copyright
--------------
1. Get an API-key from http://dirble.com
2. Add the key at ```var TOKEN = "";```, in ```mpd.js```.
Copyright
---------
2013-2014 <andy@ndyk.de> 2013-2014 <andy@ndyk.de>

View File

@ -25,6 +25,7 @@ MPD_HOST=localhost
MPD_PORT=6600 MPD_PORT=6600
WEB_PORT=8080 WEB_PORT=8080
#DIGEST=--digest /path/to/htdigest #DIGEST=--digest /path/to/htdigest
#LOCALPORT=8080
# Exit if the package is not installed # Exit if the package is not installed
@ -36,7 +37,7 @@ WEB_PORT=8080
# Load the VERBOSE setting and other rcS variables # Load the VERBOSE setting and other rcS variables
[ -f /etc/default/rcS ] && . /etc/default/rcS [ -f /etc/default/rcS ] && . /etc/default/rcS
DAEMON_OPT="--user $YMPD_USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST" DAEMON_OPT="--user $YMPD_USER --mpdpass '$MPD_PASSWORD' --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST $LOCALPORT"
do_start() do_start()
{ {

View File

@ -1,5 +1,6 @@
MPD_HOST=localhost MPD_HOST=localhost
MPD_PORT=6600 MPD_PORT=6600
MPD_PASSWORD=
WEB_PORT=8080 WEB_PORT=8080
YMPD_USER=nobody
#DIGEST=--digest /path/to/htdigest #DIGEST=--digest /path/to/htdigest
#LOCALPORT=--localport 8080

28
contrib/ympd.freebsd Executable file
View File

@ -0,0 +1,28 @@
#!/bin/sh
# PROVIDE: ympd
# REQUIRE: DAEMON musicpd
# KEYWORD: shutdown
# Add the following line to /etc/rc.conf to enable ympd:
#
# ympd_enable="YES"
. /etc/rc.subr
name="ympd"
rcvar="${name}_enable"
command="/usr/local/bin/${name}"
pidfile="/var/run/${name}.pid"
start_cmd="ympd_start"
load_rc_config "${name}"
: ${ympd_enable:="NO"}
ympd_start()
{
check_startmsgs && echo "Starting ${name}."
/usr/sbin/daemon -f -p "${pidfile}" "${command}" "${rc_flags}"
}
run_rc_command "$1"

View File

@ -3,13 +3,35 @@ Description=ympd server daemon
Requires=network.target local-fs.target Requires=network.target local-fs.target
[Service] [Service]
User=nobody
DynamicUser=yes
MountAPIVFS=yes
RemoveIPC=yes
CapabilityBoundingSet=
LockPersonality=yes
PrivateUsers=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
NoNewPrivileges=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectHome=yes
Environment=MPD_HOST=localhost Environment=MPD_HOST=localhost
Environment=MPD_PORT=6600 Environment=MPD_PORT=6600
Environment=MPD_PASSWORD=
Environment=WEB_PORT=8080 Environment=WEB_PORT=8080
Environment=YMPD_USER=nobody Environment=YMPD_USER=nobody
Environment=DIGEST= Environment=DIGEST=
Environment=LOCALPORT=
EnvironmentFile=/etc/default/ympd EnvironmentFile=/etc/default/ympd
ExecStart=/usr/bin/ympd --user $YMPD_USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST ExecStart=/usr/bin/ympd --user $USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST $LOCALPORT
Type=simple Type=simple
[Install] [Install]

View File

@ -9,29 +9,52 @@ body {
#volumeslider { #volumeslider {
width: 150px; width: 150px;
float: left;
} }
#volumeslider .progress { #volumeslider .progress {
margin-bottom: 0; margin-bottom: 0;
} }
button { button {
overflow: hidden; overflow: hidden;
} }
#volume-icon { #volume-icon {
float: left; float: left;
margin-right: 10px; margin-right: 10px;
margin-top: 2px; margin-top: 2px;
} }
#volume-number {
float: right;
margin-top: 2px;
margin-left: 10px;
}
#love {
float: right;
}
#love > button > span {
color: red;
}
#breadcrump {
display: block;
overflow: auto;
white-space: nowrap;
}
#breadcrump > li > a {
cursor: pointer;
}
#counter { #counter {
font-size: 24px; font-size: 24px;
margin-top: -6px; margin-top: -6px;
margin-left: 10px; margin-left: 10px;
min-width: 50px; min-width: 50px;
} }
#search { #search {
@ -39,7 +62,7 @@ button {
} }
.btn-group-hover { .btn-group-hover {
opacity: 20%; opacity: 20%;
} }
.btn:active, .btn:active,
@ -47,29 +70,77 @@ button {
background-image: none; background-image: none;
outline: 0; outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
color: #428bca; color: #428bca;
background-color: #fdfdfd; background-color: #fdfdfd;
border-color: #adadad; border-color: #adadad;
} }
@media (max-width: 1199px) { @media (max-width: 1199px) {
#btn-responsive-block > .btn { #btn-responsive-block > .btn {
padding: 6px 12px; padding: 6px 12px;
font-size: 14px; font-size: 14px;
border-radius: 4px; border-radius: 4px;
} }
} }
#salamisandwich td:nth-child(3), th:nth-child(3) { h1 {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
td:nth-child(4),
th:nth-child(4) {
/* This *has* to be placed before
any t[dh]:nth-last-child(2) for
the override to work. */
min-width: 50%;
}
td:nth-last-child(2),
th:nth-last-child(2) {
text-align: right; text-align: right;
width: 4em;
}
#salamisandwich td:nth-child(4) span {
font-size: 90%;
display: block;
}
td:nth-child(2),
td:nth-child(3) {
min-width: 25%;
max-width: 10em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media only screen and (max-width: 600px) {
td:nth-child(2),
td:nth-child(3) {
min-width: 0;
max-width: 0;
}
td:nth-child(4),
th:nth-child(4) {
min-width: 10%;
white-space: normal;
}
} }
tbody { tbody {
cursor: pointer; cursor: pointer;
} }
td:last-child, td:first-child { td:last-child,
td:first-child {
width: 30px; width: 30px;
} }
@ -78,7 +149,7 @@ td:last-child, td:first-child {
z-index: 9999; z-index: 9999;
} }
/* Positioning */ /* Positioning */
.notifications.top-right { .notifications.top-right {
right: 10px; right: 10px;
top: 60px; top: 60px;
@ -94,3 +165,25 @@ td:last-child, td:first-child {
button { button {
overflow: hidden; overflow: hidden;
} }
#trashmode span:last-child {
display: inline-block;
text-align: left;
width: 2.8em;
}
#filter > a.active {
font-weight: bold;
pointer-events: none;
cursor: default;
text-decoration: none;
color: black;
}
@media screen and (min-width: 992px) {
.sticky {
position: sticky;
top: 55px;
z-index: 99;
}
}

View File

@ -1,354 +1,597 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<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="ympd - fast and lightweight MPD webclient"> <meta
<meta name="author" content="andy@ndyk.de"> name="description"
content="ympd - fast and lightweight MPD webclient"
/>
<meta name="author" content="andy@ndyk.de" />
<title>ympd</title> <title>ympd</title>
<!-- Bootstrap core CSS --> <!-- Bootstrap core CSS -->
<link href="css/bootstrap.css" rel="stylesheet"> <link href="css/bootstrap.css" rel="stylesheet" />
<link href="css/bootstrap-theme.css" rel="stylesheet"> <link href="css/bootstrap-theme.css" rel="stylesheet" />
<!-- Custom styles for this template --> <!-- Custom styles for this template -->
<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
<script src="js/modernizr-custom.js"></script> href="assets/favicon.ico"
</head> rel="shortcut icon"
<body> type="image/vnd.microsoft.icon"
/>
<script src="js/modernizr-custom.js"></script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button
type="button"
class="navbar-toggle"
data-toggle="collapse"
data-target=".navbar-collapse"
>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/"
><span class="glyphicon glyphicon-play-circle"></span> ympd</a
>
</div>
<div class="collapse navbar-collapse">
<ul id="nav_links" class="nav navbar-nav">
<li id="queue"><a href="#/">Queue</a></li>
<li id="browse"><a href="#/browse/0/">Browse</a></li>
<li>
<a href="#" data-toggle="modal" data-target="#addstream"
>Add Stream</a
>
</li>
<li>
<a
href="#"
data-toggle="modal"
data-target="#settings"
onclick="getHost();"
>Settings</a
>
</li>
</ul>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
<div class="container"> <div class="btn-group">
<div class="navbar-header"> <button
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> type="button"
<span class="icon-bar"></span> class="btn btn-default"
<span class="icon-bar"></span> onclick="socket.send('MPD_API_SET_PREV');"
<span class="icon-bar"></span> >
</button> <span class="glyphicon glyphicon-backward"></span>
<a class="navbar-brand" href="/"><span class="glyphicon glyphicon-play-circle"></span> ympd</a> </button>
<button
type="button"
class="btn btn-default"
onclick="socket.send('MPD_API_SET_STOP');"
>
<span id="stop-icon" class="glyphicon glyphicon-stop"></span>
</button>
<button
type="button"
class="btn btn-default"
onclick="clickPlay();"
>
<span id="play-icon" class="glyphicon glyphicon-pause"></span>
</button>
<button
type="button"
class="btn btn-default"
onclick="socket.send('MPD_API_SET_NEXT');"
>
<span class="glyphicon glyphicon-forward"></span>
</button>
</div>
<div class="btn-group" role="group">
<button
type="button"
class="btn btn-default"
onclick="clickLocalPlay()"
>
<audio id="player" preload="none"></audio>
<span
id="localplay-icon"
class="glyphicon glyphicon-play"
></span>
</button>
</div>
<div class="btn-group" role="group" id="volume-group">
<div class="btn btn-toolbar btn-default">
<span
id="volume-icon"
class="glyphicon glyphicon-volume-up"
></span>
<div id="volumeslider"></div>
<span id="volume-number"></span>
</div>
</div>
</div>
<form id="search" class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" />
</div>
</form>
</div>
<!--/.nav-collapse -->
</div> </div>
<div class="collapse navbar-collapse">
<ul id="nav_links" class="nav navbar-nav">
<li id="queue"><a href="#/">Queue</a></li>
<li id="browse"><a href="#/browse/0/">Browse database</a></li>
<li id="dirble"><a href="#/dirble/">Dirble</a></li>
<li><a href="#" data-toggle="modal" data-target="#addstream">Add Stream</a></li>
<li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li>
</ul>
<div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
<div class="btn-group">
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_PREV');">
<span class="glyphicon glyphicon-backward"></span>
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_STOP');">
<span id="stop-icon" class="glyphicon glyphicon-stop"></span>
</button>
<button type="button" class="btn btn-default" onclick="clickPlay();">
<span id="play-icon" class="glyphicon glyphicon-pause"></span>
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_NEXT');">
<span class="glyphicon glyphicon-forward"></span>
</button>
</div>
<!--
<div class="btn-group">
<div class="btn btn-toolbar btn-default">
<span id="volume-icon" class="glyphicon glyphicon-volume-up"></span>
<div id="volumeslider"></div>
</div>
</div>
-->
<div class="btn-group" role="group">
<audio id="player" preload="none"></audio>
<button type="button" class="btn btn-default" onclick="clickLocalPlay()">
<span id="localplay-icon" class="glyphicon glyphicon-play"></span>
</button>
<button type="button" class="btn btn-default" onclick="window.open('/player.html');">
<span id="localplay-icon" class="glyphicon glyphicon-new-window"></span>
</button>
</div>
</div>
<form id="search" class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
</form>
</div><!--/.nav-collapse -->
</div> </div>
</div>
<div class="container starter-template"> <div class="container starter-template">
<div class="row"> <div class="row">
<div class="col-md-10 col-xs-12">
<div class="notifications top-right"></div>
<div class="col-md-10 col-xs-12"> <div class="panel panel-primary sticky">
<div class="notifications top-right"></div> <!-- Default panel contents -->
<div class="panel-body">
<div class="panel panel-primary"> <h1>
<!-- Default panel contents --> <span
<div class="panel-heading"><b id="panel-heading">Queue</b></div> id="track-icon"
<div class="panel-body"> onclick="clickPlay();"
<h1> class="glyphicon glyphicon-play"
<span id="track-icon" onclick="clickPlay();" class="glyphicon glyphicon-play"></span> ></span>
<span id="currenttrack"></span> <span id="currenttrack"></span>
</h1> <span id="love">
<button
id="btnlove"
type="button"
class="btn btn-default"
onclick="clickLove();"
>
<span class="glyphicon glyphicon-heart"></span></button
></span>
</h1>
<h4>
<span id="artist" class="text"></span>
<span id="album" class="text pull-right"></span>
</h4>
<p id="counter" class="text pull-right">&nbsp;&nbsp;</p>
<div id="progressbar"></div>
</div>
</div>
<!-- /.panel -->
<!-- /.panel-body -->
<div class="panel panel-primary">
<div class="panel-heading">
<b id="panel-heading">Queue</b>
<b id="panel-heading-info" class="text pull-right"></b>
</div>
<ol id="breadcrump" class="breadcrumb"></ol>
<div class="col-md-12" id="filter"></div>
<!-- Table -->
<table id="salamisandwich" class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Artist</th>
<th>Album</th>
<th>Title</th>
<th>Length</th>
<th><div style="width: 15px"></div></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<!-- /.panel -->
<ul class="pager">
<li id="prev" class="page-btn hide"><a href="">Previous</a></li>
<li id="next" class="page-btn"><a href="">Next</a></li>
</ul>
</div>
<!-- /.col-md-10 -->
<div class="col-md-2 col-xs-12 sticky">
<div class="btn-toolbar">
<div
class="btn-group-vertical btn-block btn-group-lg"
data-toggle="buttons"
>
<button id="btnrandom" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-random"></span> Random
</button>
<button id="btnconsume" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-fire"></span> Consume
</button>
<button id="btnsingle" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-star"></span> Single
</button>
<button id="btncrossfade" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-link"></span> Crossfade
</button>
<button id="btnrepeat" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-repeat"></span> Repeat
</button>
</div>
<div
id="btn-outputs-block"
class="btn-group-vertical btn-block btn-group-lg"
></div>
<div
id="trashmode"
class="btn-group-vertical btn-block btn-group-lg"
data-toggle="radio"
>
<button id="btntrashmodeup" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-chevron-up"></span>
<span class="glyphicon glyphicon-trash"></span> <span>Up</span>
</button>
<button
id="btntrashmodesingle"
type="button"
class="btn btn-default active"
>
<span class="glyphicon glyphicon-star-empty"></span>
<span class="glyphicon glyphicon-trash"></span>
<span>Single</span>
</button>
<button
id="btntrashmodedown"
type="button"
class="btn btn-default"
>
<span class="glyphicon glyphicon-chevron-down"></span>
<span class="glyphicon glyphicon-trash"></span>
<span>Down</span>
</button>
</div>
<div
id="btn-responsive-block"
class="btn-group-vertical btn-block btn-group-lg"
>
<button
type="button"
class="btn btn-default"
onclick="socket.send('MPD_API_RM_ALL');"
>
<span class="glyphicon glyphicon-trash"></span> Clear Queue
</button>
<a
href="#"
data-toggle="modal"
data-target="#savequeue"
class="btn btn-default"
>
<span class="glyphicon glyphicon-save"></span> Save Queue
</a>
</div>
</div>
</div>
<!-- /.col-md-2 -->
</div>
<!-- /.row -->
</div>
<!-- /.container -->
<!-- Modal -->
<div
class="modal fade"
id="settings"
tabindex="-1"
role="dialog"
aria-labelledby="settingsLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-hidden="true"
>
&times;
</button>
<h2 class="modal-title" id="settingsLabel">
<span class="glyphicon glyphicon-wrench"></span> Settings
</h2>
</div>
<div class="modal-body">
<h4> <h4>
<span id="album" class="text"></span> <a href="http://www.ympd.org"
<span id="artist" class="text pull-right"></span> ><span class="glyphicon glyphicon-play-circle"></span> ympd</a
>&nbsp;&nbsp;&nbsp;<small
>MPD Web GUI - written in C, utilizing Websockets and
Bootstrap/JS</small
>
</h4> </h4>
<p id="counter" class="text pull-right">&nbsp;&nbsp;</p> <p>
ympd is a lightweight MPD (Music Player Daemon) web client that
<div id="progressbar"></div> runs without a dedicated webserver or interpreters like PHP,
NodeJS or Ruby. It's tuned for minimal resource usage and requires
only very litte dependencies.
</div><!-- /.panel-body --> </p>
<h5>ympd uses following excellent software:</h5>
<ol id="breadcrump" class="breadcrumb"> <h6>
</ol> <a href="http://cesanta.com/docs.html">Mongoose</a>
<small>GPLv2</small>
<div class="col-md-12"> </h6>
<button id="add-all-songs" class="btn btn-primary pull-right">Add all</button> <h6>
</div> <a href="http://www.musicpd.org/libs/libmpdclient/"
>libMPDClient</a
>
<!-- Table --> <small>BSD License</small>
<table id="salamisandwich" class="table table-hover"> </h6>
<thead> <hr />
<tr>
<th>#</th>
<th>Title</th>
<th>Album</th>
<th>Artist</th>
<th>Duration</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="dirble_panel">
<p id="dirble_loading" style="font-size:16px;font-weight:bold;margin-left:4em;">Loading...</p>
<table style="float:right; width:44%; margin-right:4%;" id="dirble_right" class="table table-hover">
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<table style="width:44%; margin-left:4%;" id="dirble_left" class="table table-hover">
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div><!-- /.panel -->
<ul class="pager">
<li id="prev" class="page-btn hide"><a href="">Previous</a></li>
<li id="next" class="page-btn"><a href="">Next</a></li>
</ul>
</div><!-- /.col-md-10 -->
<div class="col-md-2 col-xs-12" >
<div class="btn-toolbar">
<div class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
<button id="btnrandom" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-random"></span> Random
</button>
<button id="btnconsume" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-fire"></span> Consume
</button>
<button id="btnsingle" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-star"></span> Single
</button>
<button id="btncrossfade" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-link"></span> Crossfade
</button>
<button id="btnrepeat" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-repeat"></span> Repeat
</button>
</div>
<div id="btn-outputs-block" class="btn-group-vertical btn-block btn-group-lg">
</div>
<div id="btn-responsive-block" class="btn-group-vertical btn-block btn-group-lg">
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_RM_ALL');">
<span class="glyphicon glyphicon-trash"></span> Clear queue
</button>
<a href="#" data-toggle="modal" data-target="#savequeue" class="btn btn-default">
<span class="glyphicon glyphicon-save"></span> Save queue
</a>
</div>
<div id="btn-responsive-block" class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
<button type="button" class="btn btn-default" id="btnnotify">
<span class="glyphicon glyphicon-comment"></span> Notifications
</button>
</div>
</div>
</div><!-- /.col-md-2 -->
</div><!-- /.row -->
</div><!-- /.container -->
<!-- Modal -->
<div class="modal fade" id="settings" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h2 class="modal-title" id="settingsLabel"><span class="glyphicon glyphicon-wrench"></span> Settings</h2>
</div>
<div class="modal-body">
<h4><a href="http://www.ympd.org"><span class="glyphicon glyphicon-play-circle"></span> ympd</a>&nbsp;&nbsp;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
<p>
ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated webserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.</p>
<h5>ympd uses following excellent software:</h5>
<h6><a href="http://cesanta.com/docs.html">Mongoose</a> <small>GPLv2</small></h6>
<h6><a href="http://www.musicpd.org/libs/libmpdclient/">libMPDClient</a> <small>BSD License</small></h6>
<hr />
<form role="form">
<div class="row">
<div class="form-group col-md-9">
<label class="control-label" for="mpdhost">MPD Host/IP</label>
<input type="text" class="form-control" id="mpdhost" />
</div>
<div class="form-group col-md-3">
<label class="control-label" for="mpdport">MPD Port</label>
<input type="text" class="form-control" id="mpdport" />
</div>
</div>
<div class="row"> <div class="row">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="control-label" for="mpd_pw">MPD Password</label> <button
<input type="password" class="form-control" id="mpd_pw" placeholder="Password"/> type="button"
class="btn btn-default btn-block"
onclick="updateDB();"
>
<span class="glyphicon glyphicon-refresh"></span> Update
Database
</button>
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6" data-toggle="buttons">
<label class="control-label" for="mpd_pw_con">MPD Password (Confirmation)</label> <button
<input type="password" class="form-control" id="mpd_pw_con" placeholder="Password confirmation" type="button"
data-placement="right" data-toggle="popover" data-content="Password does not match!" class="btn btn-default btn-block"
data-trigger="manual" /> id="btnnotify"
</div> >
<div class="form-group col-md-12"> <span class="glyphicon glyphicon-comment"></span> Enable
<div id="mpd_password_set" class="hide alert alert-info"> Notifications
<button type="button" class="close" aria-hidden="true">&times;</button>
MPD Password is set
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<label class="control-label" for="mpdstream">MPD Stream URL</label>
<input type="text" class="form-control" id="mpdstream" />
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<button type="button" class="btn btn-default" onclick="updateDB();">
<span class="glyphicon glyphicon-refresh"></span> Update DB
</button> </button>
</div> </div>
</div> </div>
</form> <hr />
</div> <form role="form">
<div class="modal-footer"> <div class="row">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> <div class="form-group col-md-9">
<button type="button" class="btn btn-default" onclick="confirmSettings();">Save</button> <label class="control-label" for="mpdhost">MPD Host/IP</label>
</div> <input type="text" class="form-control" id="mpdhost" />
</div><!-- /.modal-content --> </div>
</div><!-- /.modal-dialog --> <div class="form-group col-md-3">
</div><!-- /.modal --> <label class="control-label" for="mpdport">MPD Port</label>
<input type="text" class="form-control" id="mpdport" />
<!-- Modal --> </div>
<div class="modal fade" id="addstream" tabindex="-1" role="dialog" aria-labelledby="addstreamLabel" aria-hidden="true"> </div>
<div class="modal-dialog"> <div class="row">
<div class="modal-content"> <div class="form-group col-md-6">
<div class="modal-header"> <label class="control-label" for="mpd_pw">MPD Password</label>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <input
<h2 class="modal-title" id="addstreamLabel"><span class="glyphicon glyphicon-wrench"></span> Add Stream</h2> type="password"
</div> class="form-control"
<div class="modal-body"> id="mpd_pw"
<form role="form"> placeholder="Password"
/>
</div>
<div class="form-group col-md-6">
<label class="control-label" for="mpd_pw_con"
>MPD Password (Confirmation)</label
>
<input
type="password"
class="form-control"
id="mpd_pw_con"
placeholder="Confirmation"
data-placement="right"
data-toggle="popover"
data-content="Password does not match!"
data-trigger="manual"
/>
</div>
<div class="form-group col-md-12">
<div id="mpd_password_set" class="hide alert alert-info">
<button type="button" class="close" aria-hidden="true">
&times;
</button>
MPD Password is set
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<label class="control-label" for="mpdstream"
>MPD Stream URL</label
>
<input type="text" class="form-control" id="mpdstream" />
</div>
</div>
</form>
<div class="row"> <div class="row">
<div class="form-group col-md-9"> <div class="form-group col-md-12" data-toggle="buttons">
<label class="control-label" for="streamurl">Stream URL</label> <button
<input type="text" class="form-control" id="streamurl" /> type="button"
class="btn btn-default btn-block"
id="btnautoplay"
>
<span class="glyphicon glyphicon-play"></span> Autoplay stream
in this browser when mpd is playing
</button>
</div> </div>
</div> </div>
</form> </div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
<button
type="button"
class="btn btn-default"
onclick="confirmSettings();"
>
Save
</button>
</div>
</div> </div>
<div class="modal-footer"> <!-- /.modal-content -->
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> </div>
<button type="button" class="btn btn-default" onclick="addStream();">Add Stream</button> <!-- /.modal-dialog -->
</div> </div>
</div><!-- /.modal-content --> <!-- /.modal -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="modal fade" id="savequeue" tabindex="-1" role="dialog" aria-labelledby="savequeueLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h2 class="modal-title" id="savequeueLabel"><span class="glyphicon glyphicon-wrench"></span> Save Queue</h2>
</div>
<div class="modal-body">
<form role="form">
<div class="row">
<div class="form-group col-md-9">
<label class="control-label" for="playlistname">Playlist Name</label>
<input type="text" class="form-control" id="playlistname" />
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-default" onclick="saveQueue();">Save Queue</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="modal fade bs-example-modal-sm" id="wait" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false" aria-hidden="true"> <!-- Modal -->
<div class="modal-dialog"> <div
<div class="modal-content"> class="modal fade"
<div class="modal-header"> id="addstream"
<h1>Searching...</h1> tabindex="-1"
role="dialog"
aria-labelledby="addstreamLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-hidden="true"
>
&times;
</button>
<h2 class="modal-title" id="addstreamLabel">
<span class="glyphicon glyphicon-wrench"></span> Add Stream
</h2>
</div>
<div class="modal-body">
<form role="form">
<div class="row">
<div class="form-group col-md-12">
<label class="control-label" for="streamurl"
>Stream URL</label
>
<input type="text" class="form-control" id="streamurl" />
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
<button
type="button"
class="btn btn-default"
onclick="addStream();"
>
Add Stream
</button>
</div>
</div> </div>
<div class="modal-body"> <!-- /.modal-content -->
<div class="progress progress-striped active"> </div>
<div class="progress-bar" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 100%"> <!-- /.modal-dialog -->
<span class="sr-only">Please Wait</span> </div>
<!-- /.modal -->
<div
class="modal fade"
id="savequeue"
tabindex="-1"
role="dialog"
aria-labelledby="savequeueLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-hidden="true"
>
&times;
</button>
<h2 class="modal-title" id="savequeueLabel">
<span class="glyphicon glyphicon-wrench"></span> Save Queue
</h2>
</div>
<div class="modal-body">
<form role="form">
<div class="row">
<div class="form-group col-md-9">
<label class="control-label" for="playlistname"
>Playlist Name</label
>
<input type="text" class="form-control" id="playlistname" />
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
<button
type="button"
class="btn btn-default"
onclick="saveQueue();"
>
Save Queue
</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div
class="modal fade bs-example-modal-sm"
id="wait"
tabindex="-1"
role="dialog"
data-backdrop="static"
data-keyboard="false"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1>Searching...</h1>
</div>
<div class="modal-body">
<div class="progress progress-striped active">
<div
class="progress-bar"
role="progressbar"
aria-valuenow="45"
aria-valuemin="0"
aria-valuemax="100"
style="width: 100%"
>
<span class="sr-only">Please Wait</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Bootstrap core JavaScript
<!-- Bootstrap core JavaScript
================================================== --> ================================================== -->
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js"></script> <script src="js/jquery-1.10.2.min.js"></script>
<script src="js/jquery.cookie.js"></script> <script src="js/jquery.cookie.js"></script>
<script src="js/bootstrap.min.js"></script> <script src="js/bootstrap.min.js"></script>
<script src="js/bootstrap-notify.js"></script> <script src="js/bootstrap-notify.js"></script>
<script src="js/bootstrap-slider.js"></script> <script src="js/bootstrap-slider.js"></script>
<script src="js/sammy.js"></script> <script src="js/sammy.js"></script>
<script src="js/jquery-ui-sortable.min.js"></script> <script src="js/jquery-ui-sortable.min.js"></script>
<script src="js/mpd.js"></script> <script src="js/mpd.js"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@ -1,126 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0">-->
<meta name="viewport" content="width=320">
<meta name="description" content="ympd - fast and lightweight MPD webclient">
<meta name="author" content="andy@ndyk.de">
<title>ympd player</title>
<!-- Bootstrap core CSS -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/bootstrap-theme.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="css/mpd.min.css" rel="stylesheet">
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/jquery.cookie.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/bootstrap-notify.js"></script>
<script type="text/javascript">
function clickLocalPlay() {
var player = document.getElementById('player');
$("#localplay-icon").removeClass("glyphicon-play").removeClass("glyphicon-pause");
if ( player.paused ) {
var mpdstream = $.cookie("mpdstream");
player.src = mpdstream;
console.log("playing mpd stream: " + player.src);
player.load();
player.play();
$("#localplay-icon").addClass("glyphicon-pause");
} else {
player.pause();
player.src='';
player.removeAttribute("src");
$("#localplay-icon").addClass("glyphicon-play");
}
}
$(document).ready(function(){
document.getElementById('player').addEventListener('stalled', function() {
if ( !document.getElementById('player').paused ) {
this.pause();
clickLocalPlay();
$('.top-right').notify({
message:{text:"music stream stalled - trying to recover..."},
type: "danger",
fadeOut: { enabled: true, delay: 1000 },
}).show();
}
});
document.getElementById('player').addEventListener('pause', function() {
this.src='';
this.removeAttribute("src");
$("#localplay-icon").removeClass("glyphicon-pause").addClass("glyphicon-play");
});
document.getElementById('player').addEventListener('error', function failed(e) {
this.pause();
switch (e.target.error.code) {
case e.target.error.MEDIA_ERR_ABORTED:
$('.top-right').notify({
message:{text:"Audio playback aborted by user."},
type: "info",
fadeOut: { enabled: true, delay: 1000 },
}).show();
break;
case e.target.error.MEDIA_ERR_NETWORK:
$('.top-right').notify({
message:{text:"Network error while playing audio."},
type: "danger",
fadeOut: { enabled: true, delay: 1000 },
}).show();
break;
case e.target.error.MEDIA_ERR_DECODE:
$('.top-right').notify({
message:{text:"Audio playback aborted. Did you unplug your headphones?"},
type: "danger",
fadeOut: { enabled: true, delay: 1000 },
}).show();
break;
case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
$('.top-right').notify({
message:{text:"Error while loading audio (server, network or format error)."},
type: "danger",
fadeOut: { enabled: true, delay: 1000 },
}).show();
break;
default:
$('.top-right').notify({
message:{text:"Unknown error while playing audio."},
type: "danger",
fadeOut: { enabled: true, delay: 1000 },
}).show();
break;
}
}, true);
});
</script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<a class="navbar-brand" href="/" target="_blank"><span class="glyphicon glyphicon-play-circle"></span> ympd</a>
</div>
</div>
<div class="container starter-template">
<div class="row">
<div class="col-md-10 col-xs-12">
<audio id="player" preload="none"></audio>
<button type="button" class="btn btn-default btn-lg center-block" onclick="clickLocalPlay()">
<span id="localplay-icon" class="glyphicon glyphicon-play"></span>
</button>
<div class="notifications top-right"></div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,7 +1,7 @@
/* ympd /* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de> (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. the Free Software Foundation; version 2 of the License.
@ -16,24 +16,41 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include <string.h>
#include "http_server.h" #include "http_server.h"
int callback_http(struct mg_connection *c) #include <openssl/rand.h>
{ #include <string.h>
#include "mpd_client.h"
int callback_http(struct mg_connection *c) {
const struct embedded_file *req_file; const struct embedded_file *req_file;
if(!strcmp(c->uri, "/")) if (!strcmp(c->uri, "/"))
req_file = find_embedded_file("/index.html"); req_file = find_embedded_file("/index.html");
else else
req_file = find_embedded_file(c->uri); req_file = find_embedded_file(c->uri);
if(req_file) if (req_file) {
{
mg_send_header(c, "Content-Type", req_file->mimetype); mg_send_header(c, "Content-Type", req_file->mimetype);
mg_send_data(c, req_file->data, req_file->size); mg_send_data(c, req_file->data, req_file->size);
return MG_TRUE;
}
if (!strcmp(c->uri, "/wss-auth")) {
unsigned char salt[WSS_AUTH_TOKEN_SIZE + 1];
RAND_bytes(salt, WSS_AUTH_TOKEN_SIZE);
for (int i = 0; i <= WSS_AUTH_TOKEN_SIZE; i++) salt[i] = salt[i] % 26 + 65;
salt[WSS_AUTH_TOKEN_SIZE] = 0;
if (mpd.wss_auth_token)
free(mpd.wss_auth_token);
mpd.wss_auth_token = strdup((char *)salt);
mg_send_header(c, "Content-Type", "text/plain");
mg_send_data(c, salt, WSS_AUTH_TOKEN_SIZE);
return MG_TRUE; return MG_TRUE;
} }

View File

@ -1,7 +1,7 @@
/* ympd /* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de> (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. the Free Software Foundation; version 2 of the License.
@ -22,14 +22,13 @@
#include "mongoose.h" #include "mongoose.h"
struct embedded_file { struct embedded_file {
const char *name; const char *name;
const unsigned char *data; const unsigned char *data;
const char *mimetype; const char *mimetype;
size_t size; size_t size;
}; };
const struct embedded_file *find_embedded_file(const char *name); const struct embedded_file *find_embedded_file(const char *name);
int callback_http(struct mg_connection *c); int callback_http(struct mg_connection *c);
#endif #endif

View File

@ -1,7 +1,7 @@
/* ympd /* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de> (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. the Free Software Foundation; version 2 of the License.
@ -15,7 +15,7 @@
with this program; if not, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef __JSON_ENCODE_H__ #ifndef __JSON_ENCODE_H__
#define __JSON_ENCODE_H__ #define __JSON_ENCODE_H__

View File

@ -1,7 +1,7 @@
/* ympd /* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de> (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. the Free Software Foundation; version 2 of the License.
@ -16,58 +16,87 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include <stdio.h> #include "mpd_client.h"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h> #include <libgen.h>
#include <mpd/client.h> #include <mpd/client.h>
#include <mpd/message.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mpd_client.h"
#include "config.h" #include "config.h"
#include "json_encode.h" #include "json_encode.h"
/* forward declaration */ /* forward declaration */
static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev); static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev);
struct t_mpd mpd;
const char * mpd_cmd_strs[] = { const char *mpd_cmd_strs[] = {MPD_CMDS(GEN_STR)};
MPD_CMDS(GEN_STR)
};
char * get_arg1 (char *p) { char *get_arg1(char *p) {
return strchr(p, ',') + 1; return strchr(p, ',') + 1;
} }
char * get_arg2 (char *p) { char *get_arg2(char *p) {
return get_arg1(get_arg1(p)); return get_arg1(get_arg1(p));
} }
static inline enum mpd_cmd_ids get_cmd_id(char *cmd) static inline enum mpd_cmd_ids get_cmd_id(char *cmd) {
{ for (int i = 0; i < sizeof(mpd_cmd_strs) / sizeof(mpd_cmd_strs[0]); i++)
for(int i = 0; i < sizeof(mpd_cmd_strs)/sizeof(mpd_cmd_strs[0]); i++) if (!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i])))
if(!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i])))
return i; return i;
return -1; return -1;
} }
int callback_mpd(struct mg_connection *c) int callback_mpd(struct mg_connection *c) {
{
enum mpd_cmd_ids cmd_id = get_cmd_id(c->content); enum mpd_cmd_ids cmd_id = get_cmd_id(c->content);
size_t n = 0; size_t n = 0;
unsigned int uint_buf, uint_buf_2; unsigned int uint_buf, uint_buf_2;
int int_buf; int int_buf;
char *p_charbuf = NULL, *token; char *p_charbuf = NULL, *token;
if(cmd_id == -1) if (!c->connection_param)
c->connection_param = calloc(1, sizeof(struct t_mpd_client_session));
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
if (!s->authorized && (cmd_id != MPD_API_AUTHORIZE)) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"not authorized\"}");
mg_websocket_write(c, 1, mpd.buf, n);
return MG_TRUE;
}
if (cmd_id == -1)
return MG_TRUE; return MG_TRUE;
if(mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST && if (mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST &&
cmd_id != MPD_API_GET_MPDHOST && cmd_id != MPD_API_SET_MPDPASS) cmd_id != MPD_API_GET_MPDHOST && cmd_id != MPD_API_SET_MPDPASS)
return MG_TRUE; return MG_TRUE;
switch(cmd_id) switch (cmd_id) {
{ case MPD_API_AUTHORIZE:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_AUTHORIZE"))
goto out_authorize;
if ((token = strtok(NULL, ",")) == NULL)
goto out_authorize;
free(p_charbuf);
p_charbuf = strdup(c->content);
s->auth_token = strdup(get_arg1(p_charbuf));
if (!strcmp(mpd.wss_auth_token, s->auth_token))
s->authorized = 1;
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"authorized\", \"data\":\"%s\"}",
s->authorized ? "true" : "false");
out_authorize:
free(p_charbuf);
break;
case MPD_API_UPDATE_DB: case MPD_API_UPDATE_DB:
mpd_run_update(mpd.conn, NULL); mpd_run_update(mpd.conn, NULL);
break; break;
@ -90,39 +119,42 @@ int callback_mpd(struct mg_connection *c)
mpd_run_clear(mpd.conn); mpd_run_clear(mpd.conn);
break; break;
case MPD_API_RM_TRACK: case MPD_API_RM_TRACK:
if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
mpd_run_delete_id(mpd.conn, uint_buf); mpd_run_delete_id(mpd.conn, uint_buf);
break; break;
case MPD_API_RM_RANGE:
if (sscanf(c->content, "MPD_API_RM_RANGE,%u,%u", &uint_buf, &uint_buf_2))
mpd_run_delete_range(mpd.conn, uint_buf, uint_buf_2);
break;
case MPD_API_MOVE_TRACK: case MPD_API_MOVE_TRACK:
if (sscanf(c->content, "MPD_API_MOVE_TRACK,%u,%u", &uint_buf, &uint_buf_2) == 2) if (sscanf(c->content, "MPD_API_MOVE_TRACK,%u,%u", &uint_buf, &uint_buf_2) == 2) {
{
uint_buf -= 1; uint_buf -= 1;
uint_buf_2 -= 1; uint_buf_2 -= 1;
mpd_run_move(mpd.conn, uint_buf, uint_buf_2); mpd_run_move(mpd.conn, uint_buf, uint_buf_2);
} }
break; break;
case MPD_API_PLAY_TRACK: case MPD_API_PLAY_TRACK:
if(sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf))
mpd_run_play_id(mpd.conn, uint_buf); mpd_run_play_id(mpd.conn, uint_buf);
break; break;
case MPD_API_TOGGLE_RANDOM: case MPD_API_TOGGLE_RANDOM:
if(sscanf(c->content, "MPD_API_TOGGLE_RANDOM,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_TOGGLE_RANDOM,%u", &uint_buf))
mpd_run_random(mpd.conn, uint_buf); mpd_run_random(mpd.conn, uint_buf);
break; break;
case MPD_API_TOGGLE_REPEAT: case MPD_API_TOGGLE_REPEAT:
if(sscanf(c->content, "MPD_API_TOGGLE_REPEAT,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_TOGGLE_REPEAT,%u", &uint_buf))
mpd_run_repeat(mpd.conn, uint_buf); mpd_run_repeat(mpd.conn, uint_buf);
break; break;
case MPD_API_TOGGLE_CONSUME: case MPD_API_TOGGLE_CONSUME:
if(sscanf(c->content, "MPD_API_TOGGLE_CONSUME,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_TOGGLE_CONSUME,%u", &uint_buf))
mpd_run_consume(mpd.conn, uint_buf); mpd_run_consume(mpd.conn, uint_buf);
break; break;
case MPD_API_TOGGLE_SINGLE: case MPD_API_TOGGLE_SINGLE:
if(sscanf(c->content, "MPD_API_TOGGLE_SINGLE,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_TOGGLE_SINGLE,%u", &uint_buf))
mpd_run_single(mpd.conn, uint_buf); mpd_run_single(mpd.conn, uint_buf);
break; break;
case MPD_API_TOGGLE_CROSSFADE: case MPD_API_TOGGLE_CROSSFADE:
if(sscanf(c->content, "MPD_API_TOGGLE_CROSSFADE,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_TOGGLE_CROSSFADE,%u", &uint_buf))
mpd_run_crossfade(mpd.conn, uint_buf); mpd_run_crossfade(mpd.conn, uint_buf);
break; break;
case MPD_API_GET_OUTPUTS: case MPD_API_GET_OUTPUTS:
@ -139,116 +171,142 @@ int callback_mpd(struct mg_connection *c)
} }
break; break;
case MPD_API_SET_VOLUME: case MPD_API_SET_VOLUME:
if(sscanf(c->content, "MPD_API_SET_VOLUME,%ud", &uint_buf) && uint_buf <= 100) if (sscanf(c->content, "MPD_API_SET_VOLUME,%ud", &uint_buf) && uint_buf <= 100)
mpd_run_set_volume(mpd.conn, uint_buf); mpd_run_set_volume(mpd.conn, uint_buf);
break; break;
case MPD_API_SET_SEEK: case MPD_API_SET_SEEK:
if(sscanf(c->content, "MPD_API_SET_SEEK,%u,%u", &uint_buf, &uint_buf_2)) if (sscanf(c->content, "MPD_API_SET_SEEK,%u,%u", &uint_buf, &uint_buf_2))
mpd_run_seek_id(mpd.conn, uint_buf, uint_buf_2); mpd_run_seek_id(mpd.conn, uint_buf, uint_buf_2);
break; break;
case MPD_API_GET_QUEUE: case MPD_API_GET_QUEUE:
if(sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf)) if (sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf))
n = mpd_put_queue(mpd.buf, uint_buf); n = mpd_put_queue(mpd.buf, uint_buf);
break; break;
case MPD_API_GET_BROWSE: case MPD_API_GET_BROWSE:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_GET_BROWSE")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_GET_BROWSE"))
goto out_browse; goto out_browse;
uint_buf = strtoul(strtok(NULL, ","), NULL, 10); uint_buf = strtoul(strtok(NULL, ","), NULL, 10);
if((token = strtok(NULL, ",")) == NULL) if ((token = strtok(NULL, ",")) == NULL)
goto out_browse; goto out_browse;
free(p_charbuf); free(p_charbuf);
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
n = mpd_put_browse(mpd.buf, get_arg2(p_charbuf), uint_buf); n = mpd_put_browse(mpd.buf, get_arg2(p_charbuf), uint_buf);
out_browse: out_browse:
free(p_charbuf); free(p_charbuf);
break; break;
case MPD_API_ADD_TRACK: case MPD_API_ADD_TRACK:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_TRACK")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_TRACK"))
goto out_add_track; goto out_add_track;
if((token = strtok(NULL, ",")) == NULL) if ((token = strtok(NULL, ",")) == NULL)
goto out_add_track; goto out_add_track;
free(p_charbuf); free(p_charbuf);
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
mpd_run_add(mpd.conn, get_arg1(p_charbuf)); mpd_run_add(mpd.conn, get_arg1(p_charbuf));
out_add_track: out_add_track:
free(p_charbuf); free(p_charbuf);
break; break;
case MPD_API_ADD_PLAY_TRACK: case MPD_API_ADD_PLAY_TRACK:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAY_TRACK")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAY_TRACK"))
goto out_play_track; goto out_play_track;
if((token = strtok(NULL, ",")) == NULL) if ((token = strtok(NULL, ",")) == NULL)
goto out_play_track; goto out_play_track;
free(p_charbuf); free(p_charbuf);
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
int_buf = mpd_run_add_id(mpd.conn, get_arg1(p_charbuf)); int_buf = mpd_run_add_id(mpd.conn, get_arg1(p_charbuf));
if(int_buf != -1) if (int_buf != -1)
mpd_run_play_id(mpd.conn, int_buf); mpd_run_play_id(mpd.conn, int_buf);
out_play_track: out_play_track:
free(p_charbuf); free(p_charbuf);
break; break;
case MPD_API_ADD_PLAYLIST: case MPD_API_ADD_PLAYLIST:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAYLIST")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAYLIST"))
goto out_playlist; goto out_playlist;
if((token = strtok(NULL, ",")) == NULL) if ((token = strtok(NULL, ",")) == NULL)
goto out_playlist; goto out_playlist;
free(p_charbuf); free(p_charbuf);
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
mpd_run_load(mpd.conn, get_arg1(p_charbuf)); mpd_run_load(mpd.conn, get_arg1(p_charbuf));
out_playlist: out_playlist:
free(p_charbuf); free(p_charbuf);
break; break;
case MPD_API_SAVE_QUEUE: case MPD_API_SAVE_QUEUE:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_SAVE_QUEUE")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_SAVE_QUEUE"))
goto out_save_queue; goto out_save_queue;
if((token = strtok(NULL, ",")) == NULL) if ((token = strtok(NULL, ",")) == NULL)
goto out_save_queue; goto out_save_queue;
free(p_charbuf); free(p_charbuf);
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
mpd_run_save(mpd.conn, get_arg1(p_charbuf)); mpd_run_save(mpd.conn, get_arg1(p_charbuf));
out_save_queue: out_save_queue:
free(p_charbuf); free(p_charbuf);
break; break;
case MPD_API_SEARCH: case MPD_API_SEARCH:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_SEARCH")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_SEARCH"))
goto out_search;
if((token = strtok(NULL, ",")) == NULL)
goto out_search; goto out_search;
free(p_charbuf); if ((token = strtok(NULL, ",")) == NULL)
goto out_search;
free(p_charbuf);
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
n = mpd_search(mpd.buf, get_arg1(p_charbuf)); n = mpd_search(mpd.buf, get_arg1(p_charbuf));
out_search: out_search:
free(p_charbuf); free(p_charbuf);
break; break;
case MPD_API_SEND_MESSAGE:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SEND_MESSAGE"))
goto out_send_message;
if ((token = strtok(NULL, ",")) == NULL)
goto out_send_message;
free(p_charbuf);
p_charbuf = strdup(get_arg1(c->content));
if (strtok(p_charbuf, ",") == NULL)
goto out_send_message;
if ((token = strtok(NULL, ",")) == NULL)
goto out_send_message;
mpd_run_send_message(mpd.conn, p_charbuf, token);
out_send_message:
free(p_charbuf);
break;
case MPD_API_GET_CHANNELS:
mpd.buf_size = mpd_put_channels(mpd.buf);
c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
break;
#ifdef WITH_MPD_HOST_CHANGE #ifdef WITH_MPD_HOST_CHANGE
/* Commands allowed when disconnected from MPD server */ /* Commands allowed when disconnected from MPD server */
case MPD_API_SET_MPDHOST: case MPD_API_SET_MPDHOST:
int_buf = 0; int_buf = 0;
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDHOST")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDHOST"))
goto out_host_change; goto out_host_change;
if((int_buf = strtol(strtok(NULL, ","), NULL, 10)) <= 0) if ((int_buf = strtol(strtok(NULL, ","), NULL, 10)) <= 0)
goto out_host_change; goto out_host_change;
if((token = strtok(NULL, ",")) == NULL) if ((token = strtok(NULL, ",")) == NULL)
goto out_host_change; goto out_host_change;
strncpy(mpd.host, token, sizeof(mpd.host)); strncpy(mpd.host, token, sizeof(mpd.host));
@ -256,97 +314,101 @@ out_search:
mpd.conn_state = MPD_RECONNECT; mpd.conn_state = MPD_RECONNECT;
free(p_charbuf); free(p_charbuf);
return MG_TRUE; return MG_TRUE;
out_host_change: out_host_change:
free(p_charbuf); free(p_charbuf);
break; break;
case MPD_API_GET_MPDHOST: case MPD_API_GET_MPDHOST:
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"mpdhost\", \"data\": " n = snprintf(mpd.buf, MAX_SIZE,
"{\"host\" : \"%s\", \"port\": \"%d\", \"passwort_set\": %s}" "{\"type\":\"mpdhost\", \"data\": "
"}", mpd.host, mpd.port, mpd.password ? "true" : "false"); "{\"host\" : \"%s\", \"port\": \"%d\", \"passwort_set\": %s}"
"}",
mpd.host, mpd.port, mpd.password ? "true" : "false");
break; break;
case MPD_API_SET_MPDPASS: case MPD_API_SET_MPDPASS:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDPASS")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDPASS"))
goto out_set_pass; goto out_set_pass;
if((token = strtok(NULL, ",")) == NULL) if ((token = strtok(NULL, ",")) == NULL)
goto out_set_pass; goto out_set_pass;
if(mpd.password) if (mpd.password)
free(mpd.password); free(mpd.password);
mpd.password = strdup(token); mpd.password = strdup(token);
mpd.conn_state = MPD_RECONNECT; mpd.conn_state = MPD_RECONNECT;
free(p_charbuf); free(p_charbuf);
return MG_TRUE; return MG_TRUE;
out_set_pass: out_set_pass:
free(p_charbuf); free(p_charbuf);
break; break;
#endif #endif
} }
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) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}", n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}",
mpd_connection_get_error_message(mpd.conn)); mpd_connection_get_error_message(mpd.conn));
/* Try to recover error */ /* Try to recover error */
if (!mpd_connection_clear_error(mpd.conn)) if (!mpd_connection_clear_error(mpd.conn))
mpd.conn_state = MPD_FAILURE; mpd.conn_state = MPD_FAILURE;
} }
if(n > 0) if (n > 0)
mg_websocket_write(c, 1, mpd.buf, n); mg_websocket_write(c, 1, mpd.buf, n);
return MG_TRUE; return MG_TRUE;
} }
int mpd_close_handler(struct mg_connection *c) int mpd_close_handler(struct mg_connection *c) {
{
/* Cleanup session data */ /* Cleanup session data */
if(c->connection_param) if (c->connection_param) {
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
if (s->auth_token)
free(s->auth_token);
free(c->connection_param); free(c->connection_param);
}
return 0; return 0;
} }
static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) { static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) {
size_t n; size_t n;
if(!c->is_websocket) if (!c->is_websocket)
return MG_TRUE; return MG_TRUE;
if(c->callback_param) if (!c->connection_param)
{
/* error message? */
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",
(const char *)c->callback_param);
mg_websocket_write(c, 1, mpd.buf, n);
return MG_TRUE; return MG_TRUE;
}
if(!c->connection_param)
c->connection_param = calloc(1, sizeof(struct t_mpd_client_session));
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param; struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
if(mpd.conn_state != MPD_CONNECTED) { if (!s->authorized)
return MG_TRUE;
if (c->callback_param) {
/* error message? */
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",
(const char *)c->callback_param);
mg_websocket_write(c, 1, mpd.buf, n);
return MG_TRUE;
}
if (mpd.conn_state != MPD_CONNECTED) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}"); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}");
mg_websocket_write(c, 1, mpd.buf, n); mg_websocket_write(c, 1, mpd.buf, n);
} } else {
else
{
mg_websocket_write(c, 1, mpd.buf, mpd.buf_size); mg_websocket_write(c, 1, mpd.buf, mpd.buf_size);
if(s->song_id != mpd.song_id) if (s->song_id != mpd.song_id) {
{
n = mpd_put_current_song(mpd.buf); n = mpd_put_current_song(mpd.buf);
mg_websocket_write(c, 1, mpd.buf, n); mg_websocket_write(c, 1, mpd.buf, n);
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\"}");
mg_websocket_write(c, 1, mpd.buf, n); mg_websocket_write(c, 1, mpd.buf, n);
s->queue_version = mpd.queue_version; s->queue_version = mpd.queue_version;
@ -356,8 +418,7 @@ static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) {
return MG_TRUE; return MG_TRUE;
} }
void mpd_poll(struct mg_server *s) void mpd_poll(struct mg_server *s) {
{
switch (mpd.conn_state) { switch (mpd.conn_state) {
case MPD_DISCONNECTED: case MPD_DISCONNECTED:
/* Try to connect */ /* Try to connect */
@ -371,8 +432,7 @@ void mpd_poll(struct mg_server *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)) {
{
c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn); c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn);
mpd_notify_callback(c, MG_POLL); mpd_notify_callback(c, MG_POLL);
} }
@ -380,11 +440,9 @@ void mpd_poll(struct mg_server *s)
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)) {
{
c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn); c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn);
mpd_notify_callback(c, MG_POLL); mpd_notify_callback(c, MG_POLL);
} }
@ -397,8 +455,7 @@ void mpd_poll(struct mg_server *s)
mpd.conn_state = MPD_CONNECTED; mpd.conn_state = MPD_CONNECTED;
/* write outputs */ /* write outputs */
mpd.buf_size = mpd_put_outputs(mpd.buf, 1); mpd.buf_size = mpd_put_outputs(mpd.buf, 1);
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)) {
{
c->callback_param = NULL; c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL); mpd_notify_callback(c, MG_POLL);
} }
@ -409,7 +466,7 @@ void mpd_poll(struct mg_server *s)
case MPD_DISCONNECT: case MPD_DISCONNECT:
case MPD_RECONNECT: case MPD_RECONNECT:
if(mpd.conn != NULL) if (mpd.conn != NULL)
mpd_connection_free(mpd.conn); mpd_connection_free(mpd.conn);
mpd.conn = NULL; mpd.conn = NULL;
mpd.conn_state = MPD_DISCONNECTED; mpd.conn_state = MPD_DISCONNECTED;
@ -417,14 +474,17 @@ void mpd_poll(struct mg_server *s)
case MPD_CONNECTED: case MPD_CONNECTED:
mpd.buf_size = mpd_put_state(mpd.buf, &mpd.song_id, &mpd.queue_version); mpd.buf_size = mpd_put_state(mpd.buf, &mpd.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)) {
{
c->callback_param = NULL; c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL); mpd_notify_callback(c, MG_POLL);
} }
mpd.buf_size = mpd_put_outputs(mpd.buf, 0); mpd.buf_size = mpd_put_outputs(mpd.buf, 0);
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)) {
{ c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
}
mpd.buf_size = mpd_put_channels(mpd.buf);
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
c->callback_param = NULL; c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL); mpd_notify_callback(c, MG_POLL);
} }
@ -432,56 +492,51 @@ void mpd_poll(struct mg_server *s)
} }
} }
char* mpd_get_title(struct mpd_song const *song) char *mpd_get_title(struct mpd_song const *song) {
{
char *str; char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0); str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
if(str == NULL){ if (str == NULL) {
str = basename((char *)mpd_song_get_uri(song)); str = basename((char *)mpd_song_get_uri(song));
} }
return str; return str;
} }
char* mpd_get_album(struct mpd_song const *song) char *mpd_get_album(struct mpd_song const *song) {
{
char *str; char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); str = (char *)mpd_song_get_tag(song, MPD_TAG_ALBUM, 0);
if(str == NULL){ if (str == NULL) {
str = "-"; str = "-";
} }
return str; return str;
} }
char* mpd_get_artist(struct mpd_song const *song) char *mpd_get_artist(struct mpd_song const *song) {
{
char *str; char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); str = (char *)mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
if(str == NULL){ if (str == NULL) {
str = "-"; str = "-";
} }
return str; return str;
} }
char* mpd_get_year(struct mpd_song const *song) char *mpd_get_year(struct mpd_song const *song) {
{
char *str; char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_DATE, 0); str = (char *)mpd_song_get_tag(song, MPD_TAG_DATE, 0);
if(str == NULL){ if (str == NULL) {
str = "-"; str = "-";
} }
return str; return str;
} }
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version) int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version) {
{
struct mpd_status *status; struct mpd_status *status;
int len; int len;
@ -493,23 +548,18 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
} }
len = snprintf(buffer, MAX_SIZE, len = snprintf(buffer, MAX_SIZE,
"{\"type\":\"state\", \"data\":{" "{\"type\":\"state\", \"data\":{"
" \"state\":%d, \"volume\":%d, \"repeat\":%d," " \"state\":%d, \"volume\":%d, \"repeat\":%d,"
" \"single\":%d, \"crossfade\":%d, \"consume\":%d, \"random\":%d, " " \"single\":%d, \"crossfade\":%d, \"consume\":%d, \"random\":%d, "
" \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, " " \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
" \"currentsongid\": %d" " \"currentsongid\": %d"
"}}", "}}",
mpd_status_get_state(status), mpd_status_get_state(status), mpd_status_get_volume(status),
mpd_status_get_volume(status), mpd_status_get_repeat(status), mpd_status_get_single(status),
mpd_status_get_repeat(status), mpd_status_get_crossfade(status), mpd_status_get_consume(status),
mpd_status_get_single(status), mpd_status_get_random(status), mpd_status_get_song_pos(status),
mpd_status_get_crossfade(status), mpd_status_get_elapsed_time(status), mpd_status_get_total_time(status),
mpd_status_get_consume(status), mpd_status_get_song_id(status));
mpd_status_get_random(status),
mpd_status_get_song_pos(status),
mpd_status_get_elapsed_time(status),
mpd_status_get_total_time(status),
mpd_status_get_song_id(status));
*current_song_id = mpd_status_get_song_id(status); *current_song_id = mpd_status_get_song_id(status);
*queue_version = mpd_status_get_queue_version(status); *queue_version = mpd_status_get_queue_version(status);
@ -517,16 +567,15 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
return len; return len;
} }
int mpd_put_outputs(char *buffer, int names) int mpd_put_outputs(char *buffer, int names) {
{
struct mpd_output *out; struct mpd_output *out;
int nout; int nout;
char *str, *strend; char *str, *strend;
str = buffer; str = buffer;
strend = buffer+MAX_SIZE; strend = buffer + MAX_SIZE;
str += snprintf(str, strend-str, "{\"type\":\"%s\", \"data\":{", str += snprintf(str, strend - str, "{\"type\":\"%s\", \"data\":{",
names ? "outputnames" : "outputs"); names ? "outputnames" : "outputs");
mpd_send_outputs(mpd.conn); mpd_send_outputs(mpd.conn);
nout = 0; nout = 0;
@ -534,11 +583,11 @@ int mpd_put_outputs(char *buffer, int names)
if (nout++) if (nout++)
*str++ = ','; *str++ = ',';
if (names) if (names)
str += snprintf(str, strend - str, " \"%d\":\"%s\"", str += snprintf(str, strend - str, " \"%d\":\"%s\"", mpd_output_get_id(out),
mpd_output_get_id(out), mpd_output_get_name(out)); mpd_output_get_name(out));
else else
str += snprintf(str, strend-str, " \"%d\":%d", str += snprintf(str, strend - str, " \"%d\":%d", mpd_output_get_id(out),
mpd_output_get_id(out), mpd_output_get_enabled(out)); mpd_output_get_enabled(out));
mpd_output_free(out); mpd_output_free(out);
} }
if (!mpd_response_finish(mpd.conn)) { if (!mpd_response_finish(mpd.conn)) {
@ -546,36 +595,53 @@ int mpd_put_outputs(char *buffer, int names)
mpd_connection_clear_error(mpd.conn); mpd_connection_clear_error(mpd.conn);
return 0; return 0;
} }
str += snprintf(str, strend-str, " }}"); str += snprintf(str, strend - str, " }}");
return str-buffer; return str - buffer;
} }
int mpd_put_current_song(char *buffer) int mpd_put_channels(char *buffer) {
{ struct mpd_pair *channel;
int nchan;
char *str, *strend;
str = buffer;
strend = buffer + MAX_SIZE;
str += snprintf(str, strend - str, "{\"type\":\"%s\", \"data\":{", "channels");
mpd_send_channels(mpd.conn);
nchan = 0;
while ((channel = mpd_recv_channel_pair(mpd.conn)) != NULL) {
if (nchan++)
*str++ = ',';
str += snprintf(str, strend - str, " \"%d\":\"%s\"", nchan, channel->value);
mpd_return_pair(mpd.conn, channel);
}
if (!mpd_response_finish(mpd.conn)) {
fprintf(stderr, "MPD outputs: %s\n", mpd_connection_get_error_message(mpd.conn));
mpd_connection_clear_error(mpd.conn);
return 0;
}
str += snprintf(str, strend - str, " }}");
return str - buffer;
}
int mpd_put_current_song(char *buffer) {
char *cur = buffer; char *cur = buffer;
const char *end = buffer + MAX_SIZE; const char *end = buffer + MAX_SIZE;
struct mpd_song *song; struct mpd_song *song;
song = mpd_run_current_song(mpd.conn); song = mpd_run_current_song(mpd.conn);
if(song == NULL) if (song == NULL)
return 0; return 0;
cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":"); cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song)); cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
cur += json_emit_raw_str(cur, end - cur, ",\"title\":"); cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song)); cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL) cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
{ cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":"); cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
}
if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
{
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
}
cur += json_emit_raw_str(cur, end - cur, "}}"); cur += json_emit_raw_str(cur, end - cur, "}}");
mpd_song_free(song); mpd_song_free(song);
@ -584,36 +650,44 @@ int mpd_put_current_song(char *buffer)
return cur - buffer; return cur - buffer;
} }
int mpd_put_queue(char *buffer, unsigned int offset) int mpd_put_queue(char *buffer, unsigned int offset) {
{
char *cur = buffer; char *cur = buffer;
const char *end = buffer + MAX_SIZE; const char *end = buffer + MAX_SIZE;
struct mpd_entity *entity; struct mpd_entity *entity;
unsigned long totalTime = 0;
if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset+MAX_ELEMENTS_PER_PAGE)) if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset + MAX_ELEMENTS_PER_PAGE))
RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta"); RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ "); cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
while((entity = mpd_recv_entity(mpd.conn)) != NULL) { while ((entity = mpd_recv_entity(mpd.conn)) != NULL) {
const struct mpd_song *song; const struct mpd_song *song;
unsigned int drtn;
if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
song = mpd_entity_get_song(entity); song = mpd_entity_get_song(entity);
drtn = mpd_song_get_duration(song);
cur += json_emit_raw_str(cur, end - cur, "{\"id\":"); cur += json_emit_raw_str(cur, end - cur, "{\"id\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_id(song)); cur += json_emit_int(cur, end - cur, mpd_song_get_id(song));
cur += json_emit_raw_str(cur, end - cur, ",\"pos\":"); cur += json_emit_raw_str(cur, end - cur, ",\"pos\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song)); cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":"); cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song)); cur += json_emit_int(cur, end - cur, drtn);
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":"); cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song)); cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":"); cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song)); cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_raw_str(cur, end - cur, ",\"title\":"); cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song)); cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_raw_str(cur, end - cur, "},"); cur += json_emit_raw_str(cur, end - cur, "},");
totalTime += drtn;
} }
mpd_entity_free(entity); mpd_entity_free(entity);
} }
@ -621,12 +695,13 @@ int mpd_put_queue(char *buffer, unsigned int offset)
/* remove last ',' */ /* remove last ',' */
cur--; cur--;
cur += json_emit_raw_str(cur, end - cur, "]}"); cur += json_emit_raw_str(cur, end - cur, "],\"totalTime\":");
cur += json_emit_int(cur, end - cur, totalTime);
cur += json_emit_raw_str(cur, end - cur, "}");
return cur - buffer; return cur - buffer;
} }
int mpd_put_browse(char *buffer, char *path, unsigned int offset) int mpd_put_browse(char *buffer, char *path, unsigned int offset) {
{
char *cur = buffer; char *cur = buffer;
const char *end = buffer + MAX_SIZE; const char *end = buffer + MAX_SIZE;
struct mpd_entity *entity; struct mpd_entity *entity;
@ -635,25 +710,22 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset)
if (!mpd_send_list_meta(mpd.conn, path)) if (!mpd_send_list_meta(mpd.conn, path))
RETURN_ERROR_AND_RECOVER("mpd_send_list_meta"); RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ "); cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
while((entity = mpd_recv_entity(mpd.conn)) != NULL) { while ((entity = mpd_recv_entity(mpd.conn)) != NULL) {
const struct mpd_song *song; const struct mpd_song *song;
const struct mpd_directory *dir; const struct mpd_directory *dir;
const struct mpd_playlist *pl; const struct mpd_playlist *pl;
if(offset > entity_count) if (offset > entity_count) {
{
mpd_entity_free(entity); mpd_entity_free(entity);
entity_count++; entity_count++;
continue; continue;
} } else if (offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count) {
else if(offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count)
{
mpd_entity_free(entity); mpd_entity_free(entity);
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\",\"count\":"); cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\",\"count\":");
cur += json_emit_int(cur, end - cur, entity_count); cur += json_emit_int(cur, end - cur, entity_count);
cur += json_emit_raw_str(cur, end - cur, "} "); cur += json_emit_raw_str(cur, end - cur, "} ");
break; break;
} }
@ -708,23 +780,22 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset)
return cur - buffer; return cur - buffer;
} }
int mpd_search(char *buffer, char *searchstr) int mpd_search(char *buffer, char *searchstr) {
{
int i = 0; int i = 0;
char *cur = buffer; char *cur = buffer;
const char *end = buffer + MAX_SIZE; const char *end = buffer + MAX_SIZE;
struct mpd_song *song; struct mpd_song *song;
if(mpd_search_db_songs(mpd.conn, false) == false) if (mpd_search_db_songs(mpd.conn, false) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_db_songs"); RETURN_ERROR_AND_RECOVER("mpd_search_db_songs");
else if(mpd_search_add_any_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, searchstr) == false) else if (mpd_search_add_any_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, searchstr) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_add_any_tag_constraint"); RETURN_ERROR_AND_RECOVER("mpd_search_add_any_tag_constraint");
else if(mpd_search_commit(mpd.conn) == false) else if (mpd_search_commit(mpd.conn) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_commit"); RETURN_ERROR_AND_RECOVER("mpd_search_commit");
else { else {
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"search\",\"data\":[ "); cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"search\",\"data\":[ ");
while((song = mpd_recv_song(mpd.conn)) != NULL) { while ((song = mpd_recv_song(mpd.conn)) != NULL) {
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":"); cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song)); cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":"); cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
@ -735,12 +806,15 @@ int mpd_search(char *buffer, char *searchstr)
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song)); cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
cur += json_emit_raw_str(cur, end - cur, ",\"title\":"); cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song)); cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_raw_str(cur, end - cur, "},"); cur += json_emit_raw_str(cur, end - cur, "},");
mpd_song_free(song); mpd_song_free(song);
/* Maximum results */ /* Maximum results */
if(i++ >= 300) if (i++ >= 300) {
{
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},"); cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},");
break; break;
} }
@ -754,9 +828,7 @@ int mpd_search(char *buffer, char *searchstr)
return cur - buffer; return cur - buffer;
} }
void mpd_disconnect() {
void mpd_disconnect()
{
mpd.conn_state = MPD_DISCONNECT; mpd.conn_state = MPD_DISCONNECT;
mpd_poll(NULL); mpd_poll(NULL);
} }

View File

@ -1,7 +1,7 @@
/* ympd /* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de> (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. the Free Software Foundation; version 2 of the License.
@ -15,61 +15,65 @@
with this program; if not, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef __MPD_CLIENT_H__ #ifndef __MPD_CLIENT_H__
#define __MPD_CLIENT_H__ #define __MPD_CLIENT_H__
#include "mongoose.h" #include "mongoose.h"
#define RETURN_ERROR_AND_RECOVER(X) do { \ #define RETURN_ERROR_AND_RECOVER(X) \
fprintf(stderr, "MPD X: %s\n", mpd_connection_get_error_message(mpd.conn)); \ do { \
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}", \ fprintf(stderr, "MPD X: %s\n", mpd_connection_get_error_message(mpd.conn)); \
mpd_connection_get_error_message(mpd.conn)); \ cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}", \
if (!mpd_connection_clear_error(mpd.conn)) \ mpd_connection_get_error_message(mpd.conn)); \
mpd.conn_state = MPD_FAILURE; \ if (!mpd_connection_clear_error(mpd.conn)) \
return cur - buffer; \ mpd.conn_state = MPD_FAILURE; \
} while(0) return cur - buffer; \
} while (0)
#define MAX_SIZE 1024 * 100 #define MAX_SIZE 1024 * 100
#define MAX_ELEMENTS_PER_PAGE 512 #define MAX_ELEMENTS_PER_PAGE 512
#define WSS_AUTH_TOKEN_SIZE 50
#define GEN_ENUM(X) X, #define GEN_ENUM(X) X,
#define GEN_STR(X) #X, #define GEN_STR(X) #X,
#define MPD_CMDS(X) \ #define MPD_CMDS(X) \
X(MPD_API_GET_QUEUE) \ X(MPD_API_GET_QUEUE) \
X(MPD_API_GET_BROWSE) \ X(MPD_API_GET_BROWSE) \
X(MPD_API_GET_MPDHOST) \ X(MPD_API_GET_MPDHOST) \
X(MPD_API_ADD_TRACK) \ X(MPD_API_ADD_TRACK) \
X(MPD_API_ADD_PLAY_TRACK) \ X(MPD_API_ADD_PLAY_TRACK) \
X(MPD_API_ADD_PLAYLIST) \ X(MPD_API_ADD_PLAYLIST) \
X(MPD_API_PLAY_TRACK) \ X(MPD_API_PLAY_TRACK) \
X(MPD_API_SAVE_QUEUE) \ X(MPD_API_SAVE_QUEUE) \
X(MPD_API_RM_TRACK) \ X(MPD_API_RM_TRACK) \
X(MPD_API_RM_ALL) \ X(MPD_API_RM_RANGE) \
X(MPD_API_MOVE_TRACK) \ X(MPD_API_RM_ALL) \
X(MPD_API_SEARCH) \ X(MPD_API_MOVE_TRACK) \
X(MPD_API_SET_VOLUME) \ X(MPD_API_SEARCH) \
X(MPD_API_SET_PAUSE) \ X(MPD_API_GET_CHANNELS) \
X(MPD_API_SET_PLAY) \ X(MPD_API_SEND_MESSAGE) \
X(MPD_API_SET_STOP) \ X(MPD_API_SET_VOLUME) \
X(MPD_API_SET_SEEK) \ X(MPD_API_SET_PAUSE) \
X(MPD_API_SET_NEXT) \ X(MPD_API_SET_PLAY) \
X(MPD_API_SET_PREV) \ X(MPD_API_SET_STOP) \
X(MPD_API_SET_MPDHOST) \ X(MPD_API_SET_SEEK) \
X(MPD_API_SET_MPDPASS) \ X(MPD_API_SET_NEXT) \
X(MPD_API_UPDATE_DB) \ X(MPD_API_SET_PREV) \
X(MPD_API_GET_OUTPUTS) \ X(MPD_API_SET_MPDHOST) \
X(MPD_API_TOGGLE_OUTPUT) \ X(MPD_API_SET_MPDPASS) \
X(MPD_API_TOGGLE_RANDOM) \ X(MPD_API_UPDATE_DB) \
X(MPD_API_TOGGLE_CONSUME) \ X(MPD_API_GET_OUTPUTS) \
X(MPD_API_TOGGLE_SINGLE) \ X(MPD_API_TOGGLE_OUTPUT) \
X(MPD_API_TOGGLE_RANDOM) \
X(MPD_API_TOGGLE_CONSUME) \
X(MPD_API_TOGGLE_SINGLE) \
X(MPD_API_TOGGLE_CROSSFADE) \ X(MPD_API_TOGGLE_CROSSFADE) \
X(MPD_API_TOGGLE_REPEAT) X(MPD_API_TOGGLE_REPEAT) \
X(MPD_API_AUTHORIZE)
enum mpd_cmd_ids { enum mpd_cmd_ids { MPD_CMDS(GEN_ENUM) };
MPD_CMDS(GEN_ENUM)
};
enum mpd_conn_states { enum mpd_conn_states {
MPD_DISCONNECTED, MPD_DISCONNECTED,
@ -81,8 +85,11 @@ enum mpd_conn_states {
struct t_mpd { struct t_mpd {
int port; int port;
int local_port;
char host[128]; char host[128];
char *password; char *password;
char *gpass;
char *wss_auth_token;
struct mpd_connection *conn; struct mpd_connection *conn;
enum mpd_conn_states conn_state; enum mpd_conn_states conn_state;
@ -93,11 +100,15 @@ struct t_mpd {
int song_id; int song_id;
unsigned queue_version; unsigned queue_version;
} mpd; };
extern struct t_mpd mpd;
struct t_mpd_client_session { struct t_mpd_client_session {
int song_id; int song_id;
unsigned queue_version; unsigned queue_version;
int authorized;
char *auth_token;
}; };
void mpd_poll(struct mg_server *s); void mpd_poll(struct mg_server *s);
@ -105,10 +116,10 @@ int callback_mpd(struct mg_connection *c);
int mpd_close_handler(struct mg_connection *c); int mpd_close_handler(struct mg_connection *c);
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version); int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version);
int mpd_put_outputs(char *buffer, int putnames); int mpd_put_outputs(char *buffer, int putnames);
int mpd_put_channels(char *buffer);
int mpd_put_current_song(char *buffer); int mpd_put_current_song(char *buffer);
int mpd_put_queue(char *buffer, unsigned int offset); int mpd_put_queue(char *buffer, unsigned int offset);
int mpd_put_browse(char *buffer, char *path, unsigned int offset); int mpd_put_browse(char *buffer, char *path, unsigned int offset);
int mpd_search(char *buffer, char *searchstr); int mpd_search(char *buffer, char *searchstr);
void mpd_disconnect(); void mpd_disconnect();
#endif #endif

View File

@ -1,7 +1,7 @@
/* ympd /* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de> (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. the Free Software Foundation; version 2 of the License.
@ -16,42 +16,39 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h> #include <sys/time.h>
#include <pthread.h> #include <unistd.h>
#include "mongoose.h"
#include "http_server.h"
#include "mpd_client.h"
#include "config.h" #include "config.h"
#include "http_server.h"
#include "mongoose.h"
#include "mpd_client.h"
extern char *optarg; extern char *optarg;
int force_exit = 0; int force_exit = 0;
void bye() void bye() {
{
force_exit = 1; force_exit = 1;
} }
char *gpass = NULL;
static int server_callback(struct mg_connection *c, enum mg_event ev) { static int server_callback(struct mg_connection *c, enum mg_event ev) {
int result = MG_FALSE; int result = MG_FALSE;
FILE *fp = NULL; FILE *fp = NULL;
switch(ev) { switch (ev) {
case MG_CLOSE: case MG_CLOSE:
mpd_close_handler(c); mpd_close_handler(c);
return MG_TRUE; return MG_TRUE;
case MG_REQUEST: case MG_REQUEST:
if (c->is_websocket) { if (c->is_websocket) {
c->content[c->content_len] = '\0'; c->content[c->content_len] = '\0';
if(c->content_len) if (c->content_len)
return callback_mpd(c); return callback_mpd(c);
else else
return MG_TRUE; return MG_TRUE;
@ -63,10 +60,11 @@ static int server_callback(struct mg_connection *c, enum mg_event ev) {
#endif #endif
case MG_AUTH: case MG_AUTH:
// no auth for websockets since mobile safari does not support it // no auth for websockets since mobile safari does not support it
if ( (gpass == NULL) || (c->is_websocket) ) if ((mpd.gpass == NULL) || (c->is_websocket) ||
((mpd.local_port > 0) && (c->local_port == mpd.local_port)))
return MG_TRUE; return MG_TRUE;
else { else {
if ( (fp = fopen(gpass, "r")) != NULL ) { if ((fp = fopen(mpd.gpass, "r")) != NULL) {
result = mg_authorize_digest(c, fp); result = mg_authorize_digest(c, fp);
fclose(fp); fclose(fp);
} }
@ -77,8 +75,7 @@ static int server_callback(struct mg_connection *c, enum mg_event ev) {
} }
} }
int main(int argc, char **argv) int main(int argc, char **argv) {
{
int n, option_index = 0; int n, option_index = 0;
struct mg_server *server = mg_create_server(NULL, server_callback); struct mg_server *server = mg_create_server(NULL, server_callback);
unsigned int current_timer = 0, last_timer = 0; unsigned int current_timer = 0, last_timer = 0;
@ -93,24 +90,29 @@ int main(int argc, char **argv)
mg_set_option(server, "auth_domain", "ympd"); mg_set_option(server, "auth_domain", "ympd");
mpd.port = 6600; mpd.port = 6600;
mpd.local_port = 0;
mpd.gpass = NULL;
strcpy(mpd.host, "127.0.0.1"); strcpy(mpd.host, "127.0.0.1");
/* clang-format off */
static struct option long_options[] = { static struct option long_options[] = {
{"digest", required_argument, 0, 'd'}, {"digest", required_argument, 0, 'D'},
{"host", required_argument, 0, 'h'}, {"host", required_argument, 0, 'h'},
{"port", required_argument, 0, 'p'}, {"port", required_argument, 0, 'p'},
{"localport", required_argument, 0, 'l'},
{"webport", required_argument, 0, 'w'}, {"webport", required_argument, 0, 'w'},
{"user", required_argument, 0, 'u'}, {"user", required_argument, 0, 'u'},
{"version", no_argument, 0, 'v'}, {"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 0 }, {"help", no_argument, 0, 0 },
{"mpdpass", required_argument, 0, 'm'},
{0, 0, 0, 0 } {0, 0, 0, 0 }
}; };
/* clang-format on */
while((n = getopt_long(argc, argv, "d:h:p:w:u:v", while ((n = getopt_long(argc, argv, "D:h:p:l:w:u:d:v:m", long_options, &option_index)) != -1) {
long_options, &option_index)) != -1) {
switch (n) { switch (n) {
case 'd': case 'D':
gpass = strdup(optarg); mpd.gpass = strdup(optarg);
break; break;
case 'h': case 'h':
strncpy(mpd.host, optarg, sizeof(mpd.host)); strncpy(mpd.host, optarg, sizeof(mpd.host));
@ -118,52 +120,65 @@ int main(int argc, char **argv)
case 'p': case 'p':
mpd.port = atoi(optarg); mpd.port = atoi(optarg);
break; break;
case 'l':
mpd.local_port = atoi(optarg);
break;
case 'w': case 'w':
webport = strdup(optarg); webport = strdup(optarg);
break; break;
case 'u': case 'u':
run_as_user = strdup(optarg); run_as_user = strdup(optarg);
break; break;
case 'm':
if (strlen(optarg) > 0)
mpd.password = strdup(optarg);
break;
case 'v': case 'v':
fprintf(stdout, "ympd %d.%d.%d\n" fprintf(stdout,
"ympd %d.%d.%d\n"
"Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n" "Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n"
"built " __DATE__ " "__TIME__ " ("__VERSION__")\n", "built " __DATE__
" "__TIME__
" ("__VERSION__
")\n",
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH); YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH);
return EXIT_SUCCESS; return EXIT_SUCCESS;
break; break;
default: default:
fprintf(stderr, "Usage: %s [OPTION]...\n\n" fprintf(stderr,
" -d, --digest <htdigest>\tpath to htdigest file for authorization\n" "Usage: %s [OPTION]...\n\n"
" -D, --digest <htdigest>\tpath to htdigest file for authorization\n"
" \t(realm ympd) [no authorization]\n" " \t(realm ympd) [no authorization]\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"
" -l, --localport <port>\t\tskip authorization for local port\n"
" -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\n" " -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\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"
" --help\t\t\t\tthis help\n" " -m, --mpdpass <password>\tspecifies the password to use when connecting "
, argv[0]); "to mpd\n"
" --help\t\t\t\tthis help\n",
argv[0]);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if(error_msg) if (error_msg) {
{
fprintf(stderr, "Mongoose error: %s\n", error_msg); fprintf(stderr, "Mongoose error: %s\n", error_msg);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }
error_msg = mg_set_option(server, "listening_port", webport); error_msg = mg_set_option(server, "listening_port", webport);
if(error_msg) { if (error_msg) {
fprintf(stderr, "Mongoose error: %s\n", error_msg); fprintf(stderr, "Mongoose error: %s\n", error_msg);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
/* drop privilges at last to ensure proper port binding */ /* drop privilges at last to ensure proper port binding */
if(run_as_user != NULL) { if (run_as_user != NULL) {
error_msg = mg_set_option(server, "run_as_user", run_as_user); error_msg = mg_set_option(server, "run_as_user", run_as_user);
free(run_as_user); free(run_as_user);
if(error_msg) if (error_msg) {
{
fprintf(stderr, "Mongoose error: %s\n", error_msg); fprintf(stderr, "Mongoose error: %s\n", error_msg);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -172,8 +187,7 @@ int main(int argc, char **argv)
while (!force_exit) { while (!force_exit) {
mg_poll_server(server, 200); mg_poll_server(server, 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;
mpd_poll(server); mpd_poll(server);
} }

10
tools/lint.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
repo="$(git rev-parse --show-toplevel)"
pushd "$repo"
prettier --write .
for i in http_server.c http_server.h json_encode.h mpd_client.c mpd_client.h ympd.c; do
clang-format -i -style=file "src/$i"
done
popd

3
ympd.1
View File

@ -23,6 +23,9 @@ specifies the port for the webserver to listen to, defaults to 8080
\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\-V\fR, \fB\-\-version\fR \fB\-V\fR, \fB\-\-version\fR
print version and exit print version and exit
.TP .TP