Merge branch 'tandell-linter'

This commit is contained in:
SuperBFG7 2021-06-15 10:01:41 +02:00
commit 61e385a35d
17 changed files with 2113 additions and 1301 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

@ -3,16 +3,15 @@ language: c
sudo: required sudo: required
dist: trusty 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

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.

View File

@ -4,28 +4,26 @@ 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/
- OpenSSL: https://www.openssl.org/
Unix Build Instructions - libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
----------------------- - cmake 2.6: http://cmake.org/
- OpenSSL: https://www.openssl.org/
## Unix Build Instructions
1. install dependencies. cmake, libmpdclient (dev), and OpenSSL (dev) are available from all major distributions. 1. install dependencies. cmake, libmpdclient (dev), and OpenSSL (dev) are available from all major distributions.
2. create build directory ```cd /path/to/src; mkdir build; cd build``` 2. create build directory `cd /path/to/src; mkdir build; cd build`
3. create makefile ```cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr``` 3. create makefile `cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr`
4. build ```make``` 4. build `make`
5. install ```sudo make install``` or just run with ```./ympd``` 5. install `sudo make install` or just run with `./ympd`
## Run flags
Run flags
---------
``` ```
Usage: ./ympd [OPTION]... Usage: ./ympd [OPTION]...
@ -39,21 +37,23 @@ Usage: ./ympd [OPTION]...
--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"
``` ```
Copyright ## Copyright
---------
2013-2014 <andy@ndyk.de> 2013-2014 <andy@ndyk.de>

View File

@ -15,12 +15,10 @@ body {
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;
@ -34,7 +32,7 @@ button {
white-space: nowrap; white-space: nowrap;
} }
#breadcrump > li > a{ #breadcrump > li > a {
cursor: pointer; cursor: pointer;
} }
@ -58,17 +56,17 @@ 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;
} }
} }
@ -80,14 +78,16 @@ h1 {
white-space: nowrap; white-space: nowrap;
} }
td:nth-child(4), th:nth-child(4) { td:nth-child(4),
th:nth-child(4) {
/* This *has* to be placed before /* This *has* to be placed before
any t[dh]:nth-last-child(2) for any t[dh]:nth-last-child(2) for
the override to work. */ the override to work. */
min-width: 50%; min-width: 50%;
} }
td:nth-last-child(2), th:nth-last-child(2) { td:nth-last-child(2),
th:nth-last-child(2) {
text-align: right; text-align: right;
width: 4em; width: 4em;
} }
@ -98,7 +98,8 @@ td:nth-last-child(2), th:nth-last-child(2) {
display: block; display: block;
} }
td:nth-child(2), td:nth-child(3) { td:nth-child(2),
td:nth-child(3) {
min-width: 25%; min-width: 25%;
max-width: 10em; max-width: 10em;
@ -108,21 +109,24 @@ td:nth-child(2), td:nth-child(3) {
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
td:nth-child(2), td:nth-child(3) { td:nth-child(2),
min-width: 0; td:nth-child(3) {
max-width: 0; min-width: 0;
} max-width: 0;
td:nth-child(4), th:nth-child(4) { }
min-width: 10%; td:nth-child(4),
white-space: normal; 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;
} }
@ -149,9 +153,9 @@ button {
} }
#trashmode span:last-child { #trashmode span:last-child {
display:inline-block; display: inline-block;
text-align:left; text-align: left;
width:2.8em; width: 2.8em;
} }
#filter > a.active { #filter > a.active {

View File

@ -1,65 +1,110 @@
<!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>
</div> <button
<div class="collapse navbar-collapse"> type="button"
class="btn btn-default"
<ul id="nav_links" class="nav navbar-nav"> onclick="socket.send('MPD_API_SET_STOP');"
<li id="queue"><a href="#/">Queue</a></li> >
<li id="browse"><a href="#/browse/0/">Browse</a></li> <span id="stop-icon" class="glyphicon glyphicon-stop"></span>
<li><a href="#" data-toggle="modal" data-target="#addstream">Add Stream</a></li> </button>
<li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li> <button
</ul> type="button"
class="btn btn-default"
<div class="btn-toolbar navbar-btn navbar-right" role="toolbar"> onclick="clickPlay();"
<div class="btn-group"> >
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_PREV');"> <span id="play-icon" class="glyphicon glyphicon-pause"></span>
<span class="glyphicon glyphicon-backward"></span> </button>
</button> <button
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_STOP');"> type="button"
<span id="stop-icon" class="glyphicon glyphicon-stop"></span> class="btn btn-default"
</button> onclick="socket.send('MPD_API_SET_NEXT');"
<button type="button" class="btn btn-default" onclick="clickPlay();"> >
<span id="play-icon" class="glyphicon glyphicon-pause"></span> <span class="glyphicon glyphicon-forward"></span>
</button> </button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_NEXT');"> </div>
<span class="glyphicon glyphicon-forward"></span> <div class="btn-group">
</button> <button
</div> id="btnlove"
<div class="btn-group"> type="button"
<button id="btnlove" type="button" class="btn btn-default" onclick="clickLove();"> class="btn btn-default"
<span class="glyphicon glyphicon-heart"></span> onclick="clickLove();"
</button> >
</div> <span class="glyphicon glyphicon-heart"></span>
<!-- </button>
</div>
<!--
<div class="btn-group"> <div class="btn-group">
<div class="btn btn-toolbar btn-default"> <div class="btn btn-toolbar btn-default">
<span id="volume-icon" class="glyphicon glyphicon-volume-up"></span> <span id="volume-icon" class="glyphicon glyphicon-volume-up"></span>
@ -67,293 +112,493 @@
</div> </div>
</div> </div>
--> -->
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<audio id="player" preload="none"></audio> <audio id="player" preload="none"></audio>
<button type="button" class="btn btn-default" onclick="clickLocalPlay()"> <button
<span id="localplay-icon" class="glyphicon glyphicon-play"></span> type="button"
</button> class="btn btn-default"
<button type="button" class="btn btn-default" onclick="window.open('/player.html');"> onclick="clickLocalPlay()"
<span id="localplay-icon" class="glyphicon glyphicon-new-window"></span> >
</button> <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> </div>
</div>
<form id="search" class="navbar-form navbar-right" role="search">
<form id="search" class="navbar-form navbar-right" role="search"> <div class="form-group">
<div class="form-group"> <input type="text" class="form-control" placeholder="Search" />
<input type="text" class="form-control" placeholder="Search"> </div>
</div> </form>
</form> </div>
</div><!--/.nav-collapse --> <!--/.nav-collapse -->
</div>
</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">
<div class="notifications top-right"></div> <!-- Default panel contents -->
<div class="panel-heading">
<b id="panel-heading">Queue</b>
<b id="panel-heading-info" class="text pull-right"></b>
</div>
<div class="panel-body">
<h1>
<span
id="track-icon"
onclick="clickPlay();"
class="glyphicon glyphicon-play"
></span>
<span id="currenttrack"></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 class="panel panel-primary"> <div id="progressbar"></div>
<!-- Default panel contents --> </div>
<div class="panel-heading"><b id="panel-heading">Queue</b> <!-- /.panel-body -->
<b id="panel-heading-info" class="text pull-right"></b></div>
<div class="panel-body"> <ol id="breadcrump" class="breadcrumb"></ol>
<h1>
<span id="track-icon" onclick="clickPlay();" class="glyphicon glyphicon-play"></span> <div class="col-md-12" id="filter"></div>
<span id="currenttrack"></span>
</h1> <!-- 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></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">
<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="artist" class="text"></span> <a href="http://www.ympd.org"
<span id="album" 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" id="filter"> </h6>
</div> <h6>
<a href="http://www.musicpd.org/libs/libmpdclient/"
>libMPDClient</a
<!-- Table --> >
<table id="salamisandwich" class="table table-hover"> <small>BSD License</small>
<thead> </h6>
<tr> <hr />
<th>#</th>
<th>Artist</th>
<th>Album</th>
<th>Title</th>
<th>Length</th>
<th></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">
<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><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 />
<div class="row">
<div class="form-group col-md-6">
<button type="button" class="btn btn-default btn-block" onclick="updateDB();">
<span class="glyphicon glyphicon-refresh"></span> Update Database
</button>
</div>
<div class="form-group col-md-6" data-toggle="buttons">
<button type="button" class="btn btn-default btn-block" id="btnnotify">
<span class="glyphicon glyphicon-comment"></span> Enable Notifications
</button>
</div>
</div>
<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="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"
>
<span class="glyphicon glyphicon-comment"></span> Enable
Notifications
</button>
</div> </div>
<div class="form-group col-md-12"> </div>
<div id="mpd_password_set" class="hide alert alert-info"> <hr />
<button type="button" class="close" aria-hidden="true">&times;</button> <form role="form">
MPD Password is set <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> </div>
</div> <div class="row">
<div class="row"> <div class="form-group col-md-6">
<div class="form-group col-md-12"> <label class="control-label" for="mpd_pw">MPD Password</label>
<label class="control-label" for="mpdstream">MPD Stream URL</label> <input
<input type="text" class="form-control" id="mpdstream" /> type="password"
class="form-control"
id="mpd_pw"
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="form-group col-md-12" data-toggle="buttons">
<button
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>
</form>
<div class="row">
<div class="form-group col-md-12" data-toggle="buttons">
<button 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>
<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="confirmSettings();">Save</button> <!-- /.modal-dialog -->
</div> </div>
</div><!-- /.modal-content --> <!-- /.modal -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="addstream" tabindex="-1" role="dialog" aria-labelledby="addstreamLabel" aria-hidden="true"> <div
<div class="modal-dialog"> class="modal fade"
<div class="modal-content"> id="addstream"
<div class="modal-header"> tabindex="-1"
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> role="dialog"
<h2 class="modal-title" id="addstreamLabel"><span class="glyphicon glyphicon-wrench"></span> Add Stream</h2> aria-labelledby="addstreamLabel"
</div> aria-hidden="true"
<div class="modal-body"> >
<form role="form"> <div class="modal-dialog">
<div class="row"> <div class="modal-content">
<div class="form-group col-md-12"> <div class="modal-header">
<label class="control-label" for="streamurl">Stream URL</label> <button
<input type="text" class="form-control" id="streamurl" /> 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> </div>
</div> </form>
</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-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
<div class="modal-dialog"> class="modal fade"
<div class="modal-content"> id="savequeue"
<div class="modal-header"> tabindex="-1"
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> role="dialog"
<h2 class="modal-title" id="savequeueLabel"><span class="glyphicon glyphicon-wrench"></span> Save Queue</h2> aria-labelledby="savequeueLabel"
</div> aria-hidden="true"
<div class="modal-body"> >
<form role="form"> <div class="modal-dialog">
<div class="row"> <div class="modal-content">
<div class="form-group col-md-9"> <div class="modal-header">
<label class="control-label" for="playlistname">Playlist Name</label> <button
<input type="text" class="form-control" id="playlistname" /> 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> </div>
</div> </form>
</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> </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="saveQueue();">Save Queue</button> <!-- /.modal-dialog -->
</div> </div>
</div><!-- /.modal-content --> <!-- /.modal -->
</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
<div class="modal-dialog"> class="modal fade bs-example-modal-sm"
<div class="modal-content"> id="wait"
<div class="modal-header"> tabindex="-1"
<h1>Searching...</h1> role="dialog"
</div> data-backdrop="static"
<div class="modal-body"> data-keyboard="false"
<div class="progress progress-striped active"> aria-hidden="true"
<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 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 +1,168 @@
<!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="viewport" content="width=320"> <meta name="viewport" content="width=320" />
<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 player</title> <title>ympd player</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/jquery-1.10.2.min.js"></script> href="assets/favicon.ico"
<script src="js/jquery.cookie.js"></script> rel="shortcut icon"
<script src="js/bootstrap.min.js"></script> type="image/vnd.microsoft.icon"
<script src="js/bootstrap-notify.js"></script> />
<script type="text/javascript"> <script src="js/jquery-1.10.2.min.js"></script>
function clickLocalPlay() { <script src="js/jquery.cookie.js"></script>
var player = document.getElementById('player'); <script src="js/bootstrap.min.js"></script>
$("#localplay-icon").removeClass("glyphicon-play").removeClass("glyphicon-pause"); <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');
}
}
if ( player.paused ) { $(document).ready(function () {
var mpdstream = $.cookie("mpdstream"); document
player.src = mpdstream; .getElementById('player')
console.log("playing mpd stream: " + player.src); .addEventListener('stalled', function () {
player.load(); if (!document.getElementById('player').paused) {
player.play(); this.pause();
$("#localplay-icon").addClass("glyphicon-pause"); clickLocalPlay();
} else { $('.top-right')
player.pause(); .notify({
player.src=''; message: {
player.removeAttribute("src"); text: 'music stream stalled - trying to recover...',
$("#localplay-icon").addClass("glyphicon-play"); },
} type: 'danger',
} fadeOut: { enabled: true, delay: 1000 },
})
.show();
}
});
$(document).ready(function(){ document
document.getElementById('player').addEventListener('stalled', function() { .getElementById('player')
if ( !document.getElementById('player').paused ) { .addEventListener('pause', function () {
this.pause(); this.src = '';
clickLocalPlay(); this.removeAttribute('src');
$('.top-right').notify({ $('#localplay-icon')
message:{text:"music stream stalled - trying to recover..."}, .removeClass('glyphicon-pause')
type: "danger", .addClass('glyphicon-play');
fadeOut: { enabled: true, delay: 1000 }, });
}).show();
}
});
document.getElementById('player').addEventListener('pause', function() { document.getElementById('player').addEventListener(
this.src=''; 'error',
this.removeAttribute("src"); function failed(e) {
$("#localplay-icon").removeClass("glyphicon-pause").addClass("glyphicon-play"); this.pause();
}); switch (e.target.error.code) {
case e.target.error.MEDIA_ERR_ABORTED:
document.getElementById('player').addEventListener('error', function failed(e) { $('.top-right')
this.pause(); .notify({
switch (e.target.error.code) { message: { text: 'Audio playback aborted by user.' },
case e.target.error.MEDIA_ERR_ABORTED: type: 'info',
$('.top-right').notify({ fadeOut: { enabled: true, delay: 1000 },
message:{text:"Audio playback aborted by user."}, })
type: "info", .show();
fadeOut: { enabled: true, delay: 1000 }, break;
}).show(); case e.target.error.MEDIA_ERR_NETWORK:
break; $('.top-right')
case e.target.error.MEDIA_ERR_NETWORK: .notify({
$('.top-right').notify({ message: { text: 'Network error while playing audio.' },
message:{text:"Network error while playing audio."}, type: 'danger',
type: "danger", fadeOut: { enabled: true, delay: 1000 },
fadeOut: { enabled: true, delay: 1000 }, })
}).show(); .show();
break; break;
case e.target.error.MEDIA_ERR_DECODE: case e.target.error.MEDIA_ERR_DECODE:
$('.top-right').notify({ $('.top-right')
message:{text:"Audio playback aborted. Did you unplug your headphones?"}, .notify({
type: "danger", message: {
fadeOut: { enabled: true, delay: 1000 }, text: 'Audio playback aborted. Did you unplug your headphones?',
}).show(); },
break; type: 'danger',
case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED: fadeOut: { enabled: true, delay: 1000 },
$('.top-right').notify({ })
message:{text:"Error while loading audio (server, network or format error)."}, .show();
type: "danger", break;
fadeOut: { enabled: true, delay: 1000 }, case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
}).show(); $('.top-right')
break; .notify({
default: message: {
$('.top-right').notify({ text: 'Error while loading audio (server, network or format error).',
message:{text:"Unknown error while playing audio."}, },
type: "danger", type: 'danger',
fadeOut: { enabled: true, delay: 1000 }, fadeOut: { enabled: true, delay: 1000 },
}).show(); })
break; .show();
} break;
}, true); default:
}); $('.top-right')
</script> .notify({
</head> message: { text: 'Unknown error while playing audio.' },
<body> type: 'danger',
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> fadeOut: { enabled: true, delay: 1000 },
<div class="container"> })
<a class="navbar-brand" href="/" target="_blank"><span class="glyphicon glyphicon-play-circle"></span> ympd</a> .show();
</div> break;
</div> }
},
<div class="container starter-template"> true
<div class="row"> );
<div class="col-md-10 col-xs-12"> });
<audio id="player" preload="none"></audio> </script>
<button type="button" class="btn btn-default btn-lg center-block" onclick="clickLocalPlay()"> </head>
<span id="localplay-icon" class="glyphicon glyphicon-play"></span> <body>
</button> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="notifications top-right"></div> <div class="container">
<a class="navbar-brand" href="/" target="_blank"
><span class="glyphicon glyphicon-play-circle"></span> ympd</a
>
</div> </div>
</div> </div>
</div>
</body> <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> </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,22 @@
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 <string.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; 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,15 +16,16 @@
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 <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"
@ -32,44 +33,39 @@
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; 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 (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_UPDATE_DB: case MPD_API_UPDATE_DB:
mpd_run_update(mpd.conn, NULL); mpd_run_update(mpd.conn, NULL);
break; break;
@ -92,43 +88,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: case MPD_API_RM_RANGE:
if(sscanf(c->content, "MPD_API_RM_RANGE,%u,%u", &uint_buf, &uint_buf_2)) 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); mpd_run_delete_range(mpd.conn, uint_buf, uint_buf_2);
break; 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:
@ -145,123 +140,123 @@ 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: case MPD_API_SEND_MESSAGE:
p_charbuf = strdup(c->content); p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_SEND_MESSAGE")) if (strcmp(strtok(p_charbuf, ","), "MPD_API_SEND_MESSAGE"))
goto out_send_message;
if((token = strtok(NULL, ",")) == NULL)
goto out_send_message; goto out_send_message;
free(p_charbuf); if ((token = strtok(NULL, ",")) == NULL)
goto out_send_message;
free(p_charbuf);
p_charbuf = strdup(get_arg1(c->content)); p_charbuf = strdup(get_arg1(c->content));
if ( strtok(p_charbuf, ",") == NULL ) if (strtok(p_charbuf, ",") == NULL)
goto out_send_message; goto out_send_message;
if ( (token = strtok(NULL, ",")) == NULL ) if ((token = strtok(NULL, ",")) == NULL)
goto out_send_message; goto out_send_message;
mpd_run_send_message(mpd.conn, p_charbuf, token); mpd_run_send_message(mpd.conn, p_charbuf, token);
out_send_message: out_send_message:
free(p_charbuf); free(p_charbuf);
break; break;
#ifdef WITH_MPD_HOST_CHANGE #ifdef WITH_MPD_HOST_CHANGE
@ -269,13 +264,13 @@ out_send_message:
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));
@ -283,55 +278,56 @@ out_send_message:
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)
free(c->connection_param); free(c->connection_param);
return 0; return 0;
} }
@ -339,41 +335,36 @@ int mpd_close_handler(struct mg_connection *c)
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->callback_param) {
{
/* error message? */ /* error message? */
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}", n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",
(const char *)c->callback_param); (const char *)c->callback_param);
mg_websocket_write(c, 1, mpd.buf, n); mg_websocket_write(c, 1, mpd.buf, n);
return MG_TRUE; return MG_TRUE;
} }
if(!c->connection_param) if (!c->connection_param)
c->connection_param = calloc(1, sizeof(struct t_mpd_client_session)); 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 (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;
@ -383,8 +374,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 */
@ -398,8 +388,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);
} }
@ -407,11 +396,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);
} }
@ -424,8 +411,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);
} }
@ -436,7 +422,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;
@ -444,14 +430,12 @@ 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; c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL); mpd_notify_callback(c, MG_POLL);
} }
@ -459,56 +443,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;
@ -520,23 +499,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);
@ -544,16 +518,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;
@ -561,11 +534,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)) {
@ -573,18 +546,17 @@ 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_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\":");
@ -603,23 +575,22 @@ 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; 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; 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); drtn = mpd_song_get_duration(song);
@ -655,8 +626,7 @@ int mpd_put_queue(char *buffer, unsigned int offset)
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;
@ -665,25 +635,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;
} }
@ -738,23 +705,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\":");
@ -773,8 +739,7 @@ int mpd_search(char *buffer, char *searchstr)
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;
} }
@ -788,9 +753,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,63 +15,61 @@
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 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_RANGE) \ X(MPD_API_RM_RANGE) \
X(MPD_API_RM_ALL) \ X(MPD_API_RM_ALL) \
X(MPD_API_MOVE_TRACK) \ X(MPD_API_MOVE_TRACK) \
X(MPD_API_SEARCH) \ X(MPD_API_SEARCH) \
X(MPD_API_SEND_MESSAGE) \ X(MPD_API_SEND_MESSAGE) \
X(MPD_API_SET_VOLUME) \ X(MPD_API_SET_VOLUME) \
X(MPD_API_SET_PAUSE) \ X(MPD_API_SET_PAUSE) \
X(MPD_API_SET_PLAY) \ X(MPD_API_SET_PLAY) \
X(MPD_API_SET_STOP) \ X(MPD_API_SET_STOP) \
X(MPD_API_SET_SEEK) \ X(MPD_API_SET_SEEK) \
X(MPD_API_SET_NEXT) \ X(MPD_API_SET_NEXT) \
X(MPD_API_SET_PREV) \ X(MPD_API_SET_PREV) \
X(MPD_API_SET_MPDHOST) \ X(MPD_API_SET_MPDHOST) \
X(MPD_API_SET_MPDPASS) \ X(MPD_API_SET_MPDPASS) \
X(MPD_API_UPDATE_DB) \ X(MPD_API_UPDATE_DB) \
X(MPD_API_GET_OUTPUTS) \ X(MPD_API_GET_OUTPUTS) \
X(MPD_API_TOGGLE_OUTPUT) \ X(MPD_API_TOGGLE_OUTPUT) \
X(MPD_API_TOGGLE_RANDOM) \ X(MPD_API_TOGGLE_RANDOM) \
X(MPD_API_TOGGLE_CONSUME) \ X(MPD_API_TOGGLE_CONSUME) \
X(MPD_API_TOGGLE_SINGLE) \ 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)
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,
@ -83,10 +81,10 @@ enum mpd_conn_states {
struct t_mpd { struct t_mpd {
int port; int port;
int local_port; int local_port;
char host[128]; char host[128];
char *password; char *password;
char *gpass; char *gpass;
struct mpd_connection *conn; struct mpd_connection *conn;
enum mpd_conn_states conn_state; enum mpd_conn_states conn_state;
@ -117,4 +115,3 @@ 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,25 +16,24 @@
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;
} }
@ -42,14 +41,14 @@ 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;
@ -61,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 ( (mpd.gpass == NULL) || (c->is_websocket) || ((mpd.local_port > 0) && (c->local_port == mpd.local_port)) ) 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(mpd.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);
} }
@ -75,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;
@ -92,9 +91,10 @@ 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.local_port = 0;
mpd.gpass = NULL; 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'},
@ -107,9 +107,9 @@ int main(int argc, char **argv)
{"mpdpass", required_argument, 0, 'm'}, {"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:l:w:u:d:v:m", 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':
mpd.gpass = strdup(optarg); mpd.gpass = strdup(optarg);
@ -134,14 +134,19 @@ int main(int argc, char **argv)
mpd.password = strdup(optarg); mpd.password = strdup(optarg);
break; 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,
"Usage: %s [OPTION]...\n\n"
" -D, --digest <htdigest>\tpath to htdigest file for authorization\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"
@ -150,31 +155,30 @@ int main(int argc, char **argv)
" -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"
" -m, --mpdpass <password>\tspecifies the password to use when connecting to mpd\n" " -m, --mpdpass <password>\tspecifies the password to use when connecting "
" --help\t\t\t\tthis help\n" "to mpd\n"
, argv[0]); " --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;
} }
@ -183,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);
} }